Loïc Faugeron Technical Blog

Beyond PSR-7 11/11/2015

PSR-7 is a standard providing interfaces for HTTP messages.

The main package, psr/http-message, has a few implementations but mainly it comes down to:

The standard interfaces leave little room for implementation details by forcing immutability and streams. It mainly leaves freedom in the way implementations are constructed though.

Note: Beyond that, Diactoros and Guzzle still differ slightly.

It's a pity we didn't end up directly with standard value objects, but we'll have to do with those interfaces for now. So now, what's the next step for PSR-7?

Middlewares

Middlewares have been around for a while now (in PHP as well as in other languages). The idea is to have:

  1. an interface
  2. an implementation considered to be the basic one
  3. implementations that wraps a call to another one

Let's take Symfony's HttpKernelInterface as an example:

<?php

namespace Symfony\Component\HttpKernel\HttpKernelInterface;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

interface HttpKernelInterface
{
    const MASTER_REQUEST = 1;
    const SUB_REQUEST = 2;

    // @return Response
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = false);
}

Symfony provides a HttpKernel implementation to take care of creating a Response for the given Request. Middlewares would wrap the call to HttpKernel#handle with their own logic, which could be:

Here's an example to always rollback database transactions with Pomm Foundation.

<?php

namespace Acme\Fortune\Bridge\HttpKernel;

use PommProject\Foundation\QueryManager\QueryManagerInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RollbackKernel implements HttpKernelInterface
{
    private $httpKernel;
    private $queryManager;

    public function __construct(HttpKernelInterface $httpKernel, QueryManagerInterface $queryManager)
    {
        $this->httpKernel = $httpKernel;
        $this->queryManager = $queryManager;
    }

    // @return Response
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = false)
    {
        $this->queryManager->query('START TRANSACTION');
        $response = $this->httpKernel->handle($request, $type, $catch);
        $this->queryManager->query('ROLLBACK');

        return $response;
    }
}

The Stack initiative provides visibility on those HttpKernelInterface middlewares.

Note: Stack is inspired by Ruby's rack.

PSR-7 Middelwares

So the next step would be to provide a standard interface to allow PSR-7 middlewares. Of course such a standard isn't straigthforward as there are many ways to create middlewares.

1. Take request, return response

Similar to HttpKernelInterface, this one just receives a RequestInterface and returns a ResponseInterface:

<?php

use Psr\Message\RequestInterface;
use Psr\Message\ResponseInterface;

interface RequestHandler
{
    // @return ResponseInterface
    public function handleRequest(RequestInterface $request);
}

Names can be debated, but the point here is that calling the next middleware is up to the implementation (can use dependency injection).

Httplug is a good example using this implementation.

2. Take request, response and next

Another interface commonly encountered forces the way of managing the next middleware:

<?php

use Psr\Message\RequestInterface;
use Psr\Message\ResponseInterface;

interface RequestHanler
{
    public function handleRequest(
        RequestInterface $request,
        ResponseInterface $response,
        RequestHanler next = null
    );
}

This one requires the response to be passed as an argument, as well as the next middleware.

A good example using this implementation would be Zend Stratigility

Conclusion

Middlewares are really powerful and PSR-7 would benefit greatly from it, but we're missing a standard for it. As long as it will be missing, we're going to assist to the creation of a wide range of "PSR-7 middlewares", all incompatible with each others.

Since this seems inevitable, maybe we should instead try to figure out a new standard that would allow to compose all those middlewares?

XKCD explaining how standards proliferate