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:
- an interface
- an implementation considered to be the basic one
- 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:
- HTTP cache, save the Response for a given Request when possible and serve the copy until it's stale
- Content Negotiation, guess how to format the Response body based on the
Accept
header - Database transaction, with the possibility to rollback on error (or always rollback for tests)
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?