The type system in PHP has become safer each version since PHP 7.0, by using a strict type declaration on top of the PHP file. As a newly-developed feature, it doesn’t come with the billion-dollar mistake of allowing NULL
for variables declared with a class type, unless you allow it specifically.
However, if a function need to accept a callable which accepts parameters of a specific type and returns a specific type, you cannot do it with callable
type declaration because there is no way to add type information into the callable
declaration.
Luckily, there is a magic function called __invoke()
. It can turn any object into a callable, and as a member function, you can add type declaration into it, which make the object only callable with the specified parameters.
For example, if you are writing an exception handler factory which need a third-party function to convert a Throwable
into a ResponseInterface
to produce a handler, instead of writing this:
function make_exception_handler(callable $get_response) : callable { return function (Throwable $e) use ($get_response) { // log the exception $response = $get_response($e); // output the response }; }
You can write the following instead:
interface ExceptionResponseFactoryInterface { public function __invoke(Throwable $exception) : ResponseInterface; } function make_exception_handler(ExceptionResponseFactoryInterface $get_response) : callable { return function (Throwable $e) use ($get_response) { // log the exception $response = $get_response($e); // output the response }; }
As you can see, there is absolutely no change in the function body, but the expectation is now clearly indicated in the function signature: get a Throwable
and return a ResponseInterface
. Now the code can be statically type-analysed by an IDE.
However, a caveat is that, you can’t directly pass anonymous functions into the function, instead, you must make an object implementing the interface instead. For example:
set_exception_handler( make_exception_handler( new class implements ExceptionResponseFactoryInterface { public function __invoke(Throwable $exception) : ResponseInterface { // do stuff here return new Response(); } } ) );
Or use a generic wrapper class to convert non-type-safe legacy code:
class CallableExceptionResponseFactory implements ExceptionResponseFactoryInterface { private $callable; public function __construct(callable $callable) { $this->callable = $callable; } public function __invoke(Throwable $exception) : ResponseInterface { return ($this->callable)($exception); } } set_exception_handler( make_exception_handler( new CallableExceptionResponseFactory('get_response_from_exception') ) );
By using the above method, you can do type-safe functional programming in PHP, despite not having type signatures for callables.