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

Reply via email to