rfc:allow_casting_closures_into_single-method_interface_implementations
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:allow_casting_closures_into_single-method_interface_implementations [2023/04/20 16:05] – nicolasgrekas | rfc:allow_casting_closures_into_single-method_interface_implementations [2023/04/25 19:36] (current) – nicolasgrekas | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Title: Allow casting closures into single-method interface implementations ===== | + | ====== PHP RFC: Allow casting closures into single-method interface implementations |
* Version: 1.0 | * Version: 1.0 | ||
Line 8: | Line 8: | ||
* Implementation: | * Implementation: | ||
- | ==== Introduction ==== | + | ===== Introduction |
- | This RFC proposes a new method for the '' | + | This RFC proposes a new method for the <php>Closure</ |
- | ==== Proposal ==== | + | ===== Proposal |
- | A new method, | + | A new method, |
<code php> | <code php> | ||
Line 53: | Line 53: | ||
* **Testing and mocking**: This feature could help create mock objects on-the-fly for unit testing, especially when testing the interaction between objects and their dependencies. By using a closure to implement the interface, developers can quickly create lightweight, | * **Testing and mocking**: This feature could help create mock objects on-the-fly for unit testing, especially when testing the interaction between objects and their dependencies. By using a closure to implement the interface, developers can quickly create lightweight, | ||
- | * **Adapters**: | + | * **Decoupling through |
* **Single-method interfaces**: | * **Single-method interfaces**: | ||
- | * **Rapid prototyping**: During | + | * **Dynamic behavior**: In scenarios where the implementation of an interface needs to be changed at runtime, this feature can help create |
- | * **Dynamic behavior**: In scenarios where the implementation | + | * **Providing a type-safe alternative to the < |
- | Here is an example | + | ==== Example 1: Using a closure as a default implementation for a TranslatorInterface ==== |
+ | |||
+ | This example | ||
<code php> | <code php> | ||
Line 79: | Line 81: | ||
</ | </ | ||
- | ==== Backward Incompatible Changes ==== | + | In this example, a '' |
- | This proposal introduces | + | If no custom implementation of '' |
- | ==== Open Issues ==== | + | ==== Example 2: Type-safe alternative to using Closure ==== |
+ | |||
+ | Imagine that you have the following function, that takes a closure and two numbers to do some operation on them: | ||
+ | |||
+ | <code php> | ||
+ | function executeOperation(Closure $operator, int $a, int $b): int { | ||
+ | return $operator($a, | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | This example demonstrates how the < | ||
+ | |||
+ | <code php> | ||
+ | interface OperatorInterface { | ||
+ | public function __invoke(int $x, int $y): int; | ||
+ | } | ||
+ | |||
+ | function executeOperation(OperatorInterface $operator, int $a, int $b): int { | ||
+ | return $operator($a, | ||
+ | } | ||
+ | |||
+ | $addition = function (int $x, int $y): int { | ||
+ | return $x + $y; | ||
+ | }; | ||
+ | |||
+ | $multiplication = function (int $x, int $y): int { | ||
+ | return $x * $y; | ||
+ | }; | ||
+ | |||
+ | $additionOperator = $addition-> | ||
+ | $multiplicationOperator = $multiplication-> | ||
+ | |||
+ | $result1 = executeOperation($additionOperator, | ||
+ | $result2 = executeOperation($multiplicationOperator, | ||
+ | |||
+ | echo $result1; // Output: 8 | ||
+ | echo $result2; // Output: 15 | ||
+ | </ | ||
+ | |||
+ | In this example, an '' | ||
+ | |||
+ | ==== Example 3: Decoupling URI template implementations using adapters ==== | ||
+ | |||
+ | This example demonstrates how the < | ||
+ | decoupling from different implementations of the URI template RFC from the Guzzle, Rize and League projects. | ||
+ | |||
+ | <code php> | ||
+ | |||
+ | // This interface would live in the consumer' | ||
+ | interface UriExpanderInterface { | ||
+ | public function expand(string $template, array $variables): | ||
+ | } | ||
+ | |||
+ | // GuzzleHttp\UriTemplate:: | ||
+ | $guzzleExpander = GuzzleHttp\UriTemplate:: | ||
+ | -> | ||
+ | |||
+ | // Rize\UriTemplate:: | ||
+ | $rizeExpander = (fn (string $template, array $variables): | ||
+ | => (new Rize\UriTemplate())-> | ||
+ | )-> | ||
+ | |||
+ | // League\Uri\UriTemplate:: | ||
+ | $leagueExpander = (fn (string $template, array $variables): | ||
+ | => (string) (new League\Uri\UriTemplate($template))-> | ||
+ | )-> | ||
+ | |||
+ | // Usage | ||
+ | function getExpandedUrl(UriExpanderInterface $expander, string $template, array $variables): | ||
+ | return $expander-> | ||
+ | } | ||
+ | |||
+ | $template = " | ||
+ | $variables = [' | ||
+ | |||
+ | $guzzleExpandedUrl = getExpandedUrl($guzzleExpander, | ||
+ | $rizeExpandedUrl = getExpandedUrl($rizeExpander, | ||
+ | $leagueExpandedUrl = getExpandedUrl($leagueExpander, | ||
+ | |||
+ | // The 3 implementations return: " | ||
+ | </ | ||
+ | |||
+ | In this example, an '' | ||
+ | 3 different implementations of the interface are derived using the < | ||
+ | Finally, the < | ||
+ | < | ||
+ | |||
+ | Guzzle, Rize and League URI template instances can be used interchangeably with this function, | ||
+ | illustrating the decoupling and flexibility provided by using adapters created with the < | ||
+ | |||
+ | ==== Prototype implementation ==== | ||
+ | |||
+ | Using < | ||
+ | |||
+ | <code php> | ||
+ | class Closure | ||
+ | { | ||
+ | // [...] Closure is an internal class | ||
+ | |||
+ | public function castTo(self $closure, string $interface): | ||
+ | { | ||
+ | static $cache = []; | ||
+ | |||
+ | $cacheKey = < | ||
+ | |||
+ | if (isset($cache[$cacheKey])) { | ||
+ | return $cache[$cacheKey]($closure); | ||
+ | } | ||
+ | |||
+ | $method = < | ||
+ | $signature = < | ||
+ | $return = str_ends_with($signature, | ||
+ | |||
+ | $object = eval(<<< | ||
+ | return new class (\$closure) implements {$interface} { | ||
+ | public function __construct(private readonly Closure \$closure) { | ||
+ | } | ||
+ | public function {$method}{$signature} { | ||
+ | {$return}\$this-> | ||
+ | } | ||
+ | }; | ||
+ | PHP); | ||
+ | |||
+ | $cache[$cacheKey] = $object:: | ||
+ | |||
+ | return $object; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A working prototype implementation is provided by this package: | ||
+ | https:// | ||
+ | |||
+ | ===== Backward Incompatible Changes ===== | ||
+ | |||
+ | This proposal introduces no backward incompatible changes, as it only adds a new method to the < | ||
+ | |||
+ | ===== Open Issues | ||
* exact behavior when checks fail | * exact behavior when checks fail | ||
* behavior of the reflection API on resulting objects | * behavior of the reflection API on resulting objects | ||
* would the engine allow implementing this without adding any extra frame in the call stack? | * would the engine allow implementing this without adding any extra frame in the call stack? | ||
- | * would the engine allow implementing this for closures constructed from callables? | ||
- | * would the engine allow implementing this for closures bound to an object and who's object doesn' | ||
- | ==== Unaffected PHP Functionality ==== | + | ===== Unaffected PHP Functionality |
No existing PHP functionality would be affected by this proposal. | No existing PHP functionality would be affected by this proposal. | ||
- | ==== Future Scope ==== | + | ===== Future Scope ===== |
- | * Allow a closure to declare a single-method interface it implements, as in e.g. '' | + | * Allow a closure to declare a single-method interface it implements, as in e.g. <php>function () implements FooInterface {...}</ |
- | * When passing a closure to a parameter that is typed for a single-method interface, call < | + | * When passing a closure to a parameter that is typed for a single-method interface, call < |
- | Both ideas are complementary to the one proposed in this RFC and don't conflict, neither technically nor feature-wise. | + | Both ideas are complementary to the one proposed in this RFC. |
- | ==== Proposed PHP Version ==== | + | ===== Proposed PHP Version |
This RFC targets PHP version 8.3. | This RFC targets PHP version 8.3. | ||
- | ==== Vote ==== | + | ===== Vote ===== |
The vote will require a 2/3 majority to be accepted. Voting will start on [Vote start date] and end on [Vote end date]. | The vote will require a 2/3 majority to be accepted. Voting will start on [Vote start date] and end on [Vote end date]. | ||
+ | |||
rfc/allow_casting_closures_into_single-method_interface_implementations.1682006748.txt.gz · Last modified: 2023/04/20 16:05 by nicolasgrekas