open source guy
open source guy

Reputation: 2787

How can I replace a variable in a string with the value in PHP?

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?

Upvotes: 51

Views: 75194

Answers (13)

zamnuts
zamnuts

Reputation: 9592

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;

Upvotes: 5

Ramon Bakker
Ramon Bakker

Reputation: 1085

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);
}

Upvotes: 0

lukistar
lukistar

Reputation: 68

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.

Upvotes: -1

David H.
David H.

Reputation: 373

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 . '";');

Upvotes: 1

homberghp
homberghp

Reputation: 11

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);
 }

Upvotes: 0

tiffin joe
tiffin joe

Reputation: 89

/**
 * 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 . ' ');
}

Upvotes: 8

Robert Russell
Robert Russell

Reputation: 305

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.

Upvotes: 2

mohammad mohsenipur
mohammad mohsenipur

Reputation: 3149

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

Upvotes: -1

Aleks G
Aleks G

Reputation: 57316

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.

Upvotes: -2

Moises Gumbs
Moises Gumbs

Reputation: 81

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

Upvotes: 8

Jader A. Wagner
Jader A. Wagner

Reputation: 171

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);
}

Upvotes: 0

Dipesh Parmar
Dipesh Parmar

Reputation: 27364

Try the preg_replace PHP function.

<?php
    $club = "Barcelona";
    echo $string = preg_replace('#\{.*?\}#si', $club, 'I am a {$club} fan');
?>

Upvotes: -2

Husman
Husman

Reputation: 6909

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.

Upvotes: 103

Related Questions