50

I have string like this in database (the actual string contains 100s of word and 10s of variable):

I am a {$club} fan

I echo this string like this:

$club = "Barcelona";
echo $data_base[0]['body'];

My output is I am a {$club} fan. I want I am a Barcelona fan. How can I do this?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
open source guy
  • 2,727
  • 8
  • 38
  • 61
  • 1
    [str_replace()](http://php.net/manual/en/function.str-replace.php) – Eggplant Feb 25 '13 at 11:00
  • 1
    my string contain 20 variable like this. need i use str_replace() fn 20 times? – open source guy Feb 25 '13 at 11:06
  • preg_replace is what you are looking for. – Dipesh Parmar Feb 25 '13 at 11:07
  • You could actually remove `{` and `}` from the string and just use double quotes around it to get the values stored in those variables (if I've understood what you are actually doing): `echo "My var's value is $var";`. This is VERY bad however. Probably it's better to have an array which stores those values, and use a `for` to replace them. – Eggplant Feb 25 '13 at 11:09
  • 5
    `{$club}` is valid PHP syntax for a double quote string interpretation. use this to your advantage. – zamnuts Feb 25 '13 at 11:14
  • possible duplicate of [Replacing variables in a string](http://stackoverflow.com/questions/18197348/replacing-variables-in-a-string) - also there is http://php.net/get_defined_vars – hakre Jan 12 '14 at 10:36

13 Answers13

103

Use strtr. It will translate parts of a string.

$club = "Barcelona";
echo strtr($data_base[0]['body'], array('{$club}' => $club));

For multiple values (demo):

$data_base[0]['body'] = 'I am a {$club} fan.'; // Tests

$vars = array(
  '{$club}'       => 'Barcelona',
  '{$tag}'        => 'sometext',
  '{$anothertag}' => 'someothertext'
);

echo strtr($data_base[0]['body'], $vars);

Program Output:

I am a Barcelona fan.
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Husman
  • 6,819
  • 9
  • 29
  • 47
  • 3
    my string contain 20 variable like this. need i use str_replace() fn 20 times? – open source guy Feb 25 '13 at 11:04
  • That would be the easy way out. You can also use a combination of associative arrays, and a loop. See my code above. – Husman Feb 25 '13 at 11:07
  • @messifan With this solution, make sure to remember to update your associateive array every time you add another variable into a template. – Aleks G Feb 25 '13 at 11:27
  • ok, thanks. $array should be like this array('{$club}' => "Barcelona") double quotes producing error for me – open source guy Feb 25 '13 at 11:29
  • 2
    -1 (ideally) But for multiple times. So this answer allows to inject variables as text. To overcome this, use http://php.net/strtr – hakre Jan 12 '14 at 10:27
  • @hakre - In your edit, I see the benefit of using `strtr` in the multiple-value case. But I don't see why you also changed to using `strtr` in the first (single-value) case. Isn't the result the same as the (slightly) simpler `str_replace` code you removed? – ToolmakerSteve Apr 24 '19 at 22:52
  • I've realized the dollar sign `$` is not necessary. I can set inside the text template "{club}" and it works. A bit easier for the final user – Pathros Dec 06 '22 at 19:06
8
/**
 * A function to fill the template with variables, returns filled template.
 *
 * @param string $template A template with variables placeholders {$variable}.
 * @param array $variables A key => value store of variable names and values.
 *
 * @return string
 */

public function replaceVariablesInTemplate($template, array $variables){

 return preg_replace_callback('#{(.*?)}#',
       function($match) use ($variables){
            $match[1] = trim($match[1], '$');
            return $variables[$match[1]];
       },
       ' ' . $template . ' ');
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tiffin joe
  • 89
  • 1
  • 1
8

I would suggest the sprintf() function.

Instead of storing I am a {$club} fan, use I am a %s fan, so your echo command would go like:

$club = "Barcelona";

echo sprintf($data_base[0]['body'],$club);

Output: I am a Barcelona fan

That would give you the freedom of use that same code with any other variable (and you don't even have to remember the variable name).

So this code is also valid with the same string:

$food = "French fries";

echo sprintf($data_base[0]['body'], $food);

Output: I am a French fries fan

$language = "PHP";

echo sprintf($data_base[0]['body'], $language);

Output: I am a PHP fan

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Moises Gumbs
  • 81
  • 1
  • 2
  • 1
    This is a useful technique, when it is convenient to provide the replacement value(s) that are specific to one string. Note that this would not be convenient for OP - nor for anyone else who has a language-translation dictionary that they wish to apply to a large number of sentences. For example, if there are three sentences `.. $a .. $b`, `.. $c .. $b ..`, and `..$c .. $a .. $b`, there is no convenient way to substitute values into all three sentences. (Where all the `$a` should become the same translated string.) – ToolmakerSteve Apr 24 '19 at 23:11
5

Edit: This answer still gets upvotes, so people need to be aware that there's a security vulnerability in the naive interpolation technique present in the below code snippets. An adversary could include arbitrary variables in the input string which would reveal information about the server or other data in the runtime variable register. This is due to the way the general expression search is performed in that it finds any arbitrary variable name pattern, and then uses those variable names verbatim in the subsequent compact call. This causes clients to control server-side behavior similar to eval. I'm leaving this answer for posterity.


You are looking for nested string interpolation. A theory can be read in the blog post Wanted: PHP core function for dynamically performing double-quoted string variable interpolation.

The major problem is that you don't really know all of the variables available, or there may be too many to list.

Consider the following tested code snippet. I stole the regex from Mohammad Mohsenipur.

$testA = '123';
$testB = '456';
$testC = '789';
$t = '{$testA} adsf {$testB}adf 32{$testC} fddd{$testA}';

echo 'before: ' . $t . "\n";

preg_match_all('~\{\$(.*?)\}~si', $t, $matches);
if ( isset($matches[1])) {
    $r = compact($matches[1]);
    foreach ( $r as $var => $value ) {
        $t = str_replace('{$' . $var . '}', $value, $t);
    }
}

echo 'after: ' . $t . "\n";

Your code may be:

$club = 'Barcelona';
$tmp = $data_base[0]['body'];
preg_match_all('~\{\$(.*?)\}~si', $tmp, $matches);
if ( isset($matches[1])) {
    $r = compact($matches[1]);
    foreach ( $r as $var => $value ) {
        $tmp = str_replace('{$' . $var . '}', $value, $tmp);
    }
}
echo $tmp;
zamnuts
  • 9,492
  • 3
  • 39
  • 46
  • 1
    To clarify, this technique is useful if you have an unknown set of global variables that you wish to substitute into strings. If the set of substitutions is known beforehand, then a straightforward string substitution (with an array of from/to pairs) is more straightforward (see accepted answer). – ToolmakerSteve Apr 24 '19 at 23:26
3
if (preg_match_all('#\$([a-zA-Z0-9]+)#', $q, $matches, PREG_SET_ORDER));
{
    foreach ($matches as $m)
    {
        eval('$q = str_replace(\'' . $m[0] . '\', $' . $m[1] . ', $q);');
    }
}

This matches all $variables and replaces them with the value.

I didn't include the {}'s, but it shouldn't be too hard to add them something like this...

if (preg_match_all('#\{\$([a-zA-Z0-9]+)\}#', $q, $matches, PREG_SET_ORDER));
{
    foreach ($matches as $m)
    {
        eval('$q = str_replace(\'' . $m[0] . '\', $' . $m[1] . ', $q);');
    }
}

Though it seems a bit slower than hard coding each variable. And it introduces a security hole with eval. That is why my regular expression is so limited. To limit the scope of what eval can grab.

I wrote my own regular expression tester with Ajax, so I could see, as I type, if my expression is going to work. I have variables I like to use in my expressions so that I don't need to retype the same bit for each expression.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 2
    Given how easy it would be to make a mistake and open a security hole (as you mention), I don't see any justification for using `eval` simply to accomplish string substitution. – ToolmakerSteve Apr 24 '19 at 23:00
  • True, but there might be a use for something like my answer. The answer above is much better than mine for this purpose. This was just what I came up with 6 years ago. – Robert Russell Apr 25 '19 at 19:23
2

I've found these approaches useful at times:

$name = 'Groot';
$string = 'I am {$name}';
echo eval('return "' . $string . '";');
$data = array('name' => 'Groot');
$string = 'I am {$data[name]}';
echo eval('return "' . $string . '";');
$name = 'Groot';
$data = (object)get_defined_vars();
$string = 'I am {$data->name}';
echo eval('return "' . $string . '";');
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David H.
  • 355
  • 2
  • 9
0

Here is my solution:

$club = "Barcelona";

$string = 'I am a {$club} fan';

preg_match_all("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", $string, $matches);

foreach ($matches[0] as $key => $var_name) {
    if (!isset($GLOBALS[$matches[1][$key]]))
        $GLOBALS[$matches[1][$key]] = 'default value';
    $string = str_replace($var_name, $GLOBALS[$matches[1][$key]], $string);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jader A. Wagner
  • 171
  • 1
  • 2
0

You can use a simple parser that replaces {$key} with a value from a map if it exists.

Use it like:

$text = templateWith('hello $item}', array('item' => 'world'...));`

My first version is:

/**
 * Template with a string and simple map.
 * @param string $template
 * @param array $substitutions map of substitutions.
 * @return string with substitutions applied.
 */
function templateWith(string $template, array $substitutions) {
    $state = 0; // forwarding
    $charIn = preg_split('//u', $template, -1, PREG_SPLIT_NO_EMPTY);
    $charOut = array();
    $count = count($charIn);
    $key = array();
    $i = 0;
    while ($i < $count) {
        $char = $charIn[$i];
        switch ($char) {
            case '{':
                    if ($state === 0) {
                        $state = 1;
                    }
                break;
            case '}':
                if ($state === 2) {
                    $ks = join('', $key);
                   if (array_key_exists($ks, $substitutions)) {
                        $charOut[] = $substitutions[$ks];
                   }
                   $key = array();
                   $state = 0;
                }
                break;
            case '$': if ($state === 1) {
                        $state = 2;
                      }
                  break;
             case '\\':    if ($state === 0) {
                           $i++;
                           $charOut[] = $charIn[$i];
                       }
                 continue;
             default:
                 switch ($state) {
                    default:
                    case 0: $charOut[] = $char;
                        break;
                     case 2: $key[] = $char;
                        break;
                   }
         }
    $i++;
    }

    return join('', $charOut);
 }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
homberghp
  • 11
  • 2
0

Maybe the following snippet is (partly) usefull for someone.

/**
 * Access an object property using "dot" notation
 *
 * @param  object  $object
 * @param  string|null  $path
 * @param  mixed  $default
 * @return mixed
 */
function xobject_get(object $object, $path, $default = null) {
    return array_reduce(explode('.', $path), function ($o, $p) use ($default) { 
        return is_numeric($p) ? $o[$p] ?? $default : $o->$p ?? $default; 
    }, $object);
}

/**
 * Access an array's property using "dot" notation
 *
 * @param  array  $array
 * @param  string|null  $path
 * @param  mixed  $default
 * @return mixed
 */
function xarray_get(array $array, $path, $default = null) {
    return array_reduce(explode('.', $path), function ($a, $p) use ($default) { 
        return $a[$p] ?? $default; 
    }, $array);
}

/**
 * Replaces placeholders from a string with object or array values using "dot" notation
 *
 * Example:
 * "The book {title} was written by {author.name}" becomes "The book Harry Potter was written by J.K. Rowling"
 *
 * @param  array|object  $data
 * @param  string  $template
 * @return string
 */
function render_template($data, string $template) {
    preg_match_all("/\{([^\}]*)\}/", $template, $matches); 
    $replace = [];
    foreach ($matches[1] as $param) { 
        $replace['{'.$param.'}'] = is_object($data) ? xobject_get($data, $param) : xarray_get($data, $param); 
    }
    return strtr($template, $replace);
}
Ramon Bakker
  • 1,075
  • 11
  • 24
-1

You can use preg_replace_callback for getting a variable name like:

$data_base[0]['body'] = preg_replace_callback(
    '#{(.*?)}#',
    function($m) {
        $m[1] = trim($m[1], '$');
        return $this->$m[1];
    },
    ' ' . $data_base[0]['body'] . ' '
);

Attention: This code I wrote is for class($this);. You can declare a variable into the class. Then use this code for detecting the variables and replace them like:

<?php
    class a {

        function __construct($array) {
            foreach($array as $key => $val) {
                $this->$key = $val;
            }
        }

        function replace($str){
            return preg_replace_callback(
                '#{(.*?)}#', function($m) {$m[1] = trim($m[1], '$'); return $this->$m[1];},
                ' ' . $str . ' ');
        }

    }

    $obj = new a(array('club' => 3523));

    echo $obj->replace('I am a {$club} fan');

Output:

I am a 3523 fan
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mohammad mohsenipur
  • 3,218
  • 2
  • 17
  • 22
  • Just tried your code on string _I am a {club} fan_ (without `$` sign) - got the following: **PHP Fatal error: Using $this when not in object context in /tmp/p.php on line 3** – Aleks G Feb 25 '13 at 11:25
  • @AleksG i told in answer this code is for class you can't use it out of class – mohammad mohsenipur Feb 25 '13 at 11:26
  • I've hacked your code about a bit and the problem I've had is that the variables defined like $club are out of scope for the callback function so I end up getting `Undefined variable: $club` – Jacob Tomlinson Feb 25 '13 at 11:27
  • It still wouldn't work correctly. Try with string _I am a {$0} fan_. Make sure you use the correct regex for a php variable. – Aleks G Feb 25 '13 at 11:31
-1

Try the preg_replace PHP function.

<?php
    $club = "Barcelona";
    echo $string = preg_replace('#\{.*?\}#si', $club, 'I am a {$club} fan');
?>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dipesh Parmar
  • 27,090
  • 8
  • 61
  • 90
-1

For your case, honestly, I do not see a reason not to use eval :)
Here is some extra way to define your variables if they are too into your database:

$my_variable_name = 'club'; //coming from database
$my_value = 'Barcelona'; //coming from database
$my_msg= 'I am a {$club} fan'; //coming from database

$$my_variable_name = $my_value;  // creating variable $club dinamically
$my_msg = eval("return \"$my_msg\";"); // eating the forbidden fruit
echo $my_msg; // prints 'I am Barcelona fan'

This code is fully tested and working with php 7. But if you allow your users to define such strings into your database, better don't do it. You should run eval only with trusted data.

lukistar
  • 58
  • 1
  • 7
-2

Something like this should solve your problem:

$club = "Barcelona";
$var = 'I am a {$club} fan';

$res = preg_replace('/\{\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/e', "$$1", $var);
echo "$res\n";

It's a one-line preg_replace.

With PHP 5.5, /e modifier is deprecated. You can use a callback instead:

$club = "Barcelona";
$var = 'I am a {$club} fan';

$res = preg_replace_callback('/\{\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/',
                             create_function(
                                 '$matches',
                                 'extract($GLOBALS, EXTR_REFS | EXTR_SKIP); return $$matches[1];'),
                              $var);
echo "$res\n";

Note that this uses a hack of importing all global variables. This may not be exactly what you want. Possibly using closures would be a better idea.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aleks G
  • 56,435
  • 29
  • 168
  • 265
  • 2
    eval is a very bad suggestion – David Feb 25 '13 at 11:14
  • @David: I know, this is why I wrote that it's a bad thing to do. – Aleks G Feb 25 '13 at 11:20
  • If it is a bad thing to do is there any point in even posting it? Or perhaps it should have been a comment rather than an answer. – Jacob Tomlinson Feb 25 '13 at 11:29
  • @JacobTomlinson Just because something is a bad thing to do, doesn't mean that it won't accomplish what you need. As long as you understand _why_ it is a bad thing to do and understand _how_ to protect yourself against it, there is not reason not to use a feature in a language. Afterall, if it's really so bad, why is still not deprecated? – Aleks G Feb 25 '13 at 11:33
  • 1
    Yes I appreciate your point. But speaking of deprecated the `/e` modifier has been deprecated and so I receive the warning `The /e modifier is deprecated, use preg_replace_callback instead` when running your new code. – Jacob Tomlinson Feb 25 '13 at 11:39
  • `create_function` is also a similar bad suggestion like using `/e`. Just saying. I stumbled over it once badly because it's actually `eval`. Prevent to use that function at all cost. – hakre Jan 12 '14 at 11:03