PSR-18: The HTTP client PSR

The process of creating a PSR for HTTP clients is coming to an end. I would like to encourage you to review it and raise your concerns or thoughts. But it’s obviously a real hard thing to do without knowing any background or decisions made earlier in the process. In order to ease your review I will give you the history, process and future of the current state of the HTTP client PSR.

You can read the current state of the PSR at Github.

History

A long long time ago there was some heavy development on the HTTP message PSR (PSR-7). Many saw the great potential of these HTTP message interfaces and were trying to make adapters for HTTP clients to support the new interfaces. A small team of young software developers made something they called HTTPlug. See their history in this blog post by Mark Sagi-Kazar.

The idea of HTTPlug was to create an abstraction over the HTTP client. Using an abstraction would avoid the issue with Guzzle5/Guzzle6 that the PHP community experienced in 2015 and 2016. Guzzle released 3 major versions in 18 months. Each major version had many backwards compatibility breaking changes. Because so many libraries depended on Guzzle, you had a dependency hell with many conflicting versions. With years of HTTPlug testing and trying to find a solution to the Guzzle5/Guzzle6 issue they finally had a solid foundation of interfaces for both synchronous HTTP requests and asynchronous. Let’s have a quick look at the synchronous HTTP client interface:

With such a super simple interface and the argument that depending on an implementation is wrong (the [Dependency inversion principle), it was easy to see the community adoption of HTTPlug. As the end of January 2018 the interface celebrated 2 years and 3.5 million downloads. Many major libraries are now using the interface and Symfony has accepted HTTPlug in their official repository for Flex recipes.

The small team of young software developers were now a large team of not-so-young software developers. They decided to bring HTTPlug to the PHP-FIG as a proof of concept and saying:

Look, the community think this is great and the interfaces really work. Should we make a PSR of this?

Writing the PSR

When the PHP-FIG accepted the proposal of a HTTP client PSR, the working group consisting of representatives of well-known HTTP clients began their work. It should have been really easy: just make some small modifications to HTTPlug and copy the interfaces to a new repository.

Promises to the community

One major goal of the PSR was that it should be compatible with HTTPlug. Libraries that uses the HTTPlug interface should have a real smooth upgrade path. It should be possible to execute this upgrade path in a minor version.

This seems like a real fair deal since we do not want to create a “PSR vs HTTPlug” situation similar to Guzzle 5/Guzzle6.

At the same time, we want to make it optional to use the PSR, if some libraries still want to use HTTPlug they should still be able to do so.

The issue

Everything so far has been a real dance on roses. Then someone suggested to drop support for PHP5. After some discussions on the PHP-FIG mailing list it was decided to drop PHP5 and do the interfaces with PHP7 only.

A great feature of PHP7 is return type declaration. A new PSR interface that is created for PHP7 only should naturally use return type interfaces. But that made it non compatible with HTTPlug.

Consider the signature of HTTPlug:

HttpClient::sendRequest(RequestInterface $request);

That is not compatible with the PSR

ClientInterface::sendRequest(RequestInterface $request): ResponseInterface;

The HTTPlug interface cannot extend the PSR interface as originally planned. That would have been a real smooth upgrade path for both libraries consuming HTTPlug and for libraries implementing HTTPlug.

The work on the PSR stalled here for a few months while trying to come up with a solution that kept the promise to the community.

Possible solutions

Let me list some possible solutions of the issue and highlight the drawbacks of each. The list of drawback may not be complete or accurate. So please make a comment if you think I’m wrong.

This list also reflects our thought process.

A) Rename PSR function

To rename the “sendRequest” function in the PSR interface to something like “sendHttpRequest” would be an okey solution but with an ugly name. This would require implementing libraries to create an “adapter function” that mapped one method to another. Consuming libraries would have problems supporting both the PSR and HTTPlug at the same time.

B) Let consuming libraries require both interfaces

Since the two interfaces are so similar one could without issues use both interfaces interchangeably. An implementing library just implements them both. The HTTPlug interface is fine with any return type. A consuming library could just assume any of the interfaces like:

In a time where PHP move towards more type annotations it might be weird to check the type manually like this…, and yes. It is weird.

Another problem with this solution is that it could force consuming libraries to break backwards compatibility in order to use the new PSR.

C) Create a HTTPlug version 2.0

HTTPlug could create a new major version to add the return types and be compatible with the PSR. Your first reaction may be that this is insane and it would lead to many more issues like Guzzle5/Guzze6.

This is actually quite similar to solution B. If HTTPlug was dropping PHP5 support, extend the PSR and released version 2.0.0, it will all work. A consuming library could update their dependencies to require php-http/httplug:^1.0||^2.0. No other change is required. They will still support PHP5 if they want to and they will still type hint for HttpClient.

All the existing PHP5 clients that are using HTTPlug will obviously need to release a major version to add the type hint in their sendRequest implementation. For PHP7 clients there is no issue implementing both HTTPlug 1, HTTPlug 2 and the PSR, as adding the return type declaration to the method does not violate the interface of verison 1 that is missing the type declaration.

The current state of the PSR

To have a smooth upgrade path for the community the solution C is preferred. That would mean minimal change for consuming libraries. If consuming libraries would like to release a major version they could use the PSR directly instead of indirectly via HTTPlug.

Will it be a Guzzle5/Guzzle6 issue?

No, there were quite a few things you needed to do to upgrade from Guzzle 5 to Guzzle 6. When it comes to HTTPlug 1 and HTTPlug 2 there is nothing. All libraries that are consuming HTTPlug 1 could upgrade to support HTTPlug v2 in a heartbeat.

There are two rules though:

  • Every consuming library should depend on php-http/httplug: ^1.0||^2.0
  • Every implementing library should depend on php-http/httplug: ^1.0||^2.0

Future

As the time of writing, the PSR is not finalized yet. Which means that you may have plenty of comments or suggestions. Make sure to read the meta document which shows some of our previous discussions.

Future of HTTPlug

HTTPlug will still exist and push out great features like Symfony bundles, loads of plugins packed with features and most importantly: Support for asynchronous clients.

After the acceptance of the PSR there will be some work to make the php-http clients/adapters PHP7 only and to make sure they support the PSR and HTTPlug 2.0. The HTTPlugBundle will also be updated to support PSR clients.