On 2011-11-23, Anthony Ferrara <ircmax...@php.net> wrote: > 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.
Oof, yes. :) <snip> > 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. This seems reasonable, but there are some pretty big drawbacks as well: * Type-hinting -- typically, a decorator should be able to be used wherever the object it decorates is used. As such, if you simply implement PHP\Decorator, type-hinting breaks. * It looks like it would require a particular constructor pattern, which could be problematic. (You don't actually specify if the constructor is the only means for injection, so I can give you the benefit of the doubt here.) The primary problem is the first listed, though, and it's pretty big -- I'd expect it would have large ramifications on Reflection, as well as on various editors and IDEs to provide hinting. <snip> > 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. The problem with this is similar to that of Option 1 -- by using __call(), you lose Reflection and hinting capabilities. <snip> > 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. I _really_ like this option, to be honest. My only nitpick is that, like Option 1, we need to flesh out how the object to be decorated is injected into the decorator. Otherwise, this answers the problems I raised in Option 1 and Option 2. > 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);`? in good OOP, you should be typehinting on interfaces, not concrete implementations; following that logic, it's no necessary. However, this breaks when decorating internal classes, where there typically aren't interfaces. So my vote is that the hint be passed on to the decorator. I have no idea how that would be handled, though. > 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? -- Matthew Weier O'Phinney Project Lead | matt...@zend.com Zend Framework | http://framework.zend.com/ PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php