[Pre-RFC] Support for decorator patterns

  108455
February 11, 2020 10:14 nikita.ppv@gmail.com (Nikita Popov)
Hi internals,

I'd like to get some feedback on the idea implemented in
https://github.com/php/php-src/pull/5168. It provides an easy way to
implement decorates, by taking care of forwarding any methods you do not
want to override in a type-safe way. See the PR description for details.

I've been playing with this thought for a while, and decided to try this
out now, because typed properties in PHP 7.4 offer a very nice syntax
choice for this feature.

Any initial thoughts?

Regards,
Nikita
  108567
February 14, 2020 11:09 rowan.collins@gmail.com (Rowan Tommins)
On Tue, 11 Feb 2020 at 10:14, Nikita Popov ppv@gmail.com> wrote:

> I'd like to get some feedback on the idea implemented in > https://github.com/php/php-src/pull/5168. It provides an easy way to > implement decorates, by taking care of forwarding any methods you do not > want to override in a type-safe way. See the PR description for details. >
Hi Nikita, I almost missed this thread because all the discussion has been happening off-list; for anyone else interested, please note that there's a whole discussion of the concept, not just the implementation, on the PR. I actually posted a similar idea a few months ago: https://externals.io/message/103353 One of the criticisms of my example syntax was that it required you to list the methods to delegate, which makes multiple delegates easier (you can't have a name conflict unless you write the name more than once), but large or frequently-changing targets unwieldy. The idea of using the property's interface type to generate the whitelist does feel a lot cleaner. The other criticism though was that it only helps for the methods you're completely delegating, not those you want to decorate in some way. It would be nice if there could also be some sugar for those, perhaps a limited form of AOP where you could intercept the return value of a delegated call? interface Bar { public function doSomething(int $a, string $b, Blob $c): string } class Foo implements Bar { public delegate Bar $bar; after delegate doSomething { return $return . ' of doom'; } } would de-sugar to: class Foo implements Bar { public Bar $bar; public function doSomething(int $a, string $b, Blob $c): string { $return = $this->bar->doSomething($a, $b, $c); return $return . ' of doom'; } } That would also cover the fluent interface case: after delegate setFoo { $this->delegated = $return; return $this; } Decorating what happens *before* the call would be trickier, because you need to assign names for the parameters, and by the time you've written out the whole signature, you might as well implement the method in full. Regards, -- Rowan Tommins [IMSoP]
  108570
February 14, 2020 11:55 rowan.collins@gmail.com (Rowan Tommins)
On Fri, 14 Feb 2020 at 11:09, Rowan Tommins collins@gmail.com> wrote:

> > That would also cover the fluent interface case: > > after delegate setFoo { > $this->delegated = $return; > return $this; > } >
I just realised that this could be easily extended to share an implementation across multiple methods, making a really succinct decorator definition: class FluentThingDecorator implements FluentThing { private int $setterCount=0; private int $getterCount =0; private delegate FluentThing $delegated; public function __construct(FluentThing $delegate) { $this->delegated = $delegate; } public function getSetterCount() { return $this->setterCount; } public function getGetterCount() { return $this->getterCount; } after delegate getFoo, getBar, getBaz { $this->getterCount++; return $return; } after delegate setFoo, setBar, setBaz { $this->setterCount++; $this->delegated = $return; return $this; } } It might be useful to have access to the name of the function actually called, e.g. for an audit log decorator; maybe the de-sugaring could happen early enough that __METHOD__ took on the right value for each case. Regards, -- Rowan Tommins [IMSoP]