简体   繁体   中英

PHP Variable Concatenation Inside Line of File

I have some PHP code on my site that reads a line from a file and echo s that line. In one of these lines of cade I have placed a variable concatenation in the form of {$varname} , but in the end result it actually echos {$varname} instead of switching it with the variable.

The line reads:

<p>Today's date (at the server) is {$date}.</p>

The code used to echo the line reads:

$line = fgets($posts);
echo $line;

And the output of that code is: 'Today's date (at the server) is {$date}.'

And the variable $date is declared earlier in the code. I am wondering if there is some special way of doing this for lines in a file, or if I'm not doing this right?

EDIT: The output is also available at http://codegamecentral.grn.cc/main/?pageNumber=2 .

ANOTHER EDIT: This code is run through a while loop until it reaches the end of the file, read line by line. This should preferably be a fast solution that will not cause problems when used with a string that doesn't contain {$date} .

怎么样一个string_replace()

echo str_replace('{$date}', $date, $line);

This is how you can do what you want:

eval("\$line = \"$line\";");
echo $line; 

Warning: Although this will do the job, I would strongly discourage you from doing it unless you have 100% certainty that only you or trusted people can generate the files that will be evaluated this way because eval() can run any PHP code inside the variable.

You are reading a line as text, and PHP substitution won't work unless it's done against code .

You need some more complicated processing (or to tell PHP to consider that text as if it was code, by using eval ; which is strongly discouraged for security reasons, and may not work everywhere since the eval function is sometimes disabled by webmasters, for those same security reasons).

The most powerful alternative would be to use preg_replace_callback to recognize text sequences such as {$varname} and replace them with $varname . Of course $varname would need to be defined, or checked for existence:

function expandVariables($text, $allowedVariables) {
    return preg_replace_callback('#{\$([a-z][a-z_0-9]*)}#i',
        function($replace) use ($allowedVariables) {
            if (array_key_exists($replace[1], $allowedVariables)) {
                return $allowedVariables[$replace[1]];
            }
            return "NO '{$replace[1]} VARIABLE HERE.";
        },
        $text
    );
}

$date = date('Y-m-d H:i:s');

$line = '<p>Now (at the server) is {$date}.</p>';

$vars = get_defined_vars(); // LOTS of memory :-(

// better:
// $vars = array ( 'date' => $date, ... ); // Only allowed variables.

$eval = expandVariables($line, $vars);

print "The line is {$line}\nand becomes:\n{$eval}";

Outputs:

The line is <p>Now (at the server) is {$date}.</p>
and becomes:
<p>Now (at the server) is 2014-10-12 18:36:16.</p>

Caveats

This implementation is more secure than straight eval() , which would execute any PHP code at all it were to find in the read line. But it can still be used to output the content of any defined variable provided the attacker knows its name and is allowed to request it; an admittedly unrealistic example would be <p>Hello, {$adminPassword}!</p> .

To be more secure still, albeit at the expense of flexibility, I'd endorse Viktor Svensson's solution which only allows very specific variables to be set, and is both simpler and faster:

// Remember to use 'single quotes' for '{$variables}', because you DO NOT
// want expanded them in here, but in the replaced text!

$text = str_replace(array('{$date}', '{$time}' /*, ...more... */),
                    array(date('Y-m-d'), date('H:i:s') /*, ...more... */),
                    $text);

Also, you might be interested in checking out some templating solutions such as Smarty .

Processing a whole file

To process a whole file, if memory is not an object, you can in both cases (preg and str_) load the whole file as an array of lines:

$file = file($fileName);

and use $file as subject of the replace. Then you can iterate on the results:

// replaceVariables receives a string and returns a string
// or receives an array of strings and returns the same.
$text = replaceVariables($file, $variables);

foreach ($text as $line) {
     // Do something with $line, where variables have already been replaced.
}

Speed

Something one doesn't often appreciate is that regular expressions are fast . I don't know what text-matching algorithm str_replace employs (I think Boyer-Moore's), but preg has the advantage of being Perlishly array-aware. It has a heavier setup (linear on the dictionary size), but then it "scales" better in the replacement time, while str_replace is constant setup, scales linearly in the replacement time. Which means preg_replace will save huge amounts of times in massive replacements; it fares worse in simpler contexts.

Very roughly, run time is (S + R L V)*V ( V = number of variables, L = number of lines ) where preg_replace has a detectable S and a negligible R, while str has the reverse. With large values of V you really want the smallest R you can get, even at the expense of an increase in setup time S.

Dependency on Variable N.. ? variables, 18 lines, keylen 5, vallen 20
v       preg advantage
5       -82%
15      -55%
25      -2%
35      14%
45      65%
55      41%
65      51%
75      197%
85      134%
95      338%

Dependency on File length. 32 variables, ? lines, keylen 5, vallen 20
l       preg advantage
5       -31%
15      -33%
25      14%
35      80%
45      116%

Of course, maintainability is also an issue - str_replace does it in one line of code, and the function itself is maintained by the PHP team. The function built around preg_replace requires as much as 15 lines. Granted that, once tested, you shouldn't need to modify it any longer, just pass it a dictionary.

Looping

Finally, you may want to use variables referring other variables . In this case neither preg nor str_ will work reliably, and you will have to implement a loop of your own:

<?php
$file   = "This is a {\$test}. And {\$another}. And {\$yet_another}.\n";

$vars   = array(
    "test"  => "test",
    "another" => "another {\$test}",
    "yet_another" => "yet {\$another} {\$test}",
);

$text = preg_replace_callback('#{\$([a-z][a-z_0-9]*)}#i',
    function($replace) use ($vars) {
        if (array_key_exists($replace[1], $vars)) {
            return $vars[$replace[1]];
        }
        return "NO '{$replace[1]} VARIABLE HERE.";
    },
    $file
);

$keys   = array_map(function($k){ return "{\${$k}}"; }, array_keys($vars));
$vals   = array_values($vars);

$text2  = str_replace($keys, $vals, $file);

$text3  = $file;
do {
    $prev   = $text3;
    $text3  = str_replace($keys, $vals, $text3);
} while ($text3 != $prev);

print "PREG: {$text}\nSTR_: {$text2}\nLOOP: {$text3}\n";

The output is:

PREG: This is a test. And another {$test}. And yet {$another} {$test}.
STR_: This is a test. And another {$test}. And yet {$another} {$test}.
LOOP: This is a test. And another test. And yet another test test.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM