On Mon, Jun 9, 2025 at 7:36 PM Dmitry Derepko <xepo...@gmail.com> wrote:

> Hello, Internals!
>
> I've implemented an alpha implementation of the Extension Functions in PHP.
> Basically, it's a syntax sugar of the imperative call of the function with
> the passing the object as a first argument, but anyway.
>
> Here is how it looks like in Kotlin:
> https://kotlinlang.org/docs/extensions.html
>
> In PHP it might looks like so:
>
> function stdClass.getName() {
>     return $this->name;
> }
>
> $obj = new stdClass;
> $obj->name = 'Dmitrii';
> echo $obj->getName(); // prints Dmitrii
>
> so desugared version is:
>
> function getName(stdClass $obj) {
>     return $obj->name;
> }
>
> $obj = new stdClass;
> $obj->name = 'Dmitrii';
> echo getName($obj); // prints Dmitrii
>
> Quite simple improvement, but a really convenient way to "attach"
> behaviour and extend vendor code to make it more readable.
>
> Functions register as usual, using `use` keyword. So there are no
> surprises when you call a function which does not exist, only an implicit
> way to specify the function.
>
> "Attaching" the function with the name of an existing member
> function raises an exception.
> But you may juggle different functions from different namespaces:
>
> namespace A;
>
> use function aa; // from global namespace
> OR
> use function B\aa; // from B namespace
> OR
> use function B\aa as Baa; // from B namespace with alias
>
> `use function` construction may be enlarged with the "extension" keyword:
> use function getTime; // regular function
> use extension function getTime; // extension function, unable to call
> without correct receiver
>
> As in Kotlin, it should access only public members.
>
> class A { private $name; }
>
> function A.getName() {
>     return $this->name; // raises an exception because private and
> protected members aren't available
> }
>
> Supporting types DNF is under question, but I'd leave them as future scope.
>
> function ((A&B)|null).smth() { ... }
> but it could be resolved to
> function smth((A&B)|null $obj) { ... }
>
> The dot as a delimiter is also under the question, here are a few options:
>
> function stdClass::getName();
> function stdClass->getName();
> function stdClass.getName();
> function ::stdClass getName();
> function stdClass<-getName();
> function getName of stdClass();
>
> etc.
>
> I've tried to implement the feature through attaching a function to the
> class functions scope, which is wrong and does not follow the requirements.
> It has memory leaks.
>
> https://github.com/php/php-src/compare/master...xepozz:php-src:extension-functions?expand=1#diff-1dd36b02e5025ec3a5a546f8e41374ee4fc18c411bce447dd1dc2952329ccbe6R25
>
> I also thought about adding this feature as a custom attribute behaviour,
> but it's a way difficult:
>
> #[Extension("stdClass")]
> function hasName(): bool {
>     return $this->name;
> }
>
> I can try to implement the de-sugared version to make it work correctly.
>
> ---
>
> WDYT guys?
>
> --
> Best regards,
> Dmitrii Derepko.
> @xepozz
>

It's an interesting concept, and reminds me of how Lua handles this with
`string.len(theVariable)` vs `theVariable:len()`. The way you define the
function with a dot or colon, determines whether or not you have `self`
(`$this`) available or not. Feature wise, what would be use-cases to have
this?

DTOs in libraries, but perhaps also value objects could add a custom
factory function. I don't want my database models/entities to know about
the DTO, but the (vendor) DTO can't know about the database model/entity
either. This scenario would be to define a runtime static function to
create the object, and perhaps even a runtime function the other way around
to populate my entity based on the DTO. I could keep this code separate
from both classes and merely have it act as a glue. I don't know if this is
a good practice though.

What about interfaces and abstract classes, would these functions fulfill
contracts in any way? It sounds like runtime traits in that regard, but
makes static code analysis a lot more complex; not only for tools, but also
for humans to parse and understand. Would runtime traits be an alternate
solution to function extension to deal with autoloading?

Reply via email to