string.Format() in PHP[source]

xml
<glacius:metadata>
    <title>string.Format() in PHP</title>
    <description>Implementation of .NET-style string.Format() in PHP</description>
    <category>PHP</category>
    <category>C#</category>
    <category>Programming</category>
    <category>Legacy blog posts</category>
</glacius:metadata>
<glacius:macro name="legacy blargh banner">
    <properties>
        <originalUrl>https://tmont.com/blargh/2010/1/string-format-in-php</originalUrl>
        <originalDate>2010-01-09T22:22:08.000Z</originalDate>
    </properties>
</glacius:macro>
<p>
  .NET's <a href="https://www.php.net/sprintf"><code>sprintf()</code></a> 
  <a href="https://docs.microsoft.com/en-us/dotnet/api/system.string.format?view=net-6.0">equivalent</a> 
  has ordered parameters.
</p>
<glacius:code lang="csharp"><![CDATA[var s = string.Format("My name is {1}, {0}", "John", "Doe");
//s = "My name is Doe, John"
var s = string.Format("My name is {{{0}}}", "John");
//s = "My name is {John}"]]></glacius:code>
<p>I needed that. Here it is, PHPified:</p>
<glacius:code lang="php"><![CDATA[function format($format) {
	$args = func_get_args();
	$format = array_shift($args);
	
	preg_match_all('/(?=\{)\{(\d+)\}(?!\})/', $format, $matches, PREG_OFFSET_CAPTURE);
	$offset = 0;
	foreach ($matches[1] as $data) {
		$i = $data[0];
		$format = substr_replace($format, @$args[$i], $offset + $data[1] - 1, 2 + strlen($i));
		$offset += strlen(@$args[$i]) - 2 - strlen($i);
	}
	
	return $format;
}]]></glacius:code>
<p>
  There's a lot going on in here that isn't used very often in PHP. Here is a terse 
  explanation of the not-so-obvious features:
</p>
<ul>
  <li>
    The <a href="https://www.php.net/manual/en/pcre.constants.php#constant.preg-offset-capture"><code>PREG_OFFSET_CAPTURE</code></a> 
    constant tells the regex engine to capture the string index offset of each match.
  </li>
  <li>
    <code>(?=\{)</code> and <code>(?!\})</code> in the regular expression are 
    <a href="https://www.regular-expressions.info/lookaround.html">positive and negative lookarounds</a>, 
    respectively. The nice thing about that is they're also zero-width matching, which means they 
    won't be returned as part of the match result. These are needed because to output a 
    literal <code>{</code> in a C# string you precede it with another <code>{</code> (a little known 
    fact about C#).
  </li>
  <li>
    <a href="https://www.php.net/manual/en/function.substr-replace.php"><code>substr_replace</code></a> is 
    an infrequently used, extremely useful function, that replaces a substring within a string, 
    expanding to the given width.
  </li>
  <li>
    The <code>$offset</code> variable tracks the string length delta. Since we're modifying the
    string, the offsets captured in the regex match becomes out of date.
  </li>
</ul>