...

View Full Version : Variables within Strings within Variables?



johnnyb
05-07-2008, 06: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, 07: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, 07: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, 09:02 PM
That's why you use a regular expression.

Fumigator
05-08-2008, 12:15 AM
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, 04: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, 04: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, 04: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, 05: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, 06: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, 06: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, 06: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.



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum