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/25 09:17] – 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 < | ||
- | ==== Proposal ==== | + | ===== Proposal |
A new method, < | A new method, < | ||
Line 61: | Line 61: | ||
* **Providing a type-safe alternative to the < | * **Providing a type-safe alternative to the < | ||
- | ===== Example 1: Using a closure as a default implementation for a TranslatorInterface | + | ==== Example 1: Using a closure as a default implementation for a TranslatorInterface ==== |
This example demonstrates how to use the < | This example demonstrates how to use the < | ||
Line 85: | Line 85: | ||
If no custom implementation of '' | If no custom implementation of '' | ||
- | ===== Example 2: Type-safe alternative to using <php>Closure</ | + | ==== Example 2: Type-safe alternative to using Closure ==== |
- | Imagine that you have the following function, that takes a closure and two number | + | Imagine that you have the following function, that takes a closure and two numbers |
<code php> | <code php> | ||
- | function executeOperation(Closure $operation, int $a, int $b): int { | + | function executeOperation(Closure $operator, int $a, int $b): int { |
- | return $operation($a, $b); | + | return $operator($a, $b); |
} | } | ||
</ | </ | ||
Line 102: | Line 102: | ||
} | } | ||
- | function executeOperation(InvokableInterface | + | function executeOperation(OperatorInterface |
- | return $operation($a, $b); | + | return $operator($a, $b); |
} | } | ||
Line 126: | Line 126: | ||
In this example, an '' | In this example, an '' | ||
- | ==== Backward Incompatible Changes ==== | + | ==== 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 < | This proposal introduces no backward incompatible changes, as it only adds a new method to the < | ||
- | ==== Open Issues ==== | + | ===== Open Issues |
* exact behavior when checks fail | * exact behavior when checks fail | ||
Line 136: | Line 228: | ||
* 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? | ||
- | ==== 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. < | ||
Line 147: | Line 239: | ||
Both ideas are complementary to the one proposed in this RFC. | 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.1682414274.txt.gz · Last modified: 2023/04/25 09:17 by nicolasgrekas