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.
|
|