[PSR-7] Do different implementations truly exist?

572 views
Skip to first unread message

Daniel Plainview

unread,
Jun 20, 2016, 4:47:21 PM6/20/16
to PHP Framework Interoperability Group
Hello,

The one thing in PSR-7 still bothers me: do different implementations truly exist?
Or in other words: how these implementations may differ in terms of behaviour?
If they don't really exist, so why PSR-7 doesn't provide Request/Response classes?

I'll try to bring my point through imaginary dialog.

— You don't like interfaces? Program to an interface, not an implementation, Luke!

PSR-7 provides the set of interfaces for HTTP-messages.
Yes, "program to an interface, not an implementation" is the first thought.
But sometimes different implementations don't make much sense.
Usually, one-to-one interfaces make no sense for value objects and entities.
If value object behaves differently in the same use cases, you probably work with different concepts.

You always expect that DateTime('2000/12/31') + 1 day gives us DateTime('2001/01/01').
(if you work with Gregorian Calendar of course).
Behaviour must be the same in all possible implementations.

However, in context of PSR-7, I see some cases when behaviour really may differ.
For example:
> UriInterface::getAuthority()
> If the port component is not set or is the standard port for the current
> scheme, it SHOULD NOT be included.

It means that if I write
echo $uri->withScheme('http')->withPort(80)->getAuthority(), PHP_EOL;
it is possible to see different outputs: with port or without it.

But the difference above is very minor.
I doubt someone chooses a library just because it hides port or not.
I also tried to find another good examples of different behaviour, but I failed.

— OK, what about existing libraries? Guzzle, HttpFoundation, etc. They can attach our shiny interface to existing VOs, as simple as that.

Sounds nice in theory, but in practice is not as simple. 
As far as I can see, most of popular libraries made new components to work with PSR-7.
For example:
https://github.com/symfony/psr-http-message-bridge (uses zend-diactoros, but anyway)

— PSR-FIG doesn't provide ready-to-use components anyway. We are not a framework.

From my point of view, it sounds like excuse for comment-driven development.
You did great work with specification.
However, probably every statement from spec can be programmed very unambiguously.
If you compare existing implementations, they looks suspiciously similar. 
Let's compare ResponseInterface::withoutHeader() from ZF and from Guzzle:
// ZF
public function withoutHeader($header)
{
    if (! $this->hasHeader($header)) {
        return clone $this;
    }
    $normalized = strtolower($header);
    $original   = $this->headerNames[$normalized];
    $new = clone $this;
    unset($new->headers[$original], $new->headerNames[$normalized]);
    return $new;
}

// Guzzle
public function withoutHeader($header)
{
    $normalized = strtolower($header);
    if (!isset($this->headerNames[$normalized])) {
        return $this;
    }
    $header = $this->headerNames[$normalized];
    $new = clone $this;
    unset($new->headers[$header], $new->headerNames[$normalized]);
    return $new;
}

Oh my god, someone copy pasted code from another implementation?
Probably, yes.
Probably, no.
Because it's hard to imagine *different* implementation.
They will probably differ in terms of variable names, some equivalent expressions, etc...

— Implementations may depend on some low-level details like PCRE extension (preg_match), etc. 

That's true. However, some dependencies are so widespread, so you can ignore this fact.

---

After all, this new PSR with factories for HTTP-messages wouldn't be needed.
It would make impossible to have incompatible implementations.
And it makes many other things much simpler.
Do I miss something?

Matthew Weier O'Phinney

unread,
Jun 20, 2016, 11:59:13 PM6/20/16
to php...@googlegroups.com


On Jun 20, 2016 3:47 PM, "Daniel Plainview" <daniel...@gmail.com> wrote:
>
> Hello,
>
> The one thing in PSR-7 still bothers me: do different implementations truly exist?

Of the top of my head: Diactoros, Guzzle, and Slim, and I've read of others working on additional implementations.

> Or in other words: how these implementations may differ in terms of behaviour?

Guzzle has traditionally focused on client behavior, and has optimizations around memory usage and generator driven streams; streams are its particular strength. Diactoros and Slim focus on server side details, with the former being a full, general purpose implementation, and the latter only implementing the server request and response (IIRC).

league/uri provides an implementation of the UriInterface only, and is the most thorough implementation I've seen.

Each is addressing particular elements that are of specific interest to their projects and intended use cases. The basic functionality is the same, but some features and approaches differ.

> If they don't really exist, so why PSR-7 doesn't provide Request/Response classes?
>
> I'll try to bring my point through imaginary dialog.
>
> — You don't like interfaces? Program to an interface, not an implementation, Luke!
>
> PSR-7 provides the set of interfaces for HTTP-messages.
> Yes, "program to an interface, not an implementation" is the first thought.
> But sometimes different implementations don't make much sense.
> Usually, one-to-one interfaces make no sense for value objects and entities.
> If value object behaves differently in the same use cases, you probably work with different concepts.

Where I'm seeing differentiation is around convenience features. As an example, specialized response types for json, html, XML, or even callable or iterator streams. Additionally, populating a server request typically requires a factory, and that detail will change based on whether you're using an SAPI or an async server.

So, yes, there's definitely room for multiple implementations, even when programming to an interface.

>
> You always expect that DateTime('2000/12/31') + 1 day gives us DateTime('2001/01/01').
> (if you work with Gregorian Calendar of course).
> Behaviour must be the same in all possible implementations.
>
> However, in context of PSR-7, I see some cases when behaviour really may differ.
> For example:
> > UriInterface::getAuthority()
> > If the port component is not set or is the standard port for the current
> > scheme, it SHOULD NOT be included.
>
> It means that if I write
> echo $uri->withScheme('http')->withPort(80)->getAuthority(), PHP_EOL;
> it is possible to see different outputs: with port or without it.
>
> But the difference above is very minor.
> I doubt someone chooses a library just because it hides port or not.

You'd be surprised. :-)

> I also tried to find another good examples of different behaviour, but I failed.
>
> — OK, what about existing libraries? Guzzle, HttpFoundation, etc. They can attach our shiny interface to existing VOs, as simple as that.
>
> Sounds nice in theory, but in practice is not as simple. 
> As far as I can see, most of popular libraries made new components to work with PSR-7.
> For example:
> https://github.com/zendframework/zend-http/tree/master/src vs https://github.com/zendframework/zend-diactoros
> https://github.com/guzzle/psr7
> https://github.com/symfony/psr-http-message-bridge (uses zend-diactoros, but anyway)

This is not s valid comparison. Each of these projects predates psr-7, and adding psr-7 support cannot be done without breaking backwards compatibility. As such, they each have multiple supported versions, targeting the different APIs, or offer a parallel project, or are doing a stepped migration. You can't erase history, or force an upgrade overnight.

> — PSR-FIG doesn't provide ready-to-use components anyway. We are not a framework.
>
> From my point of view, it sounds like excuse for comment-driven development.
> You did great work with specification.
> However, probably every statement from spec can be programmed very unambiguously.

The key word here is "probably". Considering we have multiple implementations that do differ, and which are now generating errata due to differences observed, I disagree with your assertion.

--
Matthew Weier O'Phinney
https://mwop.net

Daniel Plainview

unread,
Jun 21, 2016, 4:15:59 AM6/21/16
to PHP Framework Interoperability Group
> Guzzle has traditionally focused on client behavior, and has optimizations around memory usage and generator driven streams; streams are its particular strength. Diactoros and Slim focus on server side details, with the former being a full, general purpose implementation, and the latter only implementing the server request and response (IIRC).

It means that they are not fully compatible. Guzzle allows arbitrary header name, for example.
It's harder to trust another implementation if they are behaves differently like *this*.
Also it's not clear for me that Guzzle ignores validation on purpose. Maybe it's a bug. :)

> Where I'm seeing differentiation is around convenience features. As an example, specialized response types for json, html, XML, or even callable or iterator streams. Additionally, populating a server request typically requires a factory, and that detail will change based on whether you're using an SAPI or an async server.

It's like saying that 
final FryGotFrozenAt extends \DateTime
{
    public function __construct() {
        parent::__construct('1999/12/31 18:00:00 UTC');
    }
}

is different implementation of DateTime.
But I think it's not concern of Request/Response objects to work with different formats.
Sooner or later you may come to request/response-as-a-service with DI dependencies, configuration, etc.
I don't this that having XmlResponse is an advantage, I see the opposite.
You can fill your request/response objects in different layers.

> This is not s valid comparison. Each of these projects predates psr-7, and adding psr-7 support cannot be done without breaking backwards compatibility.
Exactly what I tried to say. Almost all existing (pre-PSR-7) libraries are not compatible; every new future implementation will be very similar to other ones.

> Considering we have multiple implementations that do differ, and which are now generating errata due to differences observed, I disagree with your assertion.

Of course you will see differences, but the question is if these differences are "true" and "valid"?
They differ in terms of lack of validation (if you mean that case with withHost()).
It reveals design issue: they shouldn't differ here; they should behave the same.

Matthieu Napoli

unread,
Jun 23, 2016, 5:37:53 PM6/23/16
to PHP Framework Interoperability Group
Hi Daniel,

I think this is an interesting question, for reference here is a thread last month about it: https://groups.google.com/forum/#!topic/php-fig/u2Nmovw_Rlc

If there are specific reasons for multiple implementations then maybe it would make sense to collect them in PSR-7's meta-document? That way it will be documented. If there are no reasons, then maybe it's worth discussing standardizing an implementation?

Matthieu

Larry Garfield

unread,
Jun 23, 2016, 6:41:34 PM6/23/16
to php...@googlegroups.com
On 06/23/2016 04:37 PM, Matthieu Napoli wrote:
> Hi Daniel,
>
> I think this is an interesting question, for reference here is a
> thread last month about
> it: https://groups.google.com/forum/#!topic/php-fig/u2Nmovw_Rlc
>
> If there are specific reasons for multiple implementations then maybe
> it would make sense to collect them in PSR-7's meta-document? That way
> it will be documented. If there are no reasons, then maybe it's worth
> discussing standardizing an implementation?
>
> Matthieu

This sounds like an excellent use of the metadocument, especially if we
can cite differences in known implementations now.

--Larry Garfield

Daniel Plainview

unread,
Jun 24, 2016, 4:39:55 PM6/24/16
to PHP Framework Interoperability Group
Hi,

I'm glad to see that you raised similar questions.

It definitely makes sense to make some research on this topic and find out differences in most popular implementations.

Just another my 2 cents...

If there are multiple implementations with different validation and all these implementations strictly respect PSR-7, then PSR-7 has "white spots".
In makes things more implicit, harder to replace one implementation with another one.

If there are multiple implementations, but they are not strictly respect PSR-7, then they violate the Liskov substitution principle.
There is no excuse for such violations even for "performance reasons" and "memory usage".
After all, it's not hard to make official PHP extension.

Daniel Plainview

unread,
Jun 24, 2016, 4:41:32 PM6/24/16
to PHP Framework Interoperability Group
Sorry for typos, btw. 
These mailing lists, eh...

Daniel Plainview

unread,
Jun 26, 2016, 4:13:47 PM6/26/16
to PHP Framework Interoperability Group
> Guzzle has traditionally focused on client behavior, ... generator driven streams; streams are its particular strength

Stream implementation is definitely out of scope for FIG, it has nothing to do with this topic.

On Tuesday, June 21, 2016 at 6:59:13 AM UTC+3, Matthew Weier O'Phinney wrote:

Christopher Pitt

unread,
Jun 26, 2016, 4:32:37 PM6/26/16
to PHP Framework Interoperability Group
Stream implementation is definitely out of scope for FIG, it has nothing to do with this topic.
 
It is of particular interest to the async interoperability work going on. Perhaps we'll get a specification/implementation out of there...

Jonathan Eskew

unread,
Jun 27, 2016, 1:43:11 PM6/27/16
to PHP Framework Interoperability Group


On Sunday, June 26, 2016 at 1:32:37 PM UTC-7, Christopher Pitt wrote:
Stream implementation is definitely out of scope for FIG, it has nothing to do with this topic.
 
It is of particular interest to the async interoperability work going on. Perhaps we'll get a specification/implementation out of there...

PSR-7 defines an interface for streams, and Guzzle offers a suite of implementations (as detailed at https://github.com/guzzle/psr7#stream-implementation). I'd say that the variety of stream implementations is one of the reasons someone would opt to use Guzzle's PSR-7 library instead of another, so it seems to directly answer the original poster's question. Not sure how it is off topic.

Daniel Plainview

unread,
Jun 27, 2016, 2:12:55 PM6/27/16
to PHP Framework Interoperability Group
> I'd say that the variety of stream implementations is one of the reasons someone would opt to use Guzzle's PSR-7 library instead of another, so it seems to directly answer the original poster's question

No, it's not.

Please, pay attention that I talk about *value objects* Request and Response.

Stream is not a value object, it's more like a service.
Stream may have external dependencies, different implementations. 
Having interface for the Steam is perfectly fine.
This part of PSR (streams) is the space for different implementations of course.

However, value objects are different story.
They shouldn't have external dependencies.
They are not services.
You don't want to have different implementations of DateTime, DateInterval, DateTimeZone, etc.

Jonathan Eskew

unread,
Jun 27, 2016, 3:19:21 PM6/27/16
to PHP Framework Interoperability Group
PHP provides a DateTimeInterface and multiple implementations thereof, so some programmers *do* want different implementations of DateTime. Streams are defined as a big part of PSR-7, so I'm not sure why they shouldn't matter to an implementing library. Both requests and responses have their bodies defined as being instances of Psr\Http\Message\StreamInterface.

There's no reason a library couldn't do something similar with request/response objects and offer a few implementations. I'm not sure if any PSR-7 libraries have done so, but Symfony's HttpFoundation provided a few subclasses of Response for common use cases (like BinaryFileResponse, JsonResponse, and RedirectResponse). A PSR-7 library might choose to implement something similar.

Daniel Plainview

unread,
Jun 27, 2016, 3:27:40 PM6/27/16
to PHP Framework Interoperability Group
> PHP provides a DateTimeInterface and multiple implementations thereof, so some programmers *do* want different implementations of DateTime

That's not true.
PHP provides DateTimeInterface as a *hack*, because DateTime has a huge design mistake and can't be fixed without breaking BC: https://derickrethans.nl/immutable-datetime.html
You also cannot implement your own implementation of the DateTimeInterface, because it's not real interface (feel free to try).
I repeat, it's just a hack for making possible to typing against both implementations, but mutable DateTime was a mistake.

> Streams are defined as a big part of PSR-7, so I'm not sure why they shouldn't matter to an implementing library.

I can't understand your point. As I said, I having nothing against different stream implementations.

> but Symfony's HttpFoundation provided a few subclasses of Response for common use cases (like BinaryFileResponse, JsonResponse, and RedirectResponse)

This is just syntactic sugar. You always can replace it with middleware.

Daniel Plainview

unread,
Jun 27, 2016, 3:29:47 PM6/27/16
to PHP Framework Interoperability Group
I'm sorry for these typos, I have to read my own message several times before posting.
Reply all
Reply to author
Forward
0 new messages