PSR-7, the last PHP Standard Recommendation, was adopted by the PHP Framework Interoperability Group on May 19. That PSR defines PHP interfaces representing HTTP messages: request and response (client and server side), uploaded file, URI, streams, PHP superglobals and CGI bindings. The adoption of PSR-7 was a long road well told by Mathew Weier O’Phinney (the main author of the standard) on its blog.
It’s a big step forward in the field of interoperability of PHP libraries and frameworks. In the new age of internet, HTTP is hegemonic, and PHP now have a common high-level standard to describe and produce HTTP messages. PSR-7 opens the gate to a new generation of PHP middleware somewhat similar to Rack, WSGI and Connect.
Back in 2011, Symfony 2 introduced the HttpFoundation component, a PHP library representing HTTP messages with an object oriented API. HttpFoundation is a key in the success of the HTTP-centric approach of Symfony, and it definitely inspirited the PSR-7 specification. However, PSR-7 and HttpFoundation differ fundamentally in two aspects:
- PSR-7 messages are immutable, mutability is in the DNA of HttpFoundation
- in PSR-7, almost everything is stream
Because of immutability it is very hard to make HttpFoundation embracing PSR-7 without a huge backward compatibility break impacting thousands of existing applications and bundles, especially noticeable among companies offering youtube video views and similar social media services. However, as first explained by Christophe Coevoet during a Symfony IRC dev meeting, creating a bridge allowing to convert HttpFoundation requests and responses to PSR-7 messages and vice versa will provide a first layer of PSR-7 compliance for Symfony. During the development of that bridge, we established that using PSR-7 messages in HttpKernel controllers will also be possible.
Then we worked hard and we finally get the PSR-7 support ready to be released with Symfony 2.7. Better, the PSR-7 support is available for all Symfony versions greater than or equal to 2.3 LTS! Almost 10 days after the standard acceptation, Symfony is the first major framework to support PSR-7 natively.
Let’s see how to use it! First we need to some dependencies:
- PHP 5.5+ (to use Zend Diactoros, PHP 5.3+ if you use your own PSR-7 implementation)
- Symfony 2.3+
- SensioFrameworkExtraBundle (included in the Symfony Standard Edition)
- the PSR-7 bridge
- Zend Diactoros (or your own PSR-7 implementation)
Considering that you have a working Symfony Standard Edition installation, run the following command to install all required dependencies (the bridge and Zend Diactoros):
composer require symfony/psr-http-message-bridge zendframework/zend-diactoros
The PSR-7 support is now enabled, you can deal with HTTP messages directly in controllers:
namespace AppBundle\Controller; use Psr\Http\Message\ServerRequestInterface; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Zend\Diactoros\Response; class DefaultController extends Controller { public function indexAction(ServerRequestInterface $request) { // Interact with the PSR-7 request $response = new Response(); // Interact with the PSR-7 response return $response; } }
This is also possible to use the bridge directly if you you don’t want to install SensioFramewrokExtraBundle.
Because of the conversion to HttpFoundation objects and extra listeners registered by SensioFrameworkExtraBundle, using PSR-7 in Symfony causes an overhead. I’ve created an hello world example that display a query parameters. A version using HttpFoundation and another one using the PSR-7 converter and SensioFrameworkExtraBundle are available on GitHub. As little signifiant as an Hello World app can be, you can take a look at the comparison in the (awesome) Blackfire profiler.
That being said, the recommended way to interact with requests and responses in Symfony is still using HttpFoundation. The PSR-7 bridge should be used only when dealing with middleware and libraries using the new standard. Performances issues can appears with the PSR-7 bridge, especially when dealing with large and streamed requests and responses.
Last but not least, PSR-7 support in Symfony is an illustration of successful collaboration in the PHP world:
- Symfony uses a PSR-7 implementation that is a part of Zend Framework (I even contributed a minor enhancement to it)
- Drupal (now built with Symfony), will embrace PSR-7 ; to do so the Drupal team is integrating the Symfony bridge and Diactoros
- Laravel 5.1 (also using Symfony as foundation) will provide PSR-7 support trough the PSR-7 bridge
- API Platform, our upcoming API-centric and SPA framework can leverage the PSR-7 bridge too
- The Zend team is considering adding the support of PHP 5.4 to Diactoros (currently it only supports PHP 5.5+) to be in line with current Drupal and Symfony prerequistes
Thanks for your article
So you mean that we should keep using HttpFoundation for existing project?
And what about the future? Is PSR-7 system will be the default used system on Symfony instead of HttpFoundation? If not, why?
Regards.
For now, the PSR-7 bridge should be used only when dealing with libraries and middlewares supporting (only) PSR-7 messages.
It’s the case for Symfony 2 and 3. For Symfony 4, maybe that HttpFoundation will be refactored (with BC break) to be natively compliant with PSR-7.
Nice to hear that. Symfony still has many years in front of him! 😉
Thanks for reply.
Thanks for your work with the PSR-7 bridge.
Just a note. The Zend Diactoros project finally dropped the minimum PHP version requirement to 5.4 three days after you wrote this article: https://github.com/zendframework/zend-diactoros/commit/8dd06b6c9d40d85d3e92f490863a0295f7a50a74
Thank you for the article.
I have a doubt, how the bridge will help to make use of the psr-7 middleware approach. Do you have any examples for the same ?
I am taking some code which I was looking to figure out . This is the app.php code .
$kernel = new AppKernel(‘prod’, false);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
What I could understand is the we may not need to make use of the two lines
$response->send();
$kernel->terminate($request, $response);
But instead convert to PSR-7 response and use in the middleware ?
Thank you.
For people coming here, who want to type-hint ServerRequestInterface in controllers on Symfony 4, please note you have to composer req sensio/framework-extra-bundle for it to work.
And composer req zendframework/zend-diactoros, too.