64

Python has a feature called template strings.

>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'

I know that PHP allows you to write:

"Hello $person"

and have $person substituted, but the templates can be reused in various sections of the code?

1

8 Answers 8

103

You can use template strings like this:

$name = "Maria";
$info["last_name"] = "Warner";

echo "Hello {$name} {$info["last_name"]}";

This will echo Hello Maria Warner.

Sign up to request clarification or add additional context in comments.

10 Comments

This template string approach is similar to the template string approach in Javascript.
This one should be the right answer. Only one thing, why does it not work with single apices?
@EuberDeveloper Not sure, but for example the same as " $var="A" and echo " $var " works and ' $var ' does not? (notice single quotes)
@Miguel I tried on repl.it exactly the above code of the commented answer. If you substitute the " with the ', it stops working. But, reading php docs, it seems the normal behaviour
I'm really surprised this is so highly voted. It totally misses the point of the question where the template string is defined before the variables
|
81

You could also use strtr:

$template = '$who likes $what';

$vars = array(
  '$who' => 'tim',
  '$what' => 'kung pao',
);

echo strtr($template, $vars);

Outputs:

tim likes kung pao

2 Comments

One of the cons of this approach is that no IDE can help you validating the template and its parameters, you're basically on your own.
This is a better approach when storing/retrieve a string externally while maintaining the parameters (instead of values at that time). e.g. when using a database
14

I think there are a bunch of ways to do this... but this comes to mind.

$search = array('%who%', '%what_id%');
$replace = array('tim', 'kung pao');
$conference_target = str_replace(
    $search,
    $replace,
    "%who% likes %what%"
);

Ha, we even had one in our framework using vsprintf:

class Helper_StringFormat {

    public static function sprintf($format, array $args = array()) {

        $arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);

        for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_]\w*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
            $arg_pos = $match[0][2];
            $arg_len = strlen($match[0][0]);
            $arg_key = $match[1][0];

            if (! array_key_exists($arg_key, $arg_nums)) {
                user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
                return false;
            }
            $format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
            $pos = $arg_pos + strlen($replace);
        }

        return vsprintf($format, array_values($args));
    }
}

Which looks like it came from the sprintf page

This allows for calls like:

sprintfn('second: %(second)s ; first: %(first)s', array(
    'first' => '1st',
    'second'=> '2nd'
));

UPDATE
Here is an update to do what you want... not fully tested though

class Helper_StringFormat {

    public static function sprintf($format, array $args = array()) {
        $arg_nums = array_slice(array_flip(array_keys(array(0 => 0) + $args)), 1);

        for ($pos = 0; preg_match('/(?<=%)\(([a-zA-Z_][\w\s]*)\)/', $format, $match, PREG_OFFSET_CAPTURE, $pos);) {
            $arg_pos = $match[0][1];
            $arg_len = strlen($match[0][0]);
            $arg_key = $match[1][0];

            if (! array_key_exists($arg_key, $arg_nums)) {
                user_error("sprintfn(): Missing argument '${arg_key}'", E_USER_WARNING);
                return false;
            }
            $format = substr_replace($format, $replace = $arg_nums[$arg_key] . '$', $arg_pos, $arg_len);
            $pos = $arg_pos + strlen($replace); // skip to end of replacement for next iteration
        }

        return vsprintf($format, array_values($args));
    }
}

$str = "%(my var)s now work with a slight %(my var2)s";
$repl = array("my var" => "Spaces", "my var2" => "modification.");

echo Helper_StringFormat::sprintf($str, $repl);

OUTPUT
Spaces now work with a slight modification.

2 Comments

That's nice, except there is no way to escape a %(some character) sequence. If I ever get round to fixing this, I'll post the code in this thread
There is a typo in your second link name BTW. (sprtinf)
9

Another more simple approach would be this:

$s = function ($vars) {
    extract($vars);
    return "$who likes $what";
};
echo $s(['who' => 'Tim', 'what' => 'King Pao']); // Tim likes King Pao

And yes, PHPStorm will complain...

Comments

6

I personally most like sprintf (or vsprintf, for an array of arguments). It places them in the intended order, coerces types as needed, and has a lot more advanced features available.

Example:

$var = sprintf("%s costs %.2f dollars", "Cookies", 1.1);

This will result in the value Cookies cost 1.10 dollars.

There's an entire family of printf functions for different use cases, all listed under "See Also".

Very versatile: same methods for providing variables, array components, function results, etc.

1 Comment

There's an answer with an additional function/class to simulate sprintf – it's been builtin since PHP 4.
2

I made a function to do what you want. i made it "quck-and-dirty" because i have not much time to refactorize it, maybe i upload it to my github.

EDIT: a bug correction...

Use it like

    formattemplatter(
                     '$who likes $what'
                     , array(
                               'who'  => 'Tim'
                             , 'what' => 'Kung Pao'
                     )
    );

Variables can be [a-zA-Z0-9_] only.

 function formattemplater($string, $params) {
    // Determine largest string
    $largest = 0;
    foreach(array_keys($params) as $k) {
        if(($l=strlen($k)) > $largest) $largest=$l;
    }

    $buff   = '';

    $cp     = false;    // Conditional parenthesis
    $ip     = false;    // Inside parameter
    $isp    = false;    // Is set parameter

    $bl     = 1;    // buffer length
    $param  = '';   // current parameter

    $out    = '';  // output string
    $string .= '!';

    for($sc=0,$c=$oc='';isset($string{$sc});++$sc,++$bl) {
        $c = $string{$sc};

        if($ip) {
            $a = ord($c);

            if(!($a == 95 || (                  // underscore
                    ($a >= 48 && $a <= 57)      // 0-9
                    || ($a >= 65 && $a <= 90)   // A-Z
                    || ($a >= 97 && $a <= 122)  // a-z
                )
            )) {

                $isp = isset($params[$buff]);

                if(!$cp && !$isp) {
                    trigger_error(
                            sprintf(
                                    __FUNCTION__.': the parameter "%s" is not defined'
                                    , $buff
                            )
                            , E_USER_ERROR
                    );
                } elseif(!$cp || $isp) {
                    $out    .= $params[$buff];
                }

                $isp    = $isp && !empty($params[$buff]);
                $oc     = $buff = '';
                $bl     = 0;
                $ip     = false;
            }
        }

        if($cp && $c === ')') {
            $out .= $buff;

            $cp = $isp = false;
            $c  = $buff = '';
            $bl = 0;
        }

        if(($cp && $isp) || $ip)
            $buff .= $c;

        if($c === '$' && $oc !== '\\') {
            if($oc === '(')  $cp = true;
            else $out .= $oc;

            $ip   = true;
            $buff = $c = $oc = '';
            $bl   = 0;
        }

        if(!$cp && $bl > $largest) {
            $buff   = substr($buff, - $largest);
            $bl     = $largest;
        }

        if(!$ip && ( !$cp || ($cp && $isp))) {
            $out .= $oc;
            if(!$cp) $oc = $c;
        }
    }

    return $out;
}

Comments

1

Just for the sake of completeness: there is also Heredoc.

$template = fn( $who, $what ) => <<<EOT
    $who likes $what
EOT;

echo( $template( 'tim', 'kung pao' ) );

Outputs:

tim likes kung pao

Sidenotes:

  • You get highlighting in your favourite language (if properly configured). Just substitute EOT (from the sample above) with whatever you like (e.c. HTML, SQL, PHP, ...).
  • Escape arrays with curly braces {$data['who']}. Accessing objekts like $data->who works without braces.
  • Arrow functions like fn($a)=>$a are available since PHP 7.4. You can write function($a){return $a;} if you are using PHP<7.4.

Comments

1

An other option is to use MessageFormatter as function or object:

<?php
echo msgfmt_format_message('en_GB', 'Tom has {0, plural, =0{no cat} =1{a cat} other{# cats}}', [0]), "\n";
echo msgfmt_format_message('en_GB', 'Tom has {0, plural, =0{no cat} =1{a cat} other{# cats}}', [1]), "\n";
echo msgfmt_format_message('en_GB', 'Tom has {0, plural, =0{no cat} =1{a cat} other{# cats}}', [2]), "\n";
    
echo msgfmt_format_message('de', 'Tom hat {cat, plural, =0{keine Katze} =1{eine Katze} other{# Katzen}} und {dog} Hunde', ['cat'=>12345, 'dog'=>-5.23]), "\n";
    
echo MessageFormatter::formatMessage('de', 'Tom hat {cat, plural, =0{keine Katze} =1{eine Katze} other{# Katzen}} und {dog,number} Hunde', ['cat'=>12, 'dog'=>-5.23]), "\n";
    
$fmt = new MessageFormatter('de', 'Tom hat {cat, plural, =0{keine Katze} =1{eine Katze} other{# Katzen}} und {dog,number} Hunde');
echo $fmt->format(['cat'=>12, 'dog'=>-5.23]);

Output:

  • Tom has no cat
  • Tom has a cat
  • Tom has 2 cats
  • Tom hat 12.345 Katzen und -5.23 Hunde
  • Tom hat 12 Katzen und -5,23 Hunde
  • Tom hat 12 Katzen und -5,23 Hunde

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.