Extending classes via lambdas
In Javascript, you could compose an object piecemeal. For example, you can start with:
var foo = {};
Then extend this empty object by assigning lambdas to its properties.
foo.bar = function () { alert('bar'); }
foo.bar(); // bar
You can even pull a method from another object and assign it to this property.
var goo = {
gar: function () { alert('gar'); }
};
foo.bar = goo.gar;
foo.bar(); // gar
Or a privileged method defined in a constructor:
function Xoo(x) {
this.xar = function () { alert(x + 'ar'); }
}
moo = new Xoo('m');
foo.bar = moo.xar;
foo.bar(); // mar
This is so easy and natural because Javascript makes no distinction between methods and properties. Javascript objects can be likened to an associative array in PHP. If the array member is callable, then it is a method which can be called via its key. Otherwise, it’s just a property.
PHP isn’t quite like that, but it is possible to simulate that behavior with a bit of magic. With the new lambda syntax, it becomes even closer to the Javascript way of extending objects.
Lambda members
class Foo
{
public $bar;
}
$foo = new Foo;
$foo->bar = function () {
echo 'bar';
};
$foo->bar(); // fatal error
In this snippet, we assign a lambda to the bar property of the $foo object. Even though its value is callable, we still cannot call it directly. When the interpreter sees $foo->bar(), it looks for a bar method and leaves the bar property well alone. As far as it’s concerned, the bar method doesn’t exist. However, the lambda assigned to $foo->bar can still be invoked if it is assigned to another variable and that variable is called, like so:
$bar = $foo->bar; $bar(); // bar
But who wants that?
Fortunately, PHP allows us to capture illegal method calls. We can then proxy that illegal call to the lambda assigned to the property.
abstract class Extendable
{
protected $_behaviors = array();
public function __set($member, $value)
{
if (is_callable($value)) {
$this->_behaviors[$member] = $value;
}
}
public function __call($method, $args)
{
if (isset($this->_behaviors[$method])) {
$func = $this->_behaviors[$method];
array_unshift($args, $this);
return call_user_func_array($func, $args);
} else {
throw new BadMethodCallException('No such method: ' . $method);
}
}
}
class Foo extends Extendable
{
public $baz = 'baz';
}
$foo = new Foo();
$foo->bar = function () {
echo 'bar';
};
$foo->bar(); // bar
(There’s no real reason for the __set() and the $_behaviors array. The lambda could just as well be directly assigned to a public property).
Now it seems as if we assigned a function and called it directly. I added a little sugar in the __call() method which prepends the argument list with a reference to the calling instance. This reference must be passed because there is no $this in a lambda. So, to access the baz property:
$foo->printBaz = function ($self) {
echo $self->baz;
};
$foo->printBaz(); // baz
$foo->multiBaz = function ($self, $count) {
for ($i = 0; $i++ baz;
}
};
$foo->multiBaz(3); // bazbazbaz
Basically the same mechanism as Python objects. It’s a bit strange since the actual call doesn’t match the function signature.
It is not possible to access private or protected members inside the lambda.
Pulling functions from other objects
Since we use call_user_func_array() in __call() we could just as well pass an array containing an object and a valid method name. Just don’t forget that the $this in the assigned method refers to the ‘donor’ — the object from which the method is pulled. The $self still has to be specified in the method to access the ‘recipient’ members.
class Bar
{
public function baz($self)
{
echo $self->baz;
}
}
$bar = new Bar();
$foo->bar = array($bar, 'baz');
$foo->bar(); // baz
We could add some sugar here to make it seem like we’re passing a reference to a method.
abstract class Exportable
{
public function __get($member)
{
if (method_exists($this, $member)) {
return array($this, $member);
}
}
}
class Behaviors extends Exportable
{
protected $_bat = 'bat';
public function upperBaz($self)
{
echo strtoupper($self->baz);
}
public function multiBat($self, $count)
{
for ($i = 0; $i++ _bat;
}
}
}
$behavior = new Behaviors();
$foo->bar = $behavior->upperBaz;
$foo->bar(); // BAZ
$foo->bar = $behavior->multiBat;
$foo->bar(3); // batbatbat
Is there a point to all these?
To be honest, I don’t know. I can see myself using a lambda in an instance variable to customize some behavior. I think this is suitable for simple filters or validators. But building whole classes by pulling methods here and there seems wrong somehow. Code reuse is nice, but instantiating objects that you’ll only partially use seems wasteful. Also, not being able to use $this in the context of the calling object is quite a limitation. That means you won’t be able to ‘borrow’ methods anyway unless you write your methods in a certain way. This is not the case in Javascript where this always refers to the calling object regardless of where the function was defined.