Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:56513 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 37902 invoked from network); 23 Nov 2011 15:24:29 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 23 Nov 2011 15:24:29 -0000 Authentication-Results: pb1.pair.com header.from=ircmaxell@gmail.com; sender-id=pass Authentication-Results: pb1.pair.com smtp.mail=ircmaxell@gmail.com; spf=pass; sender-id=pass Received-SPF: pass (pb1.pair.com: domain gmail.com designates 209.85.216.49 as permitted sender) X-PHP-List-Original-Sender: ircmaxell@gmail.com X-Host-Fingerprint: 209.85.216.49 mail-qw0-f49.google.com Received: from [209.85.216.49] ([209.85.216.49:53856] helo=mail-qw0-f49.google.com) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 31/C6-31179-C201DCE4 for ; Wed, 23 Nov 2011 10:24:28 -0500 Received: by qadc16 with SMTP id c16so1622066qad.8 for ; Wed, 23 Nov 2011 07:24:25 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:sender:date:x-google-sender-auth:message-id:subject :from:to:content-type; bh=jubcSrpua/wt0HQGdOCdCq8HJO55Vf7iFEkXdreC8uk=; b=vdgmBLlevSquJxaQ4itBG+fw/SweZIUGEYXnBh5QPJaZEhJ5zzxvod3ampbSRRPqQ0 6WuWTiCdOAJSBg9hBOWhXU+Hu0s5cNnass9qNPjgUyLbUatjVW6/W/LY3C5p05fFPTaz EblR5CGFD12vlKDgLfpsJmPFGEZmeUvN6icTE= MIME-Version: 1.0 Received: by 10.224.9.72 with SMTP id k8mr10941154qak.24.1322061865634; Wed, 23 Nov 2011 07:24:25 -0800 (PST) Sender: ircmaxell@gmail.com Received: by 10.229.228.209 with HTTP; Wed, 23 Nov 2011 07:24:25 -0800 (PST) Date: Wed, 23 Nov 2011 10:24:25 -0500 X-Google-Sender-Auth: KZx1CJYY95jCYWu2XM8TYHB-P-k Message-ID: To: internals@lists.php.net Content-Type: text/plain; charset=ISO-8859-1 Subject: Thoughts On Implementing Decorator Sugar to core From: ircmaxell@php.net (Anthony Ferrara) 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