I am following the discussion that is happening these days on the PHP-FIG mailing list (https://groups.google.com/forum/#!msg/php-fig/vTtGxdIuBX8/KvNgEeiACgAJ).
Some interesting points emerged and I'd like to try to sum them up.
The discussion started from this proposal
interface MiddlewareInterface
{
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $out = null
);
}
The first point that was raised is that, to allow HTTP clients to be able to reuse the same middleware used by server side applications, the ServerRequestInterface
was not to be used.
The approach that comes to mind in the firt place is the following: simply rename MiddlewareInterface
to ServerMiddlewareInterface
and add another interface, call it still MiddlewareInterface
, that typehints directly on RequestInterface
.
Then we would like to say MiddlewareInterface extends ServerMiddlewareInterface
to be able to use general middleware also where a server-side one is required.
Unluckily this does not work, since in PHP, due to a limitation of the language, it is not possible to have inheritance between interfaces when the signature of a method changes its typehinting. Maybe sometime in the future it will be possible and everything will be nice, but for the moment we need to find another way (here you can find a message in the internals mailing list on a similar topic that exposes its technical problems: http://marc.info/?l=php-internals&m=142791636808421&w=2).
The next solution that come to mind is to typehint with RequestInterface
and impose to implementers to check explicitely if they need a ServerRequestInterface
and in case throw a InvalidArgumentException
(see here: https://github.com/php-fig/fig-standards/blob/ba0f5b8fc5cfbd8e1ac290bb2c05161a18d13edf/proposed/middleware.md#22-server-vs-client-requests).
The next issue that emerged was if the interface needed to use __invoke
or another method, like handle
maybe.
In my opinion there is not so much difference. The benefits of using __invoke
are mainly two:
- the interface will conform to what seems to be the emerging standard in projects like Slim, Radar, Expressive and Middleman
- it will be possible to use any
callable
and not just objects.
On the other hand, using handle
or any other normal method, will not tie the MiddlewareInterface
to a magic method and will make (maybe) more explicit the way that middleware is invoked.
The last point of the discussion that I would like to address is: which parameters should the __invoke
(or handle
) method have?
I think that nobody will object that we need a RequestInterface
(or a ServiceRequestInterface
) and that the return type of the callable
needs to be a ResponseInterface
. But there are two more parameters in the signature I wrote at the beginning... what about them?
Let's start with $next
. Should it be a parameter of the callable
? Not every PHP project has it in its middleware signature; for example, if I am correct, StackPhp
does not have it and, talking about other languages, Ruby's Rack does not have it. On the other hand many more projects do have it in their middleware signature (see for example Lumen: https://lumen.laravel.com/docs/5.2/middleware#defining-middleware).
It is in the idea of middleware itself that an application is built using layers that wrap an application. Hence a middleware needs to be able to delegate request/response processing to the inner of the application. This means that a middleware needs to contain some object/callable that represent "what comes next". The question id how to pass this to the middleware. I see three options:
- pass it in the constructor of the middleware: this would make it impossible to use callables as middlewares;
- use a setter: this would make the state of the middleware mutable and would imply that every time that the middleware is invoked we would have to check if the
next
step is actually set - pass it as a parameter of the
__invoke
method: this is the default choice for many project and the de-facto standard. It makes the dependency explicit and results in a very handy to use pattern.
The other argument that we could pass to the __invoke
function is a ResponseInterface
. Why should a middleware, if it is defined as a component that receives a request and returns a response, receive also a response as a parameter? In fact many projects (see StackPhp, Lumen and probably others) do not have such a parameter.
The main issue (I'm not saying that it could not be overcome, more on this later on) I see with this approach comes form the following example
class MyMiddleware
{
public function __invoke(RequestInterface $request, callable $next)
{
if (//some random condition) {
$response = // build a response somehow
return $response;
}
}
}
The issue here is that we can not use the $response
that we usually retrieve as a result of the $next
invocation. There are varoius way we could retrieve such a $response
:
- we could easily build it using
new
: this would lead the middleware to depend on a particular implementation ofPSR-7
. This is not very elegant because this would be somehow a hidden dependency (not expicited in the interface) and, if we use a lot middleware with this approach, we could end up with many different implementations ofPSR-7
in ourvendor
folder. - we could pass the
$response
, or aResponseFactory
that would be able to construct the response when needed, as a constructor parameter: this would not allow the use of callables (on the other hand, they could useuse
clauses to import such things in their scope). Moreover, there are some use cases in which the response is useful to store values before giving the control to the inner layers (this could be overcome using$request
's attributes). - we could pass the
$response
as a parameter of the__invoke
function: this is for now, in thePSR-7
world, the de-facto standard. It makes explicit in the interface that the middleware could have a dependency on aResponseInterface
. Moreover this allows the user to decice which implementation ofPSR-7
to use at the outside of the middleware onion, not necessarily on the inside, where the core domain is.
I have my preferences but I think that every approach has its benefits and its drawbacks. Maybe it would be nice to have multiple MiddlewareInterface
standards which could transform from one to the other.
For example, consider the following interfaces:
interface MiddlewareInterfaceWithResponse
{
public function __invoke(RequestInterface $request, ResponseInterface $response, callable $next);
}
interface MiddlewareInterfaceWithoutResponse
{
public function __invoke(RequestInterface $request, callable $next);
}
It would be nice if we could use something like the following to pass from an interface to the other
class MiddlewareTransformer
{
public static function removeResponse(
callable $middleware,
ResponseInterface $response
) {
$middlewareWithoutResponse = function ($request, $next) use ($middleware, $response) {
$nextWithResponse = function ($request, $response) use ($next) {
return call_user_func($next, $request);
};
return call_user_func($middleware, $request, $response, $next);
};
return $middlewareWithoutResponse;
}
public static function addResponse(callable $middleware)
{
$middlewareWithResponse = function ($request, $response, $next) use ($middleware) {
$nextWithoutResponse = function ($request) use ($response, $next) {
return call_user_func($next, $request, $response);
};
return call_user_func($middleware, $request, $nextWithoutResponse);
};
return $middlewareWithResponse;
}
}
I'm not sure this really works, but if it does I guess it is worth a try