Search code examples
phpevalpreg-replace-callbackcustom-functionvariable-names

Replace placeholders in text by referencing a translation variable


I'm trying to remove eval from the following function. I tried with sprintf and ${} , but still cannot find a solution.

Here the function:

function parseDbString(string $value = 'Looking for a good {{ $pippo }}'){
    $pippo='Pizza';
    return preg_replace_callback('/{{(.*?)}}/', function($res) use ($pippo) {
      // $val=${trim($res[1])}; Returns "Undefined variable: $pippo"
      $val=@eval("return ".trim($res[1]).";"); // Returns "Looking for a good Pizza"
      return isset($val) ? $val : $res[0];
    },$value);
}

Solution

  • So, yes, eval() is often detested as one of the highest order "evils" in php. In most cases, when a task lends itself to be solved by eval() or variable-variables (which are basically poorly packaged arrays), this is a symptom of inappropriately stored/declared data and often the best course of action is a complete rethink.

    To solve your isolated question without fundamentally rewriting the custom function, I'll offer a lesser "evil" (but still an "evil" in my opinion because there are risks in its usage) -- GLOBALS & global...

    Code: (Demo)

    function parseDbString(string $value = 'Looking for a good {{ $pippo }}'){
        global $pippo;                        // declare $pippo as a global variable
        $pippo = 'Pizza';
        return preg_replace_callback('/{{ \$(.*?) }}/', function($m) use ($pippo) {
            echo "Global: " , $GLOBALS['pippo'];
            echo "\n{$m[1]}\n";
            return $GLOBALS[$m[1]] ?? $m[0];  // null coalescing operator provides fallback
        },$value);
    }
    echo parseDbString();
    

    Output:

    Global: Pizza                    # <-- for demonstraton purposes
    pippo                            # <-- for demonstraton purposes
    Looking for a good Pizza         # <-- desired output
    

    ...so why is this workaround a "bad idea", well, imagine you have a string that contains {{ $db }} -- such a common variable name is likely to exists in your list of global variables. So if the {{ variable }} in your string matches ANY of the variables in the global scope, you're going to get faulty outcomes.


    Now, what should you do? Just declare your $pippo data in an array so that you have an associative relationship to leverage. (Demo)

    function parseDbString(string $value = 'Looking for a good {{ $pippo }}'){
        $lookup = ['pippo' => 'Pizza'];
        return preg_replace_callback('/{{ \$(.*?) }}/', function($m) use ($lookup) {
            return $lookup[$m[1]] ?? $m[0];  // null coalescing operator provides fallback
        }, $value);
    }
    echo parseDbString();
    

    Depending upon the amount of control you have over your input data, you can now afford to remove the $ before pippo in your input string -- which eliminates a few unnecessary characters here and there.

    And if you are still reading, you can clean this whole thing up with strtr() or str_replace(). (Demo)

    function parseDbString(string $value = 'Looking for a good {{ $pippo }}'){
        $lookup = ['{{ $pippo }}' => 'Pizza'];  // this can be extended all you like!
        return strtr($value, $lookup);
    }
    echo parseDbString();