Hey internals. The idea is to introduce extension methods, similar to those in Kotlin, C#, Dart. For those unfamiliar, those are just regular functions with fancy syntax. However, I think having those will not only improve readability, but also cover some of the previously requested features.
Say you have a `class Collection` and you want to add a new method `map(callable $callable): Collection`. The first instinct is to go into that file and add the method, this will work. But what if `class Collection` is defined in a vendor package? You could define a regular function like this: `map(Collection $collection, callable $callable): Collection`, then import it whenever you need to use that. This solution works, but in practice is rarely used. The reasons are: - there's no IDE completion: `$collection-> ` <- here I want IDE to auto-complete the `map` method somehow, but since it's a function this is impossible - it's ugly looking and hard to read: `getPromotions(mostExpensiveItem(getShoppingList(getCurrentUser(), 'wishlist'), ['exclude' => 'onSale']), $holiday);` - it's easy to mess up the order of arguments The pipe operator RFC [1] was trying to solve that but got rejected. Major libraries/frameworks like Laravel [2] and Carbon [3] use a solution with `__call` that allows users to define custom methods on their classes: `Carbon::mixin('toMyTimeFormat', fn () => $this->format('MM YYYY DD'));` Again, this solution: - doesn't offer IDE completion and doesn't offer IDE navigation - harder to understand - doesn't work with interfaces, traits, enums or primitives Other languages (Kotlin [4], C# [5], Dart [6]) I'm aware of solve this problem with extension methods. All of them use slightly different syntax, but the main idea is: - you define an extension method the same way you define a function, except you specify which type you're extending - you can use any type that a function can accept. This includes primitives, classes, interfaces, traits and enums - the type you're extending is implicitly bound to `$this` - you only have access to the public scope - you can't access private/protected members - you have to import those the same way you import functions. You can't define extensions globally In PHP this could look like this: ```php // Illuminate/Collection.php namespace Illuminate; class Collection {} // App/CollectionExtension.php namespace App; use Illuminate\Collection; extension CollectionExtension on Collection { function map(callable $callable): Collection { return new Collection(array_map($callable, $this->items)); } } // App/Business/Logic.php namespace App\Business; use extension App\CollectionExtension::map; (new Collection(1, 2, 3)) ->map(fn ($value) => $value + 1) ->map(fn ($value) => $value * 2); ``` The way this should work is PHP first checks whether `Collection` has `map` method. If one's missing, it gets all used extensions that match that method name, autoloads them (the same way other class-likes are loaded) and checks whether current type `Collection` matches the type specified in the extension. If so, calls the method, otherwise attempts to call __call. This same concept eliminates the need for scalar objects or scalar extension methods [7]. I'm guessing there will be problems with optimization and OPCache. What are your thoughts? References: - [1] - https://wiki.php.net/rfc/pipe-operator-v2 - [2] - https://github.com/laravel/framework/blob/9.x/src/Illuminate/Macroable/Traits/Macroable.php - [3] - https://github.com/briannesbitt/Carbon/blob/master/src/Carbon/Traits/Macro.php - [4] - https://kotlinlang.org/docs/extensions.html - [5] - https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods - [6] - https://dart.dev/guides/language/extension-methods - [7] - https://github.com/nikic/scalar_objects