I have taken a liking to jQuery/Javascript's way of extending functionality via closures. Is it possible to do something similar in PHP 5.3?
class Foo
{
public $bar;
}
$foo = new Foo;
$foo->bar = function($baz) { echo strtoupper($baz); };
$foo->bar('lorem ipsum dolor sit amet');
// LOREM IPSUM DOLOR SIT AMET
[edit] mixed up 'it' and 'is' in my question. heh.
I downloaded 5.3a3 and it does work!
class Foo
{
protected $bar;
public $baz;
public function __construct($closure)
{
$this->bar = $closure;
}
public function __call($method, $args)
{
$closure = $this->$method;
call_user_func_array($closure, $args);
}
}
$foo = new Foo(function($name) { echo "Hello, $name!\n"; });
$foo->bar('Mon');
// Hello, Mon!
$foo->baz = function($s) { echo strtoupper($s); };
$foo->baz('the quick brown fox jumps over the lazy dog');
// THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG
PHP 5.3 will allow the creation of lambda functions and closures which would allow you to implement your code example. From the PHP RFC:
function & (parameters) use (lexical vars) { body }
The function is returned as reference and can be passed as a callback. For example, creating a function:
$lambda = function () { echo "Hello World!\n"; };
Passing a lambda function as a callback:
function replace_spaces ($text) {
$replacement = function ($matches) {
return str_replace ($matches[1], ' ', ' ').' ';
};
return preg_replace_callback ('/( +) /', $replacement, $text);
}
Closures also allow you to import variables from the local scope:
$map = function ($text) use ($search, $replacement) {
if (strpos ($text, $search) > 50) {
return str_replace ($search, $replacement, $text);
} else {
return $text;
}
};
Where $search and $replacement are both declared outside the closure, and $text is the function parameter.
You can take advantage of __call
method to re-route non-existent method calls. Add a registry to your class so you can add/remove methods on the fly. Calling convention for external function is that the first parameter is the object the function has been bound to.
clas Foo {
private $registry;
public function __call($alias, $args) {
if (array_key_exists($alias, $this->registry)) {
// prepend list of parameters with $this object
array_unshift($args, $this);
return call_user_func_array($this->registry[$alias], $args)
}
}
public function register($method, $alias = null) {
$alias or $alias = $method; // use original method name if no alias
$this->registry[$alias] = $method;
}
public function unregister($alias) {
unset($this->registry[$alias]);
}
}
Imagine function clone_object
that returns array of cloned objects. First parameter is object to be cloned, and the second is number of clones.
function clone_object($foo, $n) {
$a = array();
while ($n--) {
$a[] = clone $foo;
}
return $a;
}
Now, this way you can inject method clone_object
into class Foo
and give it an alias name bar
:
$f = new Foo();
$f->register('clone_object', 'bar');
$f->bar(5); // this will return array of 5 cloned objects
In the last line calling method bar
will cause redirection to function clone_object
. Mind that calling convention will insert parameter $this
into parameters list, so (5)
will be changed to ($this, 5)
.
The caveat is that external functions can't work on private/protected properties and methods.
Just a final word, if you can think of a solution that does not change objects on the fly, you should probably use it. The above is a black wizardry and should be used with extreme caution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With