Hi,
I've been thinking about how the language could better support
Composition patterns like Decorators and Proxies, using some simple
syntax sugar not dissimilar to Traits.
Example problem:
The other day, I was writing a session handler which slightly modified
the behaviour of one provided by a library. The library implementation
was closed for inheritance (a final class accessed via a factory which
handled configuration), so I wrote a Decorator class, which added a
single method. Everything worked fine, using Composition over
Inheritance. But...
In order for my Decorator class to implement the
SessionHandlerInterface, I needed to write six stub functions, each of
the form "function foo($bar) { return
$this->delegatedHandler->foo($bar); }". With whitespace and docblocks,
these amount to nearly 90 lines of code, as much as the actual custom
code in the Decorator.
Possible solution:
Methods in a Decorator or Proxy which are delegated without any
additional functionality can easily be generated programmatically, so
could the language provide syntactic sugar to do this for you?
- To delegate to an instance held in a property, add the "delegate"
keyword at the end of the property declaration, followed by a list of
method names to generate, e.g. "private Foo $foo delegate { doX, doY };" - Delegating static method calls to another class could also be
supported, using a standalone declaration like "static delegate
ClassName { staticMethodName };" - As with Traits, the generated methods would be stitched into the class
at compile time, and not distinguishable in reflection. - Delegated methods could be used to implement an interface, either
completely or partially (again, like Traits). - Other features of traits could be included if they were seen as
useful, such as aliasing ("delegate { foo as bar }") and changing
visibility ("delegate { foo as private }")
Example:
class SpecialSessionHandlerDecorator implements
\SessionHandlerInterface, SpecialInterface {
private \SessionHandlerInterface $delegatedHandler delegate {
close, destroy, gc, open, read, write
};
public function specialMethod() { ... }
}
What do people think? Would this be useful, or am I missing some major
limitation or implementation problem? Does any other language provide a
feature like this which we can learn from?
Regards,
--
Rowan Collins
[IMSoP]
On Sun, Oct 21, 2018 at 7:41 PM Rowan Collins rowan.collins@gmail.com
wrote:
Hi,
I've been thinking about how the language could better support
Composition patterns like Decorators and Proxies, using some simple
syntax sugar not dissimilar to Traits.
I do not think that this syntactic sugar is needed:
- If you have few methods, writing the delegation code is trivially done,
tested, statically analysed. - If you have a gazillion of methods, something is really wrong (you
can't decorate something with ~5+ methods without introducing some
unexpected side-effect - that code is already a bit too complex).
Implementing this proposal to aid those scenarios is probably aiding bad
API design. - If you need to proxy and add behavior, userland libraries already exist
(mostlydoctrine/common
at
https://github.com/doctrine/common/tree/a210246d286c77d2b89040f8691ba7b3a713d2c1/lib/Doctrine/Common/Proxy,
goaop/framework
at https://github.com/goaop/framework/tree/2.2.0 and
ocramius/proxy-manager
at
https://github.com/Ocramius/ProxyManager/tree/2.2.2): if you aren't happy
with those, a bit ofReflectionClass#getMethods()
grunt work is just
fine. We're still talking about edge cases anyway. - If you are proposing this syntax for performance reasons, most current
delegation code has almost no noticeable overhead besides a new stack frame
(possibly something the engine could optimise), and when it is present, it
is to avoid I/O (much heavier anyway)
Example:
class SpecialSessionHandlerDecorator implements
\SessionHandlerInterface, SpecialInterface {
private \SessionHandlerInterface $delegatedHandler delegate {
close, destroy, gc, open, read, write
};public function specialMethod() { ... }
}
One problem with delegating calls this way, is that any new method
implemented by an ancestor will not be delegated by default. A better
solution would be to have a single delegate
keyword that intercepts all
method calls for methods not declared in this class, as if __call
was
implicitly implemented:
class SpecialSessionHandlerDecorator implements \SessionHandlerInterface,
SpecialInterface
{
private \SessionHandlerInterface $delegateHandler delegate;
public function specialMethod() { ... }
}
If you don't do it this way, you end up with a BC break any time an
ancestor defines new public API. Yes, in this case these are interfaces, so
it would already count as BC break, but if the parent is a class, adding
new methods becomes a BC break as soon as the class is not final
(I
described it more carefully at
https://github.com/Roave/BackwardCompatibilityCheck/issues/111 - missed to
add that check there, so I'm glad that this issue reminded me of that).
Still, even if you do it this way, implicit delegation of any newly added
API may also not be wished, so it is an open-ended question.
Anyway, I do think that:
- the language already has all the tooling required to implement
decorators correctly - the addition of a
delegate
functionality would cause more confusion
for something that is already really trivial to implement/test/read
The suggestion requires some stronger foundations/reasoning in order to be
turned into a valuable RFC.
Marco Pivetta
Am 21.10.2018 um 23:42 schrieb Marco Pivetta:
- the language already has all the tooling required to implement
decorators correctly- the addition of a
delegate
functionality would cause more confusion
for something that is already really trivial to implement/test/read
ACK
Hi Marco,
Thanks for the response.
* If you need to proxy and add behavior, userland libraries already
exist (mostlydoctrine/common
at
https://github.com/doctrine/common/tree/a210246d286c77d2b89040f8691ba7b3a713d2c1/lib/Doctrine/Common/Proxy,
goaop/framework
at https://github.com/goaop/framework/tree/2.2.0 and
ocramius/proxy-manager
at
https://github.com/Ocramius/ProxyManager/tree/2.2.2): if you aren't
happy with those, a bit ofReflectionClass#getMethods()
grunt work
is just fine. We're still talking about edge cases anyway.
These are certainly interesting, but they feel rather too "heavyweight"
for every day use. Imagine if you had to include a library which used
reflection to generate a string and pass it to eval() every time you
wanted to sub-class something!
Native AOP might come closer to what I was thinking about, but seems to
be a very different way of thinking about code structure, rather than
something that can be easily mixed with an existing architecture.
* If you are proposing this syntax for performance reasons, most
current delegation code has almost no noticeable overhead besides a
new stack frame (possibly something the engine could optimise), and
when it is present, it is to avoid I/O (much heavier anyway)
My main aim is to make composition feel more like a natural choice in
the language. If I may cheekily use your own words against you:
There will be less stuffing functionality in existing code via
inheritance, which, in my opinion, is a symptom of haste combined with
feature creep.
I have often heard it said that "it's a shame languages provide more
features for inheritance than composition"; I was thinking about what
features might redress that balance.
One problem with delegating calls this way, is that any new method
implemented by an ancestor will not be delegated by default. A better
solution would be to have a singledelegate
keyword that intercepts
all method calls for methods not declared in this class, as if
__call
was implicitly implemented:
This is deliberate; implicitly copying everything from another class
would just be reinventing (multiple) inheritance. If the delegated class
adds a new public method, it should be a conscious decision whether to
a) transparently delegate it; b) explicitly wrap it with custom
functionality; or c) completely ignore it.
If you don't do it this way, you end up with a BC break any time an
ancestor defines new public API. Yes, in this case these are
interfaces, so it would already count as BC break, but if the parent
is a class...
The point is not that the ancestors are interfaces, it's that the
proxy is targeting an interface. Consider this scenario:
class LibraryImplementation implements FrameworkInterface,
LibrarySpecificInterface {
public function thingRequiredByFramework() {}
public function additonalThing() {}
}
class UserlandWrapper implements FrameworkInterface {
public LibraryImplementation $inner delegate {
thingRequiredByFramework };
}
Although the wrapper is tied to a particular implementation (for
whatever reason), it's only used in the context of the framework. If
FrameworkInterface changes, then every class implementing it needs
fixing, and that includes the UserlandWrapper (e.g, it might need to
wrap a new method call with custom logic); but if
LibrarySpecificInterface changes, and LibraryImplementation is updated,
there's no relevance to UserlandWrapper.
LibraryImplementationis just a tool being used to implement
FrameworkInterface; its other abilities are irrelevant.
Anyway, I do think that:
1. the language already has all the tooling required to implement
decorators correctly
2. the addition of adelegate
functionality would cause more
confusion for something that is already really trivial to
implement/test/read
I think the existence of userland libraries like ProxyManager is rather
good evidence that the language doesn't have very powerful tools for
this. But yes, any new feature like this has to be useful enough to
justify the extra complexity and learning curve; it may be that the
version presented here isn't powerful enough to pass that bar.
Regards,
--
Rowan Collins
[IMSoP]
I have wanted something like this before. I used a trait helper. This
worked for this domain because there is an interface that is well
established for the helper to use. Here's an example (written in my
email client, untested):
trait OuterIteratorHelper {
abstract function getInnerIterator(): \Iterator;
function rewind()
: void { $this->getInnerIterator()->rewind(); }
function valid(): bool { return $this->getInnerIterator()->valid(); }
function next()
: void { $this->getInnerIterator()->next(); }
function key()
{ return $this->getInnerIterator()->key(); }
function current()
{ return $this->getInnerIterator()->current(); }
}
final class TransformIterator implements \OuterIterator {
private $transformer, $inner;
function __construct(callable $transformer, \Iterator $inner) {
$this->transformer = $transformer;
$this->inner = $inner;
}
use OuterIteratorHelper;
function getInnerIterator() { return $this->inner; }
function current()
{
return ($this->transformer)($this->inner->current());
}
}
Hi,
I've been thinking about how the language could better support
Composition patterns like Decorators and Proxies, using some simple
syntax sugar not dissimilar to Traits.Example problem:
The other day, I was writing a session handler which slightly modified
the behaviour of one provided by a library. The library implementation
was closed for inheritance (a final class accessed via a factory which
handled configuration), so I wrote a Decorator class, which added a
single method. Everything worked fine, using Composition over
Inheritance. But...In order for my Decorator class to implement the
SessionHandlerInterface, I needed to write six stub functions, each of
the form "function foo($bar) { return
$this->delegatedHandler->foo($bar); }". With whitespace and docblocks,
these amount to nearly 90 lines of code, as much as the actual custom
code in the Decorator.Possible solution:
Methods in a Decorator or Proxy which are delegated without any
additional functionality can easily be generated programmatically, so
could the language provide syntactic sugar to do this for you?
- To delegate to an instance held in a property, add the "delegate"
keyword at the end of the property declaration, followed by a list of
method names to generate, e.g. "private Foo $foo delegate { doX, doY };"- Delegating static method calls to another class could also be
supported, using a standalone declaration like "static delegate
ClassName { staticMethodName };"- As with Traits, the generated methods would be stitched into the class
at compile time, and not distinguishable in reflection.- Delegated methods could be used to implement an interface, either
completely or partially (again, like Traits).- Other features of traits could be included if they were seen as
useful, such as aliasing ("delegate { foo as bar }") and changing
visibility ("delegate { foo as private }")Example:
class SpecialSessionHandlerDecorator implements
\SessionHandlerInterface, SpecialInterface {
private \SessionHandlerInterface $delegatedHandler delegate {
close, destroy, gc, open, read, write
};public function specialMethod() { ... }
}
What do people think? Would this be useful, or am I missing some major
limitation or implementation problem? Does any other language provide a
feature like this which we can learn from?Regards,
I like this in concept. However, my big issue is return values.
Sometimes when proxying to an object like that, you want to return the return
value of the proxied method.
Sometimes when proxying to an object like that, you're supposed to return
$this, but your $this is different than the proxied object's $this.
Detecting when the generated proxy method should pass through the return value
from the method it's proxying for vs when it should override it somehow sounds
non-simple.
--Larry Garfield
Sometimes when proxying to an object like that, you're supposed to return
$this, but your $this is different than the proxied object's $this.
Hm, that's a very interesting limitation.
I wonder if there's some way to include common manipulations like this
in the syntax, while still feeling more convenient than writing out the
whole method signature.
Regards,
--
Rowan Collins
[IMSoP]