17

Just finished making this function. Basically it is suppose to look through a string and try to find any placeholder variables, which would be place between two curly brackets {}. It grabs the value between the curly brackets and uses it to look through an array where it should match the key. Then it replaces the curly bracket variable in the string with the value in the array of the matching key.

It has a few problems though. First is when I var_dump($matches) it puts puts the results in an array, inside an array. So I have to use two foreach() just the reach the correct data.

I also feel like its heavy and I've been looking over it trying to make it better but I'm somewhat stumped. Any optimizations I missed?

function dynStr($str,$vars) {
    preg_match_all("/\{[A-Z0-9_]+\}+/", $str, $matches);
    foreach($matches as $match_group) {
        foreach($match_group as $match) {
            $match = str_replace("}", "", $match);
            $match = str_replace("{", "", $match);
            $match = strtolower($match);
            $allowed = array_keys($vars);
            $match_up = strtoupper($match);
            $str = (in_array($match, $allowed)) ? str_replace("{".$match_up."}", $vars[$match], $str) : str_replace("{".$match_up."}", '', $str);
        }
    }
    return $str;
}

$variables = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
echo dynStr($string,$variables);
//Would output: 'Dear John Smith, we wanted to tell you that you won the competition.'
Tyler Hughes
  • 582
  • 1
  • 9
  • 19
  • Please provide us with a sample of data ($str & $vars) – HamZa Apr 02 '13 at 20:07
  • Your approach is quite inefficient. Consider an alternative approach: a) use `preg_replace_callback` to return matched token's value from `$vars` without a million `str_replace` calls; b) transform each entry in `$vars` to include the leading/trailing bracket, then feed `$vars` into `strtr`. – DCoder Apr 02 '13 at 20:10
  • are you doing this as a learning excercise or for production? if for production you might want to look at a templating library like `smarty` – dm03514 Apr 02 '13 at 20:14
  • @DCoder: Could you provide some examples? I'm confused by what you said. – Tyler Hughes Apr 02 '13 at 20:15
  • @dm03514 It is for production but its like a first draft. Not the final product. It's not for a template either. It's for a newsletter. – Tyler Hughes Apr 02 '13 at 20:16
  • @TylerDusty: the first suggestion I had is the same as fireeyedboy posted below. – DCoder Apr 02 '13 at 20:23
  • @TylerDusty smarty is a templating library, it was designed to do exactly what you are doing, but smarty is a large mature project. It is ready to use right now, and handles all the crazy edge cases that you will be running into as your project developes, it is fast and reliable as well – dm03514 Apr 02 '13 at 20:51

6 Answers6

45

I think for such a simple task you don't need to use RegEx:

$variables = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");
$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';

foreach($variables as $key => $value){
    $string = str_replace('{'.strtoupper($key).'}', $value, $string);
}

echo $string; // Dear John Smith, we wanted to tell you that you won the competition.
HamZa
  • 14,671
  • 11
  • 54
  • 75
  • @fireeyedboy I liked your `preg_replace_callback()` solution, I was trying to figure out how to use the anonymous function and how to include the `$allowed` variable in the scope, and then I saw your answer. +1 :D – HamZa Apr 02 '13 at 20:38
  • 2
    @HamZaDzCyberDeV I was too hung up on trying to be 'nifty', such that a solution such as yours just didn't come to mind. Sometimes it helps to take a step back first. Only minor caveat, that just occurred to me, with your solution though, is that it doesn't facilitate removing invalid placeholders from the input string (but that wasn't a requirement to begin with). – Decent Dabbler Apr 02 '13 at 20:55
  • 2
    Hint: `str_replace` can take an array of strings to find/replace. One `str_replace` call will probably be faster than a hundred. – DCoder Apr 04 '13 at 06:30
  • 2
    @DCoder Yes you're right, the problem here is that the keys in `$variables` aren't in the right format (enclosed between {} and uppercase). – HamZa Apr 04 '13 at 08:35
20

I hope I'm not too late to join the party — here is how I would do it:

function template_substitution($template, $data)
{
    $placeholders = array_map(function ($placeholder) {
        return strtoupper("{{$placeholder}}");
    }, array_keys($data));

    return strtr($template, array_combine($placeholders, $data));
}

$variables = array(
    'first_name' => 'John',
    'last_name' => 'Smith',
    'status' => 'won',
);

$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you have {STATUS} the competition.';

echo template_substitution($string, $variables);

And, if by any chance you could make your $variables keys to match your placeholders exactly, the solution becomes ridiculously simple:

$variables = array(
    '{FIRST_NAME}' => 'John',
    '{LAST_NAME}' => 'Smith',
    '{STATUS}' => 'won',
);

$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you have {STATUS} the competition.';

echo strtr($string, $variables);

(See strtr() in PHP manual.)

Taking in account the nature of the PHP language, I believe that this approach should yield the best performance from all listed in this thread.


EDIT: After revisiting this answer 7 years later, I noticed a potentially dangerous oversight on my side, which was also pointed out by another user. Be sure to give them a pat on the back in the form of an upvote!

If you are interested in what this answer looked like before this edit, check out the revision history

antichris
  • 2,827
  • 1
  • 22
  • 18
  • 2
    I agree - performance aside, this is also the most readable and maintainable IMHO. My answer on another question (http://stackoverflow.com/a/36781566/224707) also shows other advantages to this approach such as the ability to programmatically enhance the replacement values before processing (such as encoding or theming them)... – Nick Apr 21 '16 at 22:35
9

I think you can greatly simplify your code, with this (unless I'm misinterpreting some of the requirements):

$allowed = array("first_name"=>"John","last_name"=>"Smith","status"=>"won");

$resultString = preg_replace_callback(

    // the pattern, no need to escape curly brackets
    // uses a group (the parentheses) that will be captured in $matches[ 1 ]
    '/{([A-Z0-9_]+)}/',

    // the callback, uses $allowed array of possible variables
    function( $matches ) use ( $allowed )
    {
        $key = strtolower( $matches[ 1 ] );
        // return the complete match (captures in $matches[ 0 ]) if no allowed value is found
        return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : $matches[ 0 ];
    },

    // the input string
    $yourString
);

PS.: if you want to remove placeholders that are not allowed from the input string, replace

return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : $matches[ 0 ];

with

return array_key_exists( $key, $allowed ) ? $allowed[ $key ] : '';
Decent Dabbler
  • 22,532
  • 8
  • 74
  • 106
  • That looks like it might be better but I'm curious to what `function( $matches ) use ( $allowed )` is? I've never seen 'use' and my code editor (dreamweaver) says it's incorrect. – Tyler Hughes Apr 02 '13 at 20:23
  • 3
    @TylerDusty: it's an [anonymous function](http://php.net/manual/en/functions.anonymous.php), which requires PHP 5.3 . – DCoder Apr 02 '13 at 20:24
  • 2
    @TylerDusty The callback is a [closure](http://www.php.net/manual/en/functions.anonymous.php). The `use` construct is used to import a variable from the defining scope, such that it can be used inside the closure. And indeed, as DCoder already mentioned, it's a PHP 5.3+ feature. – Decent Dabbler Apr 02 '13 at 20:27
4

Just a heads up for future people who land on this page: All the answers (including the accepted answer) using foreach loops and/or the str_replace method are susceptible to replacing good ol' Johnny {STATUS}'s name with Johnny won.

Decent Dabbler's preg_replace_callback approach and U-D13's second option (but not the first) are the only ones currently posted I see that aren't vulnerable to this, but since I don't have enough reputation to add a comment I'll just write up a whole different answer I guess.

If your replacement values contain user-input, a safer solution is to use the strtr function instead of str_replace to avoid re-replacing any placeholders that may show up in your values.

$string = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';
$variables = array(
    "first_name"=>"John",
    // Note the value here
    "last_name"=>"{STATUS}",
    "status"=>"won"
);

// bonus one-liner for transforming the placeholders
// but it's ugly enough I broke it up into multiple lines anyway :)
$replacement = array_combine(
    array_map(function($k) { return '{'.strtoupper($k).'}'; }, array_keys($variables)),
    array_values($variables)
);

echo strtr($string, $replacement);

Outputs: Dear John {STATUS}, we wanted to tell you that you won the competition. Whereas str_replace outputs: Dear John won, we wanted to tell you that you won the competition.

3

This is the function that I use:

function searchAndReplace($search, $replace){
    preg_match_all("/\{(.+?)\}/", $search, $matches);

    if (isset($matches[1]) && count($matches[1]) > 0){
        foreach ($matches[1] as $key => $value) {
            if (array_key_exists($value, $replace)){
                $search = preg_replace("/\{$value\}/", $replace[$value], $search);
            }
        }
    }
    return $search;
}


$array = array(
'FIRST_NAME' => 'John',
'LAST_NAME' => 'Smith',
'STATUS' => 'won'
);

$paragraph = 'Dear {FIRST_NAME} {LAST_NAME}, we wanted to tell you that you {STATUS} the competition.';

// outputs: Dear John Smith, we wanted to tell you that you won the competition.

Just pass it some text to search for, and an array with the replacements in.

emma.fn2
  • 229
  • 1
  • 10
2
/**
   replace placeholders with object
**/
$user = new stdClass();
$user->first_name = 'Nick';
$user->last_name = 'Trom';

$message = 'This is a {{first_name}} of a user. The user\'s {{first_name}} is replaced as well as the user\'s {{last_name}}.';

preg_match_all('/{{([0-9A-Za-z_]+)}}/', $message, $matches);

foreach($matches[1] as $match)
{
    if(isset($user->$match))
        $rep = $user->$match;
    else
        $rep = '';

    $message = str_replace('{{'.$match.'}}', $rep, $message);
}

echo $message;
NIck
  • 29
  • 1