Sign in to follow this  
ID Merlin

[web] PHP 'eval' alternatives

Recommended Posts

I was looking at some old code I wrote, and optimizing bits of it, and one section in particular was pretty bad from a performance standpoint. It was convenient, fetching formulas from a database table, but... I was calling "eval" many times in one block, and realized that there was a significant performance hit for each call. I combined many eval calls into one by combining the expressions, which has helped. But I'm sure I can do much better. So now, I have a conundrum. Do I Replace that block of "eval" code with: o Regular PHP script, o A parser-evaluator class, or o Something else? From a code maintenance slant, code would be the worst for maintainability. The evaluator class would be nice, as it could be used in another block of eval-ed code elsewhere. But I haven't been able to find a good code sample to start from, and a recursive-descent parser is not the best solution. Any suggestions?

Share this post


Link to post
Share on other sites
The eval that I eliminated was in an inner loop, in a section which is called occasionally during game play. There were about 50 calls to eval.

I decided to write a parser-evaluator, as it seemed like it would be "fun" to do. I've posted the code at my site, if anyone is interested, or wants to offer feedback.

Share this post


Link to post
Share on other sites
Hey Merlin your code looks pretty good, tho there are still areas that could use some optimization or just minor edits to make maintaining the code a little easier.

I didn't really have time to go ever it thoroughly but wanted to share a revision to the eval_function you created.


function eval_function($fn, &$args, &$err) {
$f = array('abs' => 'abs',
'ceil' => 'ceil',
'floor' => 'floor',
'max' => 'max',
'min' => 'min',
'rand' => 'rand',
'round' => 'round');

if(in_array($fn, $f))
{
if ($args[1])
$value = $f[$fn]($args[0],$args[1]);
else
$value = $f[$fn]($args[0]);
}
else
{
$value = '';
$err[] = "Error: Unknown function: $fn";
}

return $value;
}



On the maintainability side you will notice that its allot easier to add or remove new functions as needed, and there is also a slight speed increase since you don't have to traverse the switch statement.

Regards,

Feral

Share this post


Link to post
Share on other sites
That's a good idea. I might go even further and specify the number of arguments in the array, using the key as the function name and an integer for the arg count. I will need to eventually accommodate functions with zero to four arguments, for example. (In your sample, you missed that for max and min I pass the array of args, as they will handle variable length arrays correctly.)

Share this post


Link to post
Share on other sites
Quote:
Original post by Feralrath
Hey Merlin your code looks pretty good, tho there are still areas that could use some optimization or just minor edits to make maintaining the code a little easier.

I didn't really have time to go ever it thoroughly but wanted to share a revision to the eval_function you created.

*** Source Snippet Removed ***

On the maintainability side you will notice that its allot easier to add or remove new functions as needed, and there is also a slight speed increase since you don't have to traverse the switch statement.

Regards,

Feral


Your code probably has the same underlying (logarithmic) complexity as an ordinary switch, look at the line where you check to see if the value is in the array: in_array() is not a magic function of constant complexity. I don't know which code the PHP compiler emits for a switch-statement, but probably it is a fixed/"hardcoded" traversal of some tree-like structure.

Sidenote: Your test "if ($args[1])" seems bogus. What if someone has the idea to pass a bool-false as the second element of $args? The proper way to test the size of an array is to use the count() function.

Further, according to the spec, you don't need to have an array to call a function. After you have checked whether the name of the function is valid, you can simply write "$f (<arguments>)" instead of "$f [$f] (<arguments>)".

After we have sacrificed some redundancy, you don't need an associative array, but instead only a simple array:

function eval_function($fn, &$args, &$err) {
$f = array('abs', 'ceil', ..........);

if(is_array ($args) && in_array($fn, $f) && function_exists ($f)) {
if (count ($arg) >= 2)
$value = $f ($args[0], $args[1]);
else
$value = $f ($args[0]);
} else {
$value = null;
$err[] = "Error: Unknown function: $fn";
}
}



My code is also a tad more paranoiac by testing whether $args is really an array (as a step further, you could check whether each argument in $args is of correct type, generally, see the "is_*" functions of PHP), further, it uses the count()-function instead on relying that non-existant arguments really yield false.

Share this post


Link to post
Share on other sites
I revised the code somewhat following your input. Thanks much for that.

I spent a lot of time trying to find some code that would replace eval for the rather specific function I need. I'm creating this for the community at large, to give something back for all the code samples I've used in the past.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this