PDA

View Full Version : Variables within Strings within Variables?



johnnyb
05-07-2008, 05:34 PM
This is kind of weird. Here's what I'm trying to so:

I have a variable that contains a string with a variable in it, and I want the variable in the string to be properly replaced. Here's some example code:



$greeting = array('gr'=>"Hi there $myname[1],");

$myname = array('John','Frank');

echo $greeting['gr'];
// should output "Hi there Frank,"



Now, I know if I reverse lines 1 and 3, it will work, but because of the way my application is written that can't happen, (I'm making a website multilingual, so I get the phrases first, (like "Hi here $myname[1]"), and then do most of the other logic later.

Is there a way to reprocess the string stored in $greeting['gr'] so that the variables are replaced?

Fumigator
05-07-2008, 06:00 PM
How about using a placeholder string that you won't find in normal text, such as "__##__", then using str_replace on the array element when you echo?



$greeting = array('gr'=>"Hi there __##__,");

$myname = array('John','Frank');

echo str_replace("__##__", $myname[1], $greeting['gr']);

johnnyb
05-07-2008, 06:43 PM
The problem is that there are more than 100 different variables that could appear in the greeting array... and to do a str_replace on them all would be a pretty big, and slow, function.

aedrin
05-07-2008, 08:02 PM
That's why you use a regular expression.

Fumigator
05-07-2008, 11:15 PM
How ironic, since str_replace is faster than regex's, and I can't visualize how regex would be any cleaner-- wouldn't you have to run 100 regex's?

JohnnyB you can send arrays to str_replace() so you only have to call the function once-- I doubt you'd see any kind of performance hit.

johnnyb
05-08-2008, 03:18 PM
So it looks like I have 2 options so far:
1) str_replace with a huge array of needles, (and maybe replaces as well)
2) a regex that would probably find a pattern something like ${(variablename)}, then capture the variable name and somehow grab the variable from the surrounding code then replace it in the string, (would probably require a pass by preg_match then a second by preg_replace, maybe the _all variants depending on how tricky I get).

Ideally, I'd like to avoid searching through strings alltogether though. Since if I define $myname before I define $greeting everything works properly, it seems like there should be a way to convince PHP that the contents of $greeting are a literal, instead of a string from before, therefore by assigning the faux-literal to a variable, I should have a completed string. I realize that this is probably a rare request, but I can see it being really useful at times. If you have any more insight that would be great.

johnnyb
05-08-2008, 03:47 PM
So, I have a solution:


error_reporting(E_ALL);
ini_set("display_errors","on");


function process($k) {
global $myname,$greeting;

$t = $greeting[$k];
$greeting[$k] = $t;
echo $t.'<br/>';
eval('$str = "'.addslashes($t).'";');

return $str;

}


$greeting = array('gr'=>'Hi there "{$myname[1]},"');

$myname = array('John','Frank');

echo process('gr');
// should output "Hi there Frank,"


However, it uses eval(). I'm going to see if there's a way to get around this, perhaps some sort of stripped-down eval? I haven't dived into the eval() manual yet, I will post back when I have.

aedrin
05-08-2008, 03:49 PM
How ironic, since str_replace is faster than regex's, and I can't visualize how regex would be any cleaner-- wouldn't you have to run 100 regex's?

No, you wouldn't.

You can use preg_replace_callback() to call upon a function for each replacement, which grabs the data from a specific source. So you define a pattern for your variables (such as {VARIABLENAME}), create the regex to collect each occurence.



#\{([^\}]+)\}#


Then create a function which either has an array of replacements, or if part of an object can access some other source (you should be able to specify a callback that references an object's method).

This is in my opinion the cleanest method.

johnnyb
05-08-2008, 04:33 PM
In the notes on the eval() man pages I found something similar, but not exactly like adrin's methos: use preg_replace with the e modifier. Then it eval()s only the part of the text that's being replaced. That in conjunction with a {$varname} construction should work. It works in my test function:


function process($k) {
global $myname,$greeting;

$message = $greeting[$k];

$message = preg_replace('/\{(\$[^}]*)}/e', '$1', $message);
return $message;
}

It is also faster than eval()ing the whole string and, in my opinion safer.

I put a $ in the capturing parentheses so the regexp will hopefully only capture variables and nothing else, (although, I guess it could capture object methods, maybe I need to do a little more work on the regex).

This should remove the need of a huge array of possible replace values, although, now as I'm writing, I realize I have one already. I will experiment a little more.

johnnyb
05-08-2008, 05:09 PM
Well, I can't seem to make the variable variables work properly in the callback of preg_replace_callback, so I'll stick with the function posted above.

aedrin
05-08-2008, 05:45 PM
Just remember that eval() is the cheap way out and isn't a real solution. But more like a stopgap.


Well, I can't seem to make the variable variables work properly in the callback of preg_replace_callback

If done properly, it will work. But it depends on how much you want to do it the proper way (i.e. what level of quality is required).

johnnyb
05-08-2008, 05:52 PM
Well, I realized that I would have the same variable scope issues with the function posted at 09:33, except that I wouldn't be able to solve them without calling both preg_match and preg_replace - not the ideal solution, so I kept working on the solution using preg_replace_callback and figured out a solution, (the problem I was having was an extra $ sign where it was not needed).

So, here's my whole test script. The function that uses preg_replace_callback is cprocess(); It should handle coming across a plain variable and an array element in the string:


<?php
error_reporting(E_ALL);
ini_set("display_errors","on");


function oprocess($k) {
global $myname,$greeting;

$t = $greeting[$k];
$greeting[$k] = $t;
// echo $t.'<br/>';
eval('$str = "'.addslashes($t).'";');

return $str;

}


function process($k) {
global $greeting;

$message = $greeting[$k];

$message = preg_replace('/\{((\$[^\]}])[^}]*)}/e', '$1', $message);
return $message;

}

function cprocess($k) {

if(!function_exists('callback')) {
function callback($arr) {
$varname = $arr[3];
// var_dump($arr);
// echo '<br/>';
global ${$varname};

// var_dump(${$varname});

if(isset($$varname)) {
if(isset($arr[4])) {
// echo '<br>returning: '.${$varname}[$arr[4]];
return ${$varname}[$arr[4]];
} else {
return ${$varname};
}
} else {
return '';
}

// echo $varname.'<br />';
// var_dump($$varname);
// print_r ($myname);
// echo $varname;
// echo '<br /><br />';

return 'test';

}
}
global $greeting;
$message = $greeting[$k];

$message = preg_replace_callback('/(\{\$(([^\[}]+)\[?([^\]+])?[^}]*)})/', 'callback', $message);
return $message;

}


$greeting = array('gr'=>'Hi there {$myname[1]}, {$myname[0]}');

$myname = array('John','Frank');

// a timer to test performance
/*
$stime = microtime();
$i=0;
while($i < 1000000) {
$str = cprocess('gr');
$i++;
}
$etime = microtime();
echo 'time: '.($etime - $stime).'<br />';
*/

echo cprocess('gr');


?>

It does run more slowly than even eval()ing the whole string, but there is no eval involved, so I'm much happier.