Problem/Motivation

sprintf() does not add value over using a double-quoted string for scenarios like test assertions and exception messages, where no data type conversion or sanitization is needed, and it generally decreases readability. Compare:

  1. "A string with $bar, {$foo['a']}, $baz, $a and $c, plus also $b embedded";
    
  2. sprintf('A string with %s, %s, %s, %s and %s, plus also %s embedded', $bar, {$foo['a']}, $baz, $a, $c, $b);
    

Proposed resolution

  1. Where possible, use a pure string literal with a fixed test value in test assertions (rather than defining random names for test data). E.g:
    $session->assertWhatever("This is test data we're asserting for article1 with term2 or whatever");
    
  2. If variables must be used (or as an intermediate step between removing excess function calls and removing random fixture values), use a double-quoted string:
    $session->assertWhatever("This is test data we're asserting for $article1 with {$term2['name']} or whatever");
    
  3. As a last resort, use string concatenation or additional local variables. Avoid adding sprintf(), t(), FormattableMarkup(), etc. to either test assertions or exception messages.
  4. Maybe: Only consider sprintf() for deeply nested array structures or method calls.

References

Comments

xmacinfo created an issue. See original summary.

cilefen’s picture

Issue tags: +Performance
alexpott’s picture

Replacing sprintf() is exceptions that are not at all part of the regular runtime is not going to improve performance. Also there are cases where sprintf() improves readability and there are probably cases where it does not. With respect to exception messages I think we should prioritise the reader over the computer.

xmacinfo’s picture

Also there are cases where sprintf() improves readability…

I have not found yet any use of sprintf() that improves readability.

xmacinfo’s picture

Issue summary: View changes

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

pfrenssen’s picture

Those two articles are not very convincing with PHP 8. They are also not exactly authoritative sources. One of them got called out for doing performance analysis while having xdebug enabled, and the other was written by a CS student 12 years ago. But the main thing is that they were testing using PHP 5. Things have changed quite a lot in PHP land in the meantime.

I just did a test concatenating two strings a million times with PHP 8.1.8: with sprintf() this took 0.046515 seconds, while with the concatenation operator it took 0.024491 seconds. So we can potentially gain a whopping 22 milliseconds, at least if we manage to eliminate 1 million calls to sprintf() from the request.

Probably the only code path we have that gets potentially called a million times in a single request is the renderer. I had a look at it and it is not using sprintf() right now (except in exception messages).

So this is probably not going to yield us any significant performance gains.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

xjm’s picture

I think the hypothesized performance aspect of this is needless micro-optimization. (That said, the addition to the call stack is unnecessary.)

The reason not to use sprintf() -- and the reason we've avoided it historically -- is that it adds absolutely no value over simply using a double-quoted string. Compare:

  1. "A string with $bar, {$foo['a']}, $baz, $a and $c, plus also $b embedded";
    
  2. sprintf('A string with %s, %s, %s, %s and %s, plus also %s embedded', $bar, {$foo['a']}, $baz, $a, $c, $b);
    

The first is much closer to natural language. The second is significantly longer and less readable, plus it's easy to lose track of which %s refers to what.

The only debatable case is if something requires concatenation (e.g. a function call is used), and even then, I find sprintf() makes it less readable. In those cases, defining a local variable with the function call (and thus avoiding concatenation in favor of something that reads like natural language) is probably a more desirable alternative than either concatenation or sprintf().

For tests, it's best to go a step further and use literal fixture values instead (we've been trying to move away from randomMachineName() etc. for years), but a double-quoted string is preferable over sprintf() when variables are necessary or as an intermediate step.

xjm’s picture

Issue summary: View changes

Updated the IS to de-emphasize the micro-optimization in favor of the readability aspects.

xjm’s picture

Issue summary: View changes

Fixing typos in the IS.

mstrelan’s picture

I've been looking for a rector rule or phpcs sniff for this but can only find the inverse.

Whereas phpstorm has the Convert a 'sprintf()' call to string interpolation intention.

For implementation it would be good to find a way to automate this.

longwave’s picture

The simple cases with bare variable names in #13 are fine for interpolation, but when it comes to interpolating the results of method calls or deep array structures I personally prefer the sprintf style.

To me there is no single best option here and we should have some flexibility on this, and therefore I think this is won't fix.

xjm’s picture

Issue summary: View changes

I added a case 4 for #17, but I really think we should discourage people from using it when it reduces readability (which is most of the time). People use it for antiquated reasons that don't apply for test assertions or exception messages. More often than not, it's an antipattern. (Even in the cases in #17 I personally would also avoid it, but I see people split 50/50 on that so I can see the counter-argument for it.)

acbramley’s picture

We always use sprintf on internal projects, I find it more readable in most cases (when you only have 1 or 2 variables). I agree that the example in the IS is slightly more readable with embedded variables but I agree with #17 that this shouldn't be hard enforced.

pfrenssen’s picture

I vote for won't fix. We shouldn't gatekeep functions from the standard library.

xjm’s picture

We "gatekeep" functional PHP code for readability all the time; that's what coding standards are.

Regarding performance, #3343913: Add comments explaining performance improvement in TypedData has a comment contradicting the above (apparently relating to a real performance issue, I guess because that one was in the critical path).

larowlan’s picture

Project: Drupal core » Coding Standards
Version: 11.x-dev »
Component: other » Coding Standards
Issue tags: +Needs issue summary update

Moving this to the coding standards project where decisions about policies/standards like this live.

Tagging for needing issue summary update as there is a prescribed format for coding standards issues

My 2c, simple variables should use pure string literals with variables as required (ie "Something $wicked this {$way['comes']}") and sprintf if there are expressions or functions sprintf('Something %s this way %s, $wicked, $way->comes())