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/13 13:33] – 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 74: | Line 76: | ||
TranslatorInterface $translator = null, | TranslatorInterface $translator = null, | ||
) { | ) { | ||
- | $this-> | + | $this-> |
} | } | ||
} | } | ||
</ | </ | ||
- | ==== 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 |
- | * naming of the method | + | Imagine that you have the following function, that takes a closure and two numbers to do some operation |
- | * exact behavior when checks fail | + | |
- | * behavior of the reflection API on resulting objects | + | |
- | ==== Unaffected PHP Functionality ==== | + | <code php> |
+ | function executeOperation(Closure $operator, int $a, int $b): int { | ||
+ | return $operator($a, | ||
+ | } | ||
+ | </ | ||
- | No existing PHP functionality would be affected | + | This example demonstrates how the < |
- | ==== Future Scope ==== | + | <code php> |
+ | interface OperatorInterface { | ||
+ | public function __invoke(int $x, int $y): int; | ||
+ | } | ||
- | None anticipated yet. | + | function executeOperation(OperatorInterface $operator, int $a, int $b): int { |
+ | return $operator($a, | ||
+ | } | ||
- | ==== Proposed PHP Version ==== | + | $addition |
+ | return $x + $y; | ||
+ | }; | ||
- | This RFC targets PHP version 8.3. | + | $multiplication = function (int $x, int $y): int { |
+ | return $x * $y; | ||
+ | }; | ||
- | ==== RFC Impact ==== | + | $additionOperator |
+ | $multiplicationOperator | ||
- | This RFC impacts the PHP engine, specifically the '' | + | $result1 = executeOperation($additionOperator, 3, 5); |
+ | $result2 = executeOperation($multiplicationOperator, | ||
- | ==== Vote ==== | + | echo $result1; // Output: 8 |
+ | echo $result2; // Output: 15 | ||
+ | </ | ||
- | The vote will require | + | In this example, an '' |
- | ==== Patches and Tests ==== | + | ==== Example 3: Decoupling URI template implementations using adapters |
- | TBD | + | This example demonstrates how the < |
+ | decoupling from different implementations of the URI template RFC from the Guzzle, Rize and League projects. | ||
- | ==== Rejected Features | + | <code php> |
+ | |||
+ | // This interface would live in the consumer' | ||
+ | interface UriExpanderInterface { | ||
+ | public function expand(string $template, array $variables): | ||
+ | } | ||
+ | |||
+ | // GuzzleHttp\UriTemplate:: | ||
+ | $guzzleExpander | ||
+ | -> | ||
+ | |||
+ | // Rize\UriTemplate:: | ||
+ | $rizeExpander | ||
+ | | ||
+ | )-> | ||
+ | |||
+ | // League\Uri\UriTemplate:: | ||
+ | $leagueExpander | ||
+ | | ||
+ | )-> | ||
+ | |||
+ | // 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 | ||
+ | * behavior of the reflection API on resulting objects | ||
+ | * would the engine allow implementing this without adding any extra frame in the call stack? | ||
+ | |||
+ | ===== Unaffected PHP Functionality ===== | ||
+ | |||
+ | No existing PHP functionality would be affected by this proposal. | ||
+ | |||
+ | ===== Future Scope ===== | ||
+ | |||
+ | * Allow a closure to declare a single-method interface it implements, as in e.g. < | ||
+ | * 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. | ||
+ | |||
+ | ===== Proposed PHP Version ===== | ||
+ | |||
+ | This RFC targets PHP version 8.3. | ||
+ | |||
+ | ===== 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]. | ||
- | None. | ||
rfc/allow_casting_closures_into_single-method_interface_implementations.1681392808.txt.gz · Last modified: 2023/04/13 13:33 by nicolasgrekas