What is middleware?

In the realm of web application development Middleware refers to functions wrapped around business logic. They rely upon the decorator pattern and are composed in a sort of layered stack. You could think of them as onion layers, with your business logic residing at the innermost stratum (more on Wikipedia).

Several modern PHP frameworks (such as Zend Expressive and Slim) embrace such framework-agnostic, reuse-oriented paradigm, wholly supporting PSR-7 compliant middleware.

Goal for PHP Middleworld is collecting all available PSR-7 compliant middleware and provide a unique, consistent repository, encouraging middleware reuse and exchange among PHP developers.

How does it work?

Middleware layers take an Http request as input and return an Http response as output. They act as a sort of filter during the request/response lifecycle of a web application. As a request is received, the middleware stack is traversed. Each middleware layer is then executed before the request reaches the designated business logic snippet, associated to the invoked route. Also, the middleware stack is traversed backwards after business logic has completed its job and before content is sent back to the client.

A small example

Slide 50

To get a better grasp on how middleware works, let's take an airplane trip as an example. In this context, there's no doubt that our core action is the flight itself (= the core action of our business logic). Before and after the flight takes place, though, we usually need to have our documents checked, drop and collect our luggages, board and alight the plane. If this context was a web application, we'd perform all of these actions through middleware functions. We'd have middleware for each of the steps, before and after the flight itself.

The nice part is that all other flights could take advantage of existing procedures, and there'd be no need to re-invent the wheel everytime, creating such procedures for every other flight. In the realm of web applications, all of these cross cutting concerns would be taken care by middleware.

How does middleware look like?

Middleware exploits PSR-7 Http abstractions and allows for reuse across framworks. The signature used by most middleware implementation is commonly known as Double Pass. An example of such a middleware (performing the doSomethingOnRequest action on the request, and the doSomethingOnResponse action on response) is described below:

class Middleware
{
    function __invoke(
        RequestInterface $request,
        ResponseInterface $response,
        callable $next
    ): ResponseInterface
    {
        if (!$this->preconditionsExist($request, $response)) {
            throw new RuntimeException();
        }

        $request = $this->doSomethingOnRequest($request);

        $response = $next($request, $response);

        return $this->doSomethingOnResponse($response);
    }
}

The request and response parameters (obviously) represent the request and response objects, whereas the next parameter is a way through which each framework allows middleware to locate and invoke next middleware layer in the stack.

It's worth noticing here that such next parameter does not represent a piece of middleware (see signature difference), but rather a way for the middleware to have the framework locating and providing the next appropriate piece of middleware in the stack.

More recently a new approach to middleware, known as Single pass has been proposed by the PHP-FIG and is on its way to become the PSR-15 standard. The above middleware would look like this as a Single pass middleware:

class Middleware
{
    function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ): ResponseInterface
    {
        if (!$this->preconditionsExist($request)) {
            throw new RuntimeException();
        }

        $request = $this->doSomethingOnRequest($request);

        $response = $delegate->process($request);

        return $this->doSomethingOnResponse($response);
    }
}

The main difference from the previous approach is that here middleware receives only two parameters, a server request and a delegate which receives the request to dispatch next middleware in the pipeline. In this form middleware has no access to a response until one is generated by innermost middleware. Middleware can then modify the response before returning back up the stack.

There are also other subtle differences. The request must be a server request and therefore this approach is not suitable for client applications. Moreover, the used method is no more __invoke but process instead; this means that callables are not allowed and middleware must be composed only from objects.