Does PHP have a feature like Python's template strings?
Asked Answered
L

8

58

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?

Ling answered 7/10, 2011 at 4:44 Comment(1)
See: packagist.org/packages/nicmart/string-templateChurning
O
79

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
Ottie answered 9/3, 2018 at 12:47 Comment(2)
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.Otha
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 databaseAutomaton
H
93

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.

Heredia answered 17/6, 2019 at 12:9 Comment(10)
This template string approach is similar to the template string approach in Javascript.Ferrick
This one should be the right answer. Only one thing, why does it not work with single apices?Antisepticize
@Antisepticize Not sure, but for example the same as " $var="A" and echo " $var " works and ' $var ' does not? (notice single quotes)Heredia
@Heredia 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 behaviourAntisepticize
@Antisepticize Right, it is related to this #3446716Heredia
What's the point in adding brackets here? It looks to be the same as using "Hello $name $info[last_name]}" in your code: try it onlineBeetlebrowed
your missing the point, you can do it of course, but this is just to demonstrate that you can do it, even for objects-keys, where is not simple the var value itself. Like you have a context array, and you want to fill with different from from that context array...Heredia
This answer is missing the point. The OP said "but the templates can be reused in various sections of the code". This however can be achieved using eval(), see Example #1 in php.net/manual/en/function.eval.phpTriturable
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 variablesIntima
@Intima is correct, this requires the vars to be available for them to "convert", that's what the OP meant with define the template before variables, and re-use. But like Tilman Vogel said, use evalHeredia
O
79

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
Ottie answered 9/3, 2018 at 12:47 Comment(2)
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.Otha
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 databaseAutomaton
L
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.

Laden answered 7/10, 2011 at 4:49 Comment(2)
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 threadLing
There is a typo in your second link name BTW. (sprtinf)Lingenfelter
C
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...

Candie answered 30/8, 2016 at 2:56 Comment(0)
R
5

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.

Rhizotomy answered 25/10, 2021 at 22:57 Comment(1)
There's an answer with an additional function/class to simulate sprintf – it's been builtin since PHP 4.Rhizotomy
O
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;
}
Oakleil answered 5/11, 2012 at 17:55 Comment(0)
D
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.
Devious answered 13/12, 2022 at 16:55 Comment(0)
B
1

An other option is to use

<?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]);
Brunson answered 5/6, 2023 at 10:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.