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?