An introduction to PSR-7 in Symfony

By Marek Pietrzak

The PSR-7 standard, which describes common HTTP message interfaces, is a big step towards interoperability across different PHP libraries. The standard was introduced not long ago, but you can already use libraries compatible with this recommendation within your Symfony-based application.

The origins of the PSR-7 standard  

Web applications nowadays may be very complex, but technically they all do the same thing: take a HTTP request, perform some actions, and return a HTTP response. A long time ago, a typical PHP application operated directly on global arrays such as $_GET or $_POST. Over the years, many frameworks have tried to solve the problem of normalising a request and handling a response. 

Symfony2 brought the HttpFoundation component to the community some time ago. The component, containing Request and Response classes abstracting HTTP messages, became widely used and beneficial for other big projects such as Laravel, Drupal 8, phpBB, eZ Platform, and Sylius, to name just a few. 

But the situation wasn’t perfect. There was no standard across the PHP world, with lots of different frameworks using their own implementations for dealing with HTTP. 

A step toward more homogeneity was achieved when the PHP Framework Interop Group accepted PSR-7 in May 2015. This recommendation describes common HTTP message interfaces. The biggest benefit the PHP community gets from the standard is a potential for interoperability across different PHP libraries. 

You can already use libraries compatible with this recommendation within your Symfony-based application thanks to the Symfony PSR-7 Http Message Bridge.

Symfony PSR-7 HTTP message bridge

The Symfony kernel is responsible for handling a HttpFoundation Request and returning a Response object defined in the same component.

Because of the backwards compatibility promise, the Symfony kernel won’t accept PSR-7 requests returning PSR-7 responses before Symfony 4.0.

However, just a couple weeks after PSR-7 was accepted, Symfony introduced the PSR-7 bridge 'which converts HttpFoundation objects from and to objects implementing HTTP message interfaces defined by the PSR-7'. It also provides a native support for Zend Diactoros which is a PSR-7 HTTP Message implementation. 

To convert a HttpFoundation Request to a Zend Diactoros ServerRequest implementing Psr\Http\Message\ServerRequestInterface you will need to use a DiactorosFactory:

use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Component\HttpFoundation\Request;

$symfonyRequest = Request::createFromGlobals();
$psr7Factory = new DiactorosFactory();
$psr7Request = $psr7Factory->createRequest($symfonyRequest);

Similarly, if you want to convert HttpFoundation Response to a Zend Diactoros Response implementing PSR-7 Psr\Http\Message\ResponseInterface, you will use the same factory object:

use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Component\HttpFoundation\Response;

$symfonyResponse = new Response('Hello world');

$psr7Factory = new DiactorosFactory();
$psr7Response = $psr7Factory->createResponse($symfonyResponse);

Converting the other way around, objects implementing PSR-7 interfaces to HttpFoundation, is as simple as the above: you will just need to use a HttpFoundationFactory instead.

How to use PSR-7 request and response in controller

If you want to use request and response objects, implementing PSR-7 interfaces in your controllers, you don’t have to convert HttpFoundation ones yourself. TheSensioFrameworkBundle comes with a simple event listener which does the trick for you.

PsrResponseListener checks whether the returned class implements Psr\Http\Message\ResponseInterface and uses HttpFoundationFactory to convert it to the HttpFoundation Response object.

This means that if you work with PSR-7 interfaces in your controller, you no longer have to worry about returning a HttpFoundation Response object from your method.
 
You can also typehint your method argument to the ServerRequestInterface thanks to the PsrServerRequestParamConverter.

Summarising, the controller compatible with PSR-7 interfaces, which prints `Hello world` (or `Hello Inviqa` if you pass `Inviqa` in a query string), might look like this one:

namespace AppBundle\Controller;

use Psr\Http\Message\ServerRequestInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Zend\Diactoros\Response;

class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(ServerRequestInterface $request)
    {
        $queryParams = $request->getQueryParams();
        $name = !empty($queryParams['name']) ? $queryParams['name'] : 'world';

        $response = new Response();
        $response->getBody()->write(sprintf("Hello %s", htmlspecialchars($name)));

        return $response;
    }
}

All you need to do is to install required dependencies via composer:

composer require \
    sensio/framework-extra-bundle \
    symfony/psr-http-message-bridge \
    zendframework/zend-diactoros

If you are not using the Symfony standard edition, you need to register the SensioExtraFrameworkBundle in your Kernel class:

use Symfony\Component\HttpKernel\Kernel;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            // ...
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new AppBundle\AppBundle(),
        ];

     // ...
        return $bundles;
    }
    // ...
}

Middlewares

One of the big benefits of PSR-7 is the ability to use so-called middlewares. These are single-responsibility code snippets that do something with the request, call another middleware, and eventually return a response.

However, PSR-7 doesn’t standardise exactly how middleware should look. A common approach is to provide a callable with the signature:

function(RequestInterface $request, ResponseInterface $response, callable $next = null) : ReponseInterface

or using a class:

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

class Foo
{
    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response,
        callable $next = null
    ) {
        if ($next) {
            $response = $next($request, $response);
        }
        // do something
        return $response;
    }
}

If you are interested in creating middleware applications, you might also be interested in Zend Expressive, RelayPHP or Slim3.

Conclusion

The PSR-7 standard is a step in a good direction, allowing developers to write PHP libraries that can be reused in many different projects.

The Symfony PSR-7 bridge may have some performance issues currently when dealing with large and streamed requests and responses, but it's a great starting point for the Symfony community, and the PSR-7 subject will definitely evolve in the future.