Proper use of eval() in Yii

Hi there,

Since I’ve never been using eval() function (still having Rasmus Lerdorf, creator of PHP - If eval() is the answer, you’re almost certainly asking the wrong question - words in my mind, see last (bottom of page) post on that page :]), I have some problems understanding how it works.

I’m trying to change CDataColumn.value, which uses eval. I’m trying to format date that I’m receiving in this column, therefore I call:


$this->widget('zii.widgets.grid.CGridView', array

(

     ...

     'columns'=>array

     (

			'ID',

			array

			(

			  	'name'=>'DATR',

			  	'value'=>'date("Y-m-d H:i:s", $data->DATR)',

			),


			'STAT',

			'BCODE',

			...

     ),

));

This works perfectly, because I’m receiving PHP timestamp in this column, which can be formatted with only one line. Problems starts when I change DB query and receive formal date or if anything forces me to use more than one PHP code line in value (eval) call. An example of object approach to use PHP’s DateTime::format uses two lines of code:


<?php

$date = new DateTime('2000-01-01');

echo $date->format('Y-m-d H:i:s');

?>

I tried to use it in value on a various ways:


'value'=>'$date = new DateTime("2000-01-01");echo $date->format("Y-m-d H:i:s");',

or


'value'=>'$date = new DateTime("2000-01-01");return $date->format("Y-m-d H:i:s");',

or


'value'=>'new DateTime("2000-01-01")->format("Y-m-d H:i:s")',

Always with no luck - various errors from either PHP parser, Yii exception catcher or parse error inside GridView’s cell.

Therefore, I’m asking what is the correct format for using more than one line of code in eval function?

Without the echo should work like


'value'=>'$date = new DateTime("2000-01-01");$date->format("Y-m-d H:i:s");',

Tried this. Should, but isn’t :[ I’m getting exception saying “htmlspecialchars() expects parameter 1 to be string, object given”.

In this particular problem (date formatting) I helped myself by using procedural approach to DateTime class:


'value'=>'date_format(date_create("2000-01-01"), "Y-m-d H:i:s")'

But it would be nice to know, in general, how to use more than one line in eval.

I tried it like this:


 'value'=>'$a="tra";$a',

and I’m getting the output “tra”

I’m getting the same output as you, but your previously posted example code still doesn’t work, claiming that object was returned, where string was expected. This is strange as DateTime::format is returning string according to PHP manual.

There must be something deeper. Since I’ve already found the solution and since your second example proves that using multi-line PHP code is possible (as supposed I must be doing something wrong, I don’t rememeber what? :]), we may end this discussion. Thank you.

Cheers Trej,

Why dont you create a new property in your model getCustomDate for example and just put name=>‘customDate’? Return the value to the Grid and control the way it is received/returned

As for the eval expression: check the source of CComponent::evaluateExpression() and you’ll see that a 'return ’ is added in front of the eval’d code.

Cheers! :]

As I said, I’ve already solved this particular problem (and even showed how! :}). But, on the other hand - your idea seems interesting for at least consideration. Thanks!

Thanks, Mike. But if I’m not mistaken - this does not explains why mdomba’s code is working at his server and is failing on mine. Really odd.

It does:




return $a="tra";$a;

// same as:

return $a="tra";

// So this would still return "tra:"

return $a="tra";$b='blabla';$b;



No, it doesn’t. I was mentioning an older example, where code:


'value'=>'$date = new DateTime("2000-01-01");$date->format("Y-m-d H:i:s");',

works fine for mdomba and is throwing "htmlspecialchars() expects parameter 1 to be string, object given" exception for me.

I can’t see that he said, this worked for him. Note the “should…” in his words…

Actually it does not work for me too (note that I wrote: it should work)…

I just tried the "tra" example and assumed your example would work… but as Mike later analyzed this happens


return $date = new DateTime("2000-01-01"); $date->format("Y-m-d H:i:s");

that is equal to only


return $date = new DateTime("2000-01-01");

and that’s why the error “object given”

Sorry, guys! Now, I got the point!

Then, isn’t that an error in implementing CComponent::evaluateExpression method? Shouldn’t line:


return eval('return '.$_expression_.';');

be changed to something like that:


return eval(((strpos('return', $_expression_) === FALSE) ? 'return' : '').' '.$_expression_.';');

or in more readable version:


$return = (strpos('return', $_expression_) === FALSE) ? 'return' : ''

return eval($return.' '.$_expression_.';');

This way developer would be able to decide himself what and when to return and if he would omit return clause, it would be added just the way it is now?

This returns ‘tra la la’




  'value'=>'MyHelpers::myEval(\'$a="tra";$b=" la la";return $a.$b;\')',






<?php

class MyHelpers

{

  public static function myEval($code) {

    return eval($code);

  }

}



/Tommy

Nice approach! But you are using your own class instead of build in one. And if you look into CComponent::evaluateExpression code, you’ll see that it also supports calling user functions, not only evaluating code. Which your class doesn’t have but I believe you just don’t need this.

Yeah, I forgot to mention my original intention was to pass a callback to CComponent::evaluateExpression() instead of nested eval’s. I just haven’t figured out the syntax yet.

/Tommy

Ehm… Am I missing something? If you do that:

[list=1][]Your class and method existence will have no more sense! What is the reason for writing own class doing exactly the same as build in functionallity? Keep in mind that whatever you put into value (in this example) will finally go through CComponent:evaluateExpression(), right?[]You will be again facing problem with return being added in front of your code (current implementation of this method forcing it), therefore your example code now working, will stop working.[/list]

Have you considered that?

You didn’t support me, but I decided to open a ticket as I’m far concern this is a bug.

The PHP eval() evaluates "code" i.e. statements, leaving to me the choice what to return, while the CComponent::evaluateExpression() is tailored to evaluate a string expression only (or a callback with an optional second parameter consisting of an array of arguments). I should have investigated how to use the callback (if at all possible). I agree that my helper function is not needed with just nested eval calls.

Just tested this, and it seems to work




  'value'=>'eval(\'$a="tra";$b=" la la";return $a.$b;\')',



Edit:

For those not familiar with the semantics of


'value'=>'someexpression',

this is a string expression to be evaluated later i.e. on rendering grid rows. In my example CComponent::evaluateExpression() will call eval() with the above string as parameter.

Edit:

This is the statement used by renderDataCellContent to evaluate the string passed in the ‘value’ element of the column definition array




$value=$this->evaluateExpression($this->value,array('data'=>$data,'row'=>$row));



Obviously there’s no way to use the calback approach since everything from our string goes into the first param.

/Tommy