Re: [Pre RFC] Automated delegation using implements by
| From: | Larry Garfield | Date: | Thu, 01 Oct 2026 13:49:38 +0000 |
| Subject: | Re: [Pre RFC] Automated delegation using implements by | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-132750@lists.php.net to get a copy of this message | ||
On Wed, Sep 30, 2026, at 7:29 PM, Karoly Negyesi wrote:
> Hello,
>
> I submitted a draft RFC to ŪY‹bï²BÙ
> €»Ë–ùhttps://github.com/php/php-src/pull/24030.
> The PR contains an implementation and tests (please be gentle, I
> haven't written C in decades).
>
> I propose an "implements by" syntax to automate delegation, closely
> modelled on Kotlin.
>
> In short,
>
> interface T { function foo(); }
> class A implements T { function foo() { print "A"; } }
> class C implements T by $t { function __construct(private T $t) {} }
> new C(new A)->foo();
>
> The performance impact should be minimal as everything happens at
> compile time and during class linking with no runtime changes. There is
> no BC break. (That's the intention, at least. I modelled the change
> after enum.)
>
> Regards
>
> Karoly Negyesi
>
> P.S. Credit to Larry Garfield for pushing for this better syntax than
> my original idea.
Credit to Kotlin, from which this syntax is borrowed wholesale. :-)
I agree the RFC is currently a bit sparse, but that can evolve. I am strongly in favor of the
concept.
I think the strongest example I have for where it would be useful is the Drupal database abstraction
layer, which I wrote many years ago (with some input from Karoly, as well). It's evolved a bit
since then, but the core issue is still there.
For the query builder, many types of queries have WHERE support. Naturally, we wanted to implement
that logic only once, but also didn't want to use inheritance for that (for all the usual
reasons). What we landed on was a ConditionInterface[1], implemented by a Condition object[2].
Then each query type class (Select, Insert, Delete, Update, etc.) would implement
ConditionInterface, have an internal $condition object, and just pass-through all of the methods
manually. That was ~100 lines of boilerplate code on every class.
Since it was originally written, it looks like someone has factored that out to a trait[3], which is
a bit better in this case since it's reused enough but it's still ~100 lines of extra
boilerplate, just automated.
Being able to directly delegate to a condition object would have made that whole process vastly
easier, and likely eliminated many uses of inheritance, too.
(chx, feel free to quote any of the above in the RFC if it is helpful.)
However, that example also highlights the main limitation of the current proposal: Many of the
methods are fluent, returning $this. A simple passthrough method would return the inner object, not
the outer object. Switching it to instead return the outer object (as the trait example listed
does) is something very difficult for the engine to detect accurately, but in many cases will be the
preferred behavior.
The best solution I can think of for that is allowing the delegation declaration to specify which
methods should be "masked" (for want of a better term; please suggest one). Something
like (spitballing):
interface I {
public function foo(): self;
public function bar(): self;
public function baz(): string;
}
class C implements I by $i (mask foo) {
// foo() will return an instance of C, bar() will return whatever $i->bar() returns (presumably
$i), and baz() just returns a string as normal.
}
I'm not sure if that is the best approach, but I throw it out to stimulate discussion.
[1] https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
[2] https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Core/Database/Query/Condition.php
[3] https://git.drupalcode.org/project/drupal/-/blob/main/core/lib/Drupal/Core/Database/Query/QueryConditionTrait.php