2

How can I convert callable (anonymous function) into a string for eval?

I'm trying to write unit tests in phpunit that use runkit to override methods. And in particular, runkit_method_redefine() requires a string parameter that will be eval()-called later.

I want to have syntax highlighting of my test code, so I don't want to write code inside of a string, so I want to do something like

deEval(function(){ 
   return 1;
});

that would output

"return 1;"

How can this be done easily (for example without doing fopen of the source file, finding a source line, parsing etc.)?

Joran Den Houting
  • 3,149
  • 3
  • 21
  • 51
Artjom Kurapov
  • 6,115
  • 4
  • 32
  • 42

2 Answers2

4

NOTE: I do not like this solution and I would not recommend it to anyone for anything, but it does solve the problem set out in the question.


class CallableStringifier
{
    private static $callables = array();

    public static function stringify($callable)
    {
        $id = count(self::$callables);
        self::$callables[$id] = $callable;
        return 'return ' . __CLASS__ . "::call($id, func_get_args());";
    }

    public static function call($id, $args)
    {
        return call_user_func_array(self::$callables[$id], $args);
    }
}

This will work for your specific use case (which is essentially create_function()). It will not work if you just eval() it, because it relies on being inside a function context.

Example:

$func = create_function('$arg1', CallableStringifier::stringify(function($arg1) {
    return $arg1;
}));

echo $func(1); // outputs 1

See it working

DaveRandom
  • 87,921
  • 11
  • 154
  • 174
  • Thanks. Although people should keep in mind that this doesn't really generate a true method body text, because it depends on current php process and whatever is stored in CallableStringifier::$callables. So if someone wants to write generated code in DB to be executed later, its not a good idea (not to mention its not a good idea to do it at all). But for current automated testing purposes its more efficient than reading file source – Artjom Kurapov Oct 31 '13 at 11:47
  • Addendum: following on from the previous comment, if your *are* trying to "write generated code in DB to be executed later", you need to take a long, hard look at your application design ;-) – DaveRandom Oct 31 '13 at 11:52
1

Wrote a not-so efficient function.. (doesn't take into account arguments)

/**
 * Converts method code to string by reading source code file
 * Function brackets {} syntax needs to start and end separately from function body
 *
 * @param Callable $callable method to deevaluate
 *
 * @return string
 */
public function callableToString($callable) {
    $refFunc = new ReflectionFunction($callable);
    $startLine = $refFunc->getStartLine();
    $endLine   = $refFunc->getEndLine();

    $f      = fopen($refFunc->getFileName(), 'r');
    $lineNo = 0;

    $methodBody = '';
    while($line = fgets($f)) {
        $lineNo++;
        if($lineNo > $startLine) {
            $methodBody .= $line;
        }
        if($lineNo == $endLine - 1) {
            break;
        }
    }
    fclose($f);

    return $methodBody;
}
Artjom Kurapov
  • 6,115
  • 4
  • 32
  • 42