rfc:callable-interfaces

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
rfc:callable-interfaces [2016/04/06 10:40] – created ocramiusrfc:callable-interfaces [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
-====== PHP RFC: Typesafe callable ======+====== PHP RFC: Callable interfaces ======
   * Version: 0.1   * Version: 0.1
-  * Date: 2016-04-04+  * Date: 2016-04-06
   * Author: Ben Scholzen, mail@dasprids.de   * Author: Ben Scholzen, mail@dasprids.de
-  * Status: Draft +  * Author: Marco Pivetta, ocramius@gmail.com 
-  * First Published at: http://wiki.php.net/rfc/typesafe-callable+  * Status: Retired 
 +  * First Published at: http://wiki.php.net/rfc/callable-interfaces
  
 ===== Introduction ===== ===== Introduction =====
-Currently, when you typehint against "callable" in your method signatures, you can never be certain whether it accepts the parameters you are giving it or if the return type is what you'd expect. You can of course validate the return type, and catch exceptions about the invalid parameters and such when calling the callable, but that is not ideal.+Currently, when you type-hint against "callable" in your method signatures, you can never be certain whether the function will accept the parameters you are giving itor if the return type is what you'd expect. You can manually validate the return type, and catch exceptions about the invalid parameters, but that is not ideal.
  
-This RFC tries to solve this problem by introducing user-defined callables.+This RFC tries to solve this problem by allowing callables to follow user-specified interfaces. 
 +This RFC is inspired by https://wiki.php.net/rfc/typesafe-callable
  
 ===== Proposal ===== ===== Proposal =====
-This RFC proposes the definition of user-defined callables. Since "callable" is already a reserved keyword, this should not be a BC break. User-defined callables should be be both allowed to be namespaced like functions and classesbut also invoke the autoloader if they are not found in the current scopeA simple callable definition could look like this:+ 
 +PHP already has way to define objects that act as functions. That mechanism is the **_****_invoke** magic method, which is widely used in libraries and frameworks. In addition to that**Closure** already implements **_****_****invoke**. 
 + 
 +**_****_****invoke** already works quite well: with this proposal, generic *callable* arrays, functions and objects will be usable as if they implemented a matching interface:
  
 <code php> <code php>
-callable FilterCallable(string $input) : string;+interface RegisterUser { 
 +    public function __invoke(Username $username) : UserRegistration; 
 +}
 </code> </code>
  
-With such a callable in place, the user can now typehint against it, like they would against any other property, and be certain that it will accept the parameters they pass in, as well as guarantee the return type they expect:+We can now implicitly implement this interface by just defining any *callable* that matches this interface: 
 + 
 + 
 +As a function:
  
 <code php> <code php>
-function foo(FilterCallable $filter) : string +function register (Username $username) : UserRegistration { 
-+    // ... domain logic here ... 
-    return $filter('bar');+     
 +    return new UserRegistration($userId);
 } }
 +</code>
  
-foo(function (string $foo) : string { return trim($foo); });+As a closure: 
 + 
 +<code php> 
 +$register = function (Username $username) : UserRegistration { 
 +    // ... domain logic here ... 
 +     
 +    return new UserRegistration($userId); 
 +};
 </code> </code>
  
-Of coursethis works with any other kind of callable as well, be it stringan array or an object.+As a static callable array: 
 + 
 +<code php> 
 +class Register { 
 +    public static function register(Username $username) : UserRegistration { 
 +        // ... domain logic here ... 
 +     
 +        return new UserRegistration($userId); 
 +    } 
 +
 + 
 +$register = [Register::class'register']; 
 +</code> 
 + 
 +As an instance callable array: 
 + 
 +<code php> 
 +class Register { 
 +    public function register(Username $username) : UserRegistration { 
 +        // ... domain logic here ... 
 +     
 +        return new UserRegistration($userId); 
 +    } 
 +
 + 
 +$register = [new Register(), 'register']; 
 +</code> 
 + 
 +We are now able to consume any of these callables wherever the interface is required in a type-hint: 
 + 
 +<code php> 
 +function runRegistration(Username $usernameRegisterUser $handler) { 
 +    var_dump($handler($username)); 
 +
 + 
 +runRegistration(new Username('DASPRiD'), $register); 
 +</code> 
 + 
 +In order for this to work, any implicitly defined callable should be cast to *Closure* at call-time. 
 + 
 +In pseudo-codethis would look like following, under the hood: 
 + 
 +<code php> 
 +function passAParameterToAPhpFunction(callable $callable, $expectedParameterInterface) { 
 +    if (! $expectedParameterInterface->isCallableInterface()) { 
 +        passParameter($callable); 
 +         
 +        return; 
 +    } 
 +     
 +    if (! $expectedParameterInterface->matches($callable)) { 
 +         throw new TypeError('Expected X, got Y'); 
 +    } 
 +     
 +    if (! is_object($callable)) { 
 +        $callable = wrapInCompatibleAnonymousClass($callable); 
 +    } 
 +     
 +    passParameter($callable); 
 +
 +</code> 
 + 
 +===== Still Open for Discussion ===== 
 + 
 +How will **instanceof** behave, when asked for a type-check against **callable**? 
 + 
 +<code php> 
 +interface RegisterUser { 
 +    public function __invoke(Username $username) : UserRegistration; 
 +
 + 
 +interface DeleteUserRegistration { 
 +    public function __invoke(Username $username) : UserRegistration; 
 +
 + 
 +$register = function (Username $username) : UserRegistration { 
 +    return new UserRegistration(...); 
 +}; 
 + 
 +var_dump($register instanceof DeleteUserRegistration); // true? false? possibly want to keep current semantics here. 
 +</code> 
 + 
 +===== Retired ===== 
 + 
 +This RFC has been retired. Reason for that is that PHP currently (Version 7.0~7.1) allows applying function semantics to objects via the **_****_invoke** magic methods. Allowing the opposite would mix the domain of functions and objects in ways that are very hard to disentangle, and it would needlessly complicate the language semantics. 
 + 
 +While it is unfortunate that migration to type-safe callables (https://wiki.php.net/rfc/typesafe-callable) would require some interface rewrites, that is indeed the correct solution, as it keeps the uni-directionality between object and function semantics.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
rfc/callable-interfaces.1459939240.txt.gz · Last modified: 2017/09/22 13:28 (external edit)