Hello all,

I've had an idea that's been burning in my head for a while.  Rather
than write an RFC or do any significant work on it, I thought I would
bounce this off of you all first.

Basically, I see a problem with implementing decorators in PHP.  To
explain the problem, let's say that I have a class that I want to
decorate that implements IteratorAggregate and Countable.  Right now,
to decorate that properly I would need to make the following class:

class MyDecorator implements IteratorAggregate, Countable {
    protected $object = null;

    public function __construct($object) {
        $this->object = $object;
    }

    public function __call($name, $args) {
        return call_user_func_array(array($this->object, $name), $args);
    }

    public function getIterator() {
        return $this->object->getIterator();
    }

    public function count() {
        return $this->object->count();
    }

    public function addedFunctionality() {
        //blah
    }
}

And that's for two interfaces implementing one method each.  Imagine
implementing a dozen methods.  That's a lot of duplication.

Now, with traits we could add a trait for each interface which proxies
back to `$this->object`.  But that's still a lot of duplication and
hard-coding.

So, I've been trying to think of a few methods to add syntactic sugar
to PHP to make this a lot easier.  So here's my thoughts

Option 1

Add a magic interface `\PHP\Decorator` which would declare a
`getDecoratedObject()` method.  It would after construction call that
method to figure out what interfaces the decorated object uses.  Then,
it would magically implement them (as above) on the class if they
weren't already implemented (overridden).  That way the decorated
object could be resolved at runtime.  I'm not sold on this concept as
it feels a bit too "magic" to me.

class MyDecorator implements \PHP\Decorator {
    protected $object;
    public function __construct($object) {
        $this->object = $object;
    }
    public function getDecoratedObject() {
        return $this->object;
    }
    public function addedFunctionality() {
        // blah
    }
}

Option 2

Implement a magic interface `\PHP\Decorator` which would then allow
all declared interfaces to be satisfied by `__call`.  This should
require the least change to the core, since the only real change
that's needed is in the core where it checks that the declared
interface is satisfied.

class MyDecorator implements IteratorAggregate, Countable {
    protected $object = null;

    public function __construct($object) {
        $this->object = $object;
    }

    public function __call($name, $args) {
        return call_user_func_array(array($this->object, $name), $args);
    }

    public function addedFunctionality() {
        // blah
    }
}

Option 3

Add syntax to the member declaration that lets you declare which
methods should be proxied to a member object.  So something like this:

class MyDecorator implements IteratorAggregate, Countable {
    protected $object decorates {
        public function getIterator();
        public function count();
    }

    public function __construct($object) {
        $this->object = $object;
    }

    public function addedFunctionality() {
        // blah
    }
}

Then, at compile time the core could do a replace on the class to add
the proxy methods.  This has a major advantage in that you could use
multiple classes to selectively satisfy an interface.  So you could
actually use it to compose an object at compile time that proxies to
multiple objects.

Personally, I like the explicitness of Option 3, but I could get
behind Option 2 as well.  Option 1 feels a bit too magic for my
tastes.

Another thought, should a decorator be able to pass the type hint that
the decorated object can pass?  For example, should `new
MyDecorator(new PDO)` be able to pass `__construct(PDO $pdo);`?  If
so, how would that be handled?  Would the `Decorates` line
automagically insert the decorator in the class hiearchy for type
checking only?  Or is that a bad idea in general (I have a feeling it
is)...

What are your thoughts?

Anthony

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to