Saturday, April 5, 2008

PHP - apply, call, and Callback class

The "news" is that while some developer is putting a lot of effort to emulate PHP functionalities with JavaScript, I would like to have JavaScript functionalities in PHP.

Nested functions, closures, and injected scope, are only some of JS cool stuff that's currently missing PHP (hoping that during this summer of code someone will implement at least one of them).

Today what I'm going to emulate is a partial implementation of apply and call Function.prototype methods, and this is the basic example:

function apply($name, array $arguments = array()){
return call_user_func_array($name, $arguments);
}

function call(){
$arguments = func_get_args();
return call_user_func_array(array_shift($arguments), $arguments);
}

As is for JavaScript, the main difference between these functions is that apply accept an array of arguments while call accepts an arbitrary number of arguments.

echo call('md5', 'hello world');
// 5eb63bbbe01eeed093cb22bb8f5acdc3

echo apply('pow', array(2, 3));
// 8

Simple, but not so useful yet.
Currently, these implementations are just shortcuts to call_user_func or call_user_func_array PHP native functions ... and these are not JavaScript style friendly ... so what could we do?

To have a JS style code we would like to be able to write something like this:

$md5->call('hello world');
$pow->apply(array(2, 3));

... and I suppose noone could say that this way couldn't be cool, isn't it?
To obtain above behavior all we need is a class, called for obvious reasons Callback, that will contain those 2 public static methods:

class Callback{

// (C) webreflection.blogspot.com - Mit Style License

public $name; // name of the function
protected $_callback; // ReflectionFunction instance

public function __construct($arguments, $callback = ''){
$this->_callback = new ReflectionFunction(
0 < strlen($arguments) &&
strlen($callback) < 1 &&
is_callable($arguments) &&
function_exists($arguments) ?
$this->name = $arguments :
$this->name = ''.create_function($arguments, $callback)
);
}

public function __toString(){
return $this->_callback->getName();
}

public function apply(array $arguments){
return $this->_callback->invokeArgs($arguments);
}

public function call(){
// /* simple, unfornutatly with bad performances */ return $this->apply(func_get_args());
return $this->_callback->invokeArgs(func_get_args());
}
}

What we can do now, is to create every kind of function alias simply sending the name of the function or arguments and body for a runtime function creation.

$md5 = new Callback('md5');
$pow = new Callback('pow');
$sum = new Callback('$x, $y', 'return $x + $y;');

echo $md5->call('hello world').PHP_EOL; // 5eb63bbbe01eeed093cb22bb8f5acdc3
echo $pow->apply(array(2, 3)).PHP_EOL; // 8
echo $sum->call(2, 3).PHP_EOL; // 5

The public name property will contain the string rappresenting the used function name (lambda too), while the magic __toString method will return the name using dedicated ReflectionFunction isntance method (note: lambda functions have everytime the same one: __lambda_func).
With a simple class like this one we are able to send or recieve callbacks between functions, methods, or whatever else ... have you never sent a function in this way?

function hashMe($value, Callback $hash){
return $hash->call($value);
}

$md5 = new Callback('md5');
$sha1 = new Callback('sha1');

echo hashMe('hello world', $md5).
PHP_EOL.
hashMe('hello world', $sha1);

// 5eb63bbbe01eeed093cb22bb8f5acdc3
// 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed

The principal goal is to solve type checks and improve code portability ... but with another little piece of code, this stuff could be even funny!

// Callback dedicated factory pattern function
function callback(){
static $Callback;
if(!isset($Callback))
$Callback = new ReflectionClass('Callback');
return $Callback->newInstanceArgs(func_get_args());
}

With above function we are now able to do something like:

echo callback('str_repeat')->call('hello world ', 2);
// hello world hello world

Or, to be extremely scriptish, something like:

// create a string with "callback" content
$F = 'callback';

// use them as a function
echo $F('str_repeat')->call('hello world ', 2);
echo $F('pow')->call(2, 3);

// but if we need better performances ...
$md5 = $F('md5');
while($i--)
echo $md5->call($container[$i]);


Enjoy :)

No comments:

Post a Comment