On Wed, 10 Aug 2022 at 17:18, Ben Ramsey <ram...@php.net> wrote:

> I believe this is also called "monkey patching" in some places, and
> Ruby, Python, and JavaScript all offer some form of object extension
> similar to this.
>
> There is also the PHP runkit extension that provides some of the
> functionality you've described: https://www.php.net/runkit7
>


Monkey-patching generally refers to the ability to completely "re-open" a
class, and implement additional behaviour (or even change existing
behaviour) *with the same privileges as the original definition*.

Extension methods are a much more constrained feature - they don't break
the class's encapsulation, only provide an extra syntax for operations that
would already be legal. In C#, for instance, calling
"foo.someExtensionMethod(bar)" is just syntactic sugar for the static
method call "SomeClass.someExtensionMethod(foo, bar)", and cannot hide or
over-ride a real method with the same name.

The challenge in PHP is that so little is resolved at compile-time.
Adapting the example from the first post:

namespace App\Business;
use extension App\CollectionExtension::map on Collection;

function foo($x) {
    $x
        ->map(fn ($value) => $value + 1)
        ->map(fn ($value) => $value * 2);
}

PHP doesn't know until run-time:

* whether App\CollectionExtension actually exists (the compiler does not
have access to an autoloader)
* whether it defines a "map" method, and applies to type Collection, even
if the user has *claimed* that in the "use" statement
* what type $x will be; even with a type constraint of "Collection $x", it
might be a sub-type, changing the answer to the following question
* whether that class already contains a "map" method, or an  __call handler

I think we would have to implement it as a final fall-back for missing
methods, between __call and throwing an error:

1. at compile-time, build a list of in-scope extensions methods; note that
these would just be strings at this point, not loaded code
2. just before throwing a "method not found" error, loop over the list,
autoloading each entry if necessary; possibly at this point, errors would
be raised for naming conflicts and other violations
3. check each in-scope extension method in turn for an "instanceof" match
against the current object
4. if one matches, despatch the call
5. if none matches, throw an error as normal

Regards,
-- 
Rowan Tommins
[IMSoP]

Reply via email to