Skip to content

Instantly share code, notes, and snippets.

@marcosh
Last active May 19, 2016 15:18
Show Gist options
  • Save marcosh/67462c5c04e0f190b9fc5a619c790598 to your computer and use it in GitHub Desktop.
Save marcosh/67462c5c04e0f190b9fc5a619c790598 to your computer and use it in GitHub Desktop.

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 of PSR-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 of PSR-7 in our vendor folder.
  • we could pass the $response, or a ResponseFactory 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 use use 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 the PSR-7 world, the de-facto standard. It makes explicit in the interface that the middleware could have a dependency on a ResponseInterface. Moreover this allows the user to decice which implementation of PSR-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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment