Hey all
I've posted before about adding the ability to do dynamic decorators
before. I think I have come up with a method to do so in core.
Basically, the problem is that I can't create a decorator for a class at
all. I would have to extend that class (with all of the coupling issues
that brings). I can create a decorator for interfaces, but I'd need to list
proxy methods for each and every interface combination in each decorator.
This leads to tons of boilerplate issues. For example:
class CachableFooDecorator implements foo {
protected $parent;
public function __construct(Foo $obj) {
$this->parent = $obj;
}
public function __call($method, $args) {
return call_user_func_array(array($this->parent, $method), $args);
}
public function method1($a, $b) {
return $this->parent->method1($a, $b);
}
public function method2($a) {
if (!$this->hasCache('method2', $a)) {
$ret = $this->parent->method2($a);
$this->setCache('method2', $a, $ret);
}
return $this->getCache('method2', $a);
}
}
That's a lot of boilerplate for each possible iteration. This is one reason
people like traits so much, as it's easier to just do automated copy/paste
than use the proper patterns.
So, I've wanted to add a dynamic way of being able to decorate classes at
the core level. So you'd declare the class in a special way and it would
handle that boilerplate for you.
I've come up with a method to do so. Basically, I created a class
SplDecorator which looks like this:
class SplDecorator {
private $parent;
public function __construct($obj) {
$this->parent = $obj;
}
public function __call($method, $args) {
return call_user_func_array(array($this->parent, $method), $args);
}
public function getDecoratedObject() {
return $this->parent;
}
}
The other difference, is there's an object creation handler which adds the
parent's class entry to the current instances interface list. Basically:
Z_OBJCE_P(this)->interfaces = safe_realloc(Z_OBJCE_P(object)->interfaces,
Z_OBJCE_P(object)->num_interfaces + 1, sizeof(zend_class_entry), 0);
Z_OPJCE_P(object)->interfaces[Z_OBJCE_P(object)->num_interfaces] =
Z_OBJCE_P(parent);
Z_OBJCE_P(object)->num_interfaces++;
Now, the internal instanceof function is recursive, so it will return true
when tested against this class list.
So, example code like:
class Foo {}
class Bar extends SplDecorator {}
$b = new Bar(new Foo);
var_dump($b instanceof Foo); // true
It also works with type hints:
function test(Foo $f) {}
test($b);
Now, there's a lot more to do (property cascading, interface validation,
etc), but the initial proof-of-concept is there.
I have it working locally, I'll push it to github on my branch in a day or
two so that you can play around with it...
What do you think? Is this a route that I should continue down? Or is there
something fundamental that I'm missing here? I know that Reflection,
get_interfaces(), etc would need to be updated to account for this.
Thoughts?
Anthony