PSR-7 middleware

2,736 views
Skip to first unread message

Rasmus Schultz

unread,
May 9, 2016, 4:00:38 AM5/9/16
to PHP Framework Interoperability Group
Dear FIG,

People often talk about "PSR-7 middleware", by which they mean middleware components designed to work with the PSR-7 Request/Response abstractions.

The problem is, PSR-7 does not define any interface for middleware, as such.

Thus, more or less every framework currently defines their own variation of (essenatially) this interface:


Sadly, that does not give us true interoperability at the middleware level - although it kinda sorta does, as most middleware stacks do not rely on type-checking this interface, and instead rely on developers to implement __invoke() or anonymous closure signatures correctly, which seems shaky.

What would it take to have this interface added to PSR-7?

Is amending a PSR even a thing? (It would not be a breaking change.)

Of course, we could propose a new PSR just for this, but people already talk of "PSR-7 middleware", and it's often hard to grok what they mean. If we add a proper interface for them to work with, "PSR-7 middleware" would actually be a thing :-)

Thoughts?

- Rasmus

Paul Jones

unread,
May 9, 2016, 9:01:54 AM5/9/16
to php...@googlegroups.com

> On May 9, 2016, at 03:00, Rasmus Schultz <ras...@mindplay.dk> wrote:
>
> Is amending a PSR even a thing? (It would not be a breaking change.)

It is, but my guess is this would be outside the scope of amendment, and require a new PSR.

> Of course, we could propose a new PSR just for this, but people already talk of "PSR-7 middleware", and it's often hard to grok what they mean. If we add a proper interface for them to work with, "PSR-7 middleware" would actually be a thing :-)

I'd say a new PSR would be needed. Calling something "PSR-{$next} compliant middleware" should be sufficient.

Glad to see someone talking about actual productive work, rather than parliamentary process.


--

Paul M. Jones
http://paul-m-jones.com



Woody Gilk

unread,
May 9, 2016, 4:39:37 PM5/9/16
to PHP Framework Interoperability Group
Seems like this is a shoo-in for implementation. Will someone volunteer to start the process?



--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/6E0D42E3-F11E-4F8F-A58B-A2F9917469B5%40gmail.com.
For more options, visit https://groups.google.com/d/optout.

Paul Jones

unread,
May 9, 2016, 6:13:06 PM5/9/16
to php...@googlegroups.com

> On May 9, 2016, at 15:38, Woody Gilk <woody...@gmail.com> wrote:
>
> Seems like this is a shoo-in for implementation.

One hopes.


> Will someone volunteer to start the process?

I'm talking about it with Rasmus even now.

Marco Perone

unread,
May 10, 2016, 2:41:25 AM5/10/16
to PHP Framework Interoperability Group
Some time ago there was some discussion on the same topic (https://groups.google.com/forum/#!searchin/php-fig/http$20handling/php-fig/Ew36Ng5EwXE/52dAzZAbAQAJ). That could be a starting point

Dracony

unread,
May 10, 2016, 3:04:15 AM5/10/16
to PHP Framework Interoperability Group
I'm really not a fan of interfacing __invoke and would like an actual method like handle() or process() instead.

The only upside of using __invoke is that you can pass a callback instead of an actual interface implementation and the result would still work. However if the Functional Interfaces RFC passes (which would be amazing btw: https://wiki.php.net/rfc/functional-interfaces ) the __invoke approach will be no longer needed, since we'll be able to implement interfaces with callbacks anyway.



On Monday, May 9, 2016 at 10:00:38 AM UTC+2, Rasmus Schultz wrote:

Stefano Torresi

unread,
May 10, 2016, 7:15:53 AM5/10/16
to php...@googlegroups.com
Let me ask something:


Il giorno mar 10 mag 2016 alle ore 09:04 Dracony <draco...@gmail.com> ha scritto:
I'm really not a fan of interfacing __invoke and would like an actual method like handle() or process() instead.

What are the upsides of an actual method vs __invoke() ?
 
The only upside of using __invoke is that you can pass a callback instead of an actual interface implementation and the result would still work.

What are the downsides of __invoke() then?

FWIW I have worked with Stratigility and ExpressJS, and the anonymous ($req, $res, callable $next = null) signature (also used by Relay) is staple, widely accepted, and without any apparent issue whatsoever.
This would also result in a standard of which two major vendors are already compliant.

Are there any compelling reasons against it, other than "meh, don't like"?

Dracony

unread,
May 10, 2016, 7:28:25 AM5/10/16
to PHP Framework Interoperability Group
Taking up a magic method somewhat limits the developers choice, since maybe they would like to use __invoke differently. With specific methods you're more safe there will be no collision when implementing multiple interfaces. The other thing is that __invoke doesn't really tell you what the method is doing and makes it clunkier to add other methods later on because of naming:

Imagine you had a method add($number), then you can also add a method addOne() which looks consistent.
Now if you used __invoke to add a number, you can't have __invokeOne, you still have to call the method addOne, which makes the __invoke one look weird.

The only upside of having __invoke is slight reduction in characters required to call the middleware, but the $middleware($request, $response) syntax might look weird to somebody who would expect $middleware->run($request, $response).

On Monday, May 9, 2016 at 10:00:38 AM UTC+2, Rasmus Schultz wrote:

Josh Di Fabio

unread,
May 10, 2016, 7:32:06 AM5/10/16
to php...@googlegroups.com
One argument for having an interface with a named method is that code becomes more explicit:

$middleware->handleRequest($request, $response);

vs

$middleware($request, $response);

Woody Gilk

unread,
May 10, 2016, 9:14:03 AM5/10/16
to PHP Framework Interoperability Group
How does that make the code more explicit? Not using __invoke makes using a closure impossible. To say that every framework that has already implemented this interface is wrong doesn't make any sense to me.

I've opened a PR to propose this interface for inclusion in FIG: https://github.com/php-fig/fig-standards/pull/755

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Dracony

unread,
May 10, 2016, 9:25:27 AM5/10/16
to PHP Framework Interoperability Group
Not impossible if the Functional Interfaces RFC gets through, which would be best.

And actually keeping the __invoke way just because there are people already using it, is not really convincing. I mean, PSR-7 came up with an interface nobody wsas using and people were forced to change. Nothing bad came out of it. 

Josh Di Fabio

unread,
May 10, 2016, 9:42:36 AM5/10/16
to php...@googlegroups.com
Functional interfaces are more explicit in the sense that the code describes what is happening, whereas __invoke doesn't. Without the functional interfaces RFC, which is currently under discussion, I agree that __invoke is probably a better approach, but I think it makes sense to wait for that RFC to undergo a vote before deciding which approach to take, even though functional interfaces would presumably be a PHP7-only feature.

Oscar Otero

unread,
May 10, 2016, 12:05:30 PM5/10/16
to php...@googlegroups.com
I like this proposal and I’m agree with using __invoke because it’s more flexible and allows to use callables for simple purposes.

I’d like also to take the opportunity to talk about an issue I found in the psr-7 middleware.

Currently, psr-7 includes two types of request: RequestInterface and ServerRequestInterface. The first one should be used for client side and the second one for server side.
The initial proposal of Woody Gilk (https://github.com/php-fig/fig-standards/pull/755) uses ServerRequestInterface so this interface cannot be used in client side applications. There are many middleware pieces that could be used in both sides, no matter whether the request is send as a client or a server, for example, logging, minify, cache, etc. There’s a discussion about this in relayphp: https://github.com/relayphp/Relay.Relay/issues/5#issuecomment-112548669

In other hand, some middleware pieces require to save/retrieve attributes, only available in ServerRequestInterface. This means that some middlewares that could be used in both contexts (client and server side), only can be used in server side due the lack of attributes in client side. (maybe this is an issue of psr-7, not allowing to store attributes in RequestInterface instances?)

So a possible solution should be to have two interfaces, a MiddlewareInterface (using a RequestInterface) and a ServerMiddlewareInterface (using the ServerRequestInterface). But I don’t like it because the first one cannot be extended by the second one (as it happens with request and serverRequest) so type checking is complicated.

I don’t know what can be the solution (or maybe you don’t see a problem here).

Regards
Oscar Otero.



Paul Jones

unread,
May 10, 2016, 12:23:13 PM5/10/16
to php...@googlegroups.com

> On May 10, 2016, at 11:05, Oscar Otero <oscar...@gmail.com> wrote:
>
> I don’t know what can be the solution (or maybe you don’t see a problem here).

I've said elsewhere that one solution might be two interfaces: a ClientMiddleware that typehints on RequestInterface, and a ServerMiddleware that typehints on ServerRequestInterface. (That's the current plan for Relay 2.x, anyway.)

Woody Gilk

unread,
May 10, 2016, 12:24:27 PM5/10/16
to PHP Framework Interoperability Group
Oscar,

I'm not entirely sure what you mean by a "client side application". Can you provide an example where a ServerRequest would not be the appropriate type? I'm not really sure what scenario middleware would be used in that wasn't a server.

Oscar Otero

unread,
May 10, 2016, 12:32:11 PM5/10/16
to php...@googlegroups.com
Any app that send a http request to other server and get a response. For example to consume an API, download files, etc. In this case we don’t need a ServerRequest. And we may want to use a middleware to save the responses in files, loggin, strip the trailing slash of the request’s uri, etc…
The middleware logic is the same than server side apps, the only difference is in we are using curl, guzzle or any other similar library instead routers and controllers.

Woody Gilk

unread,
May 10, 2016, 12:35:08 PM5/10/16
to PHP Framework Interoperability Group
Ah, so something like a Guzzle middleware stack that (for example) caches responses to an external API?

Woody Gilk

unread,
May 10, 2016, 12:37:48 PM5/10/16
to PHP Framework Interoperability Group
I see two possibilities:

1. Use a different namespace, with the same interface name: Psr\Http\ServerMiddleware vs Psr\Http\ClientMiddleware
2. Change the Request requirement to be non-server and add a new method call that determines if the middleware requires a ServerRequest, something like: isServerOnly()

Thoughts?

Oscar Otero

unread,
May 10, 2016, 1:04:43 PM5/10/16
to php...@googlegroups.com
The problem I see with the type checking is the following:
Let’s say we have a middleware dispatcher library with the following interface:

<?php
Psr\Http\ServerMiddlewareInterface;

interface MiddlewareDispatcher
{
    public function add(ServerMiddlewareInterface $middleware);
}

This interface cannot accept also ClientMiddlewareInterface instances, because ServerMiddlewareInterface cannot extend ClientMiddlewareInterface due both interfaces use the same method (__invoke). So we are creating two different incompatible worlds.

The second possibility solves this problem, but it does not allow to use closures.



Woody Gilk

unread,
May 10, 2016, 1:13:31 PM5/10/16
to PHP Framework Interoperability Group
Alright, then I would suggest that the interface be as flexible as possible and that any middleware the requires a ServerRequest do something like:


public function __invoke(...)
{
    $this->ensureServerRequest($request);
}

private function ensureServerRequest(ServerRequestInterface $request)
{
    return $request;
}


In this manner the interface can be usable for both client and server requests, and PHP runtime checking will ensure that a ServerRequest is used when appropriate. The ensureServerRequest() method could be provided by a trait in the PSR package, which would make it really easy:


class AcmeServerMiddleware implements MiddlewareInterface
{
    use EnsureServerRequestTrait;

    // __invoke will call ensureServerRequest($request) as necessary
}


Would that be an acceptable compromise?

Roman Tsjupa

unread,
May 10, 2016, 1:19:08 PM5/10/16
to PHP FIG

Exactly the problem I described earlier, there is only one invoke() and that limits you. While with normal methods you coukd have handleRequest and handleServerRequest() methods without conflict

You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/vTtGxdIuBX8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Woody Gilk

unread,
May 10, 2016, 1:22:53 PM5/10/16
to PHP Framework Interoperability Group
Roman, having two methods does not "resolve" the situation.

Last time I checked PHPixie didn't even have middleware, so what grounds are you basing your opinions on?

Oscar Otero

unread,
May 10, 2016, 1:36:21 PM5/10/16
to php...@googlegroups.com
I like this approach. Maybe throwing a RequiredServerRequestException ?


Roman Tsjupa

unread,
May 10, 2016, 1:37:54 PM5/10/16
to PHP FIG

It gas middleware embodied in processors. That allows for a declarative approach to request processing. Having separate methods allows both methids to be sarisfied and look coherently imo

You received this message because you are subscribed to a topic in the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/php-fig/vTtGxdIuBX8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Woody Gilk

unread,
May 10, 2016, 1:39:31 PM5/10/16
to PHP Framework Interoperability Group
In the example I provided, PHP would throw a type error if a Request was used instead of a ServerRequest. The proposed method could also just be anonymous and throw an exception:


private function ensureServerRequest(RequestInterface $request)
{
    if (($request instanceof ServerRequestInterface) === false) {
        throw RequiredServerRequestException();
    }
}

Tristan Darricau

unread,
May 10, 2016, 1:49:51 PM5/10/16
to PHP FIG

About the initial proposal I don't see the point of adding an interface with _invoke(). When I'm using _invoke() it's to be able to pass a closure. But when I'm introducing an interface it's to type hint against it, which prevent me from using a Closure if I'm not using php 7.1 or an higher version...


Rasmus Schultz

unread,
May 10, 2016, 2:32:29 PM5/10/16
to php...@googlegroups.com
Just catching up on all these emails, and I'm going to
comment on several issues brought up since my original post.

On the discussion about __invoke() vs a named method - this
is a radical departure from what anyone (framework or
middleware vendors) are doing now. Effectively, nothing will
work out of the box. It also breaks the very common pattern
where one simply passes a closure.

The upshot of course is strict, checkable middleware
signatures. And we don't need to wait for functional
interfaces for that either, it's possible now, with
anonymous classes, in PHP 7:

interface MiddlewareDelegateInterface {
public function run(
RequestInterface $request,
ResponseInterface $response,
MiddlewareDelegate $next
) : ResponseInterface;
}

interface MiddlewareInterface {
public function run(
RequestInterface $request,
ResponseInterface $response,
MiddlewareDelegate $next
) : ResponseInterface;
}

class Dispatcher {
// ...

public function push(MiddlewareInterface $middleware) {
// ...
}
}

$dispatcher = new Dispatcher();

$dispatcher->push(new class () implements MiddlewareInterface {
public function run(
RequestInterface $request,
ResponseInterface $response,
MiddlewareDelegate $next
) : ResponseInterface;
{
// ...
}
});

Due to the sheer verbosity and limited gain, I don't think
I'm in favor.

I'm not 100% sure, because I generally am in favor of
anything that is type-safe and supports static analysis,
provides IDE support, etc. - but it seems like a pricey
trade-off, and everyone seems pretty happy with the
widely-used approach to middleware and middleware stacks
that is already in popular. It hasn't caused me any real
problems.

So I think I'm in favor of standardizing on something that
is closer to what the community already large agrees upon,
which can be implemented in very little time, in some cases
by simply proofreading existing code and asserting that it
already complies, and/or swapping out a userland interface
for the PSR interface.

The argument about occupying the magic `__invoke()` method
is somewhat esoteric to me - if you're writing a middleware
component, it seems pretty unlikely your class is going to
have other responsibilities that require it to implement
`__invoke()` for other reasons. I agree with Woody on this
one - frameworks are already succeeding with that approach,
and the anonymous classes approach would be lumpy and
precludes any project targeting PHP < 7, which likely just
means it wouldn't get implemented or used at all.

I think we should design for the real world, e.g. to help
provide formal interoperability of software components
already present in the landscape.

I would add also that standardizing on an HTTP middleware
interface that is largely compatible with most existing
middleware and stacks today, does not preclude us from
proposing a more modern standard in the future if/when
something like functional interface comes around and becomes
commonplace. Right now, it's a bit of a fantasy.

On the ClientMiddleware vs ServerMiddleware debate, I don't
have much to add. I think that we need two interfaces -
possibly two sets of interfaces, if we decide to formally
define an interface for the `$next` middleware delegate
function, which I think we should.

Would it make sense to separate the client and server
middleware standard completely, as two different standards
and packages? A lot of projects are going to need/support
only the server middleware interfaces - and most actual
middleware components are going to need only either the
client or server interface, rarely both. In fact, Guzzle is
probably at this time (one of) the only middleware hosts
that need the client middleware interfaces at all?

Finally, re: Tristan Darricau

> About the initial proposal I don't see the point of
> adding an interface with _invoke()

In many cases, we type-hint as `callable` in method-
signatures, and type-hint statically with a php-doc tag,
e.g.:

/**
* @param callable|MiddlewareInterface
*/
public functon push(callable $middleware) {
// ...
}

It doesn't provide run-time safety, but you can implement
that with run-time checks and throw an exception in
middleware hosts. It serves primarily as documentation
in this case.

And of course it serves it's primary purpose when middleware
classes implement it, ensuring that the call signature of
middleware components is correct.

That's all I have at this time.

For the record, I'm not eager to champion this effort - I
have too many side projects as it is, so if Woody wants
to take the lead, I am fine with that :-)
> https://groups.google.com/d/msgid/php-fig/CACd2v47exVvQJJ_rhWwCnRxyMB7toWZLsqx8fTGaEAoEfS4Qqw%40mail.gmail.com.

Rasmus Schultz

unread,
May 10, 2016, 2:45:18 PM5/10/16
to php...@googlegroups.com
Oh and, er, here's my draft for the PSR and server interfaces:

https://gist.github.com/mindplay-dk/1d60ccfa083acfca32698d3c6d9a0945

Rasmus Schultz

unread,
May 10, 2016, 2:53:50 PM5/10/16
to php...@googlegroups.com
Oh, just one thing I'd like to add.

We can take a hint from virtually every middleware stack in the PHP
sphere right now and comply with what is already widely used and
already widely referred to as "PSR-7 middleware" - and deliver a PSR
that is useful to the community, now, today.

Or we can struggle to come up with something "better" that won't be
approved before at least a year, and won't be implemented for years to
come.

Or, hey, we can do both! Deliver a PSR that is useful today and can be
adopted with minimum fuss - and continue working on a modern
alternative to replace it some years from now.

I don't think there'a any reason not to do the first, since it's
already happening informally with or without this PSR, regardless of
what anybody "wants" - it's not perfect, but the language doesn't
really seem to support a "best of all worlds" middleware concept at
this time.

IMO, the community needs this PSR now - or two years ago ;-)

Woody Gilk

unread,
May 10, 2016, 2:58:00 PM5/10/16
to PHP Framework Interoperability Group
On Tue, May 10, 2016 at 1:53 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:
>
> Or, hey, we can do both! Deliver a PSR that is useful today and can be
> adopted with minimum fuss - and continue working on a modern
> alternative to replace it some years from now.


Eloquently put. I agree completely, which is exactly why I think the
current proposal is the right way to go. We can certainly do a 2.0 or
a totally new PSR with a PHP 7.1+ approach that has return type hints
and rainbows. For now, let's just standardize something that is
already de facto standard.

Oscar Otero

unread,
May 10, 2016, 3:09:09 PM5/10/16
to php...@googlegroups.com
I’d like to add a real example about separate both server/client middleware components.

I’m using this component to save the responses into files:

This allows to generate a html file with the first request and serve it as static file the next requests. In other words, a file base caching (or extreme caching as it’s named here: http://mateusztymek.pl/blog/extreme-caching-psr7)

The signature of the function use the RequestInterface, so it can be used also in a client-side app.

If the interface is divided in two, it’s needed to create two classes for the same purpose.


Paul Jones

unread,
May 10, 2016, 3:21:30 PM5/10/16
to php...@googlegroups.com
Agreed on all counts.

Ross Tuck

unread,
May 10, 2016, 3:36:06 PM5/10/16
to PHP Framework Interoperability Group
Hi folks,

Just a few thoughts, based on years of wanting this and a couple of conversations today:

a) With regards to __invoke vs a named method: there is some legit strangeness to having an interface that requires a callable for another instance, but we can't typehint it properly. I've seen this cause some confusion in Tactician as well, which operates on the same principle. If we change it to a Middleware typehint, we make it pretty clear what's happening.

b) However, that doesn't mean we have to give up closures, which I love. Instead, it would be a couple lines of code for dispatchers to ship with a "new CallableMiddleware(function (...) {})" that just wraps a callable. That's uglier syntax but the dispatcher could just do the wrapping internally with a little syntactic sugar. So, a dispatcher's add() takes a Middleware instance which it adds directly to the stack or a callable which it wraps and then adds it to the stack. If you want strong typehint, okay, add() vs addCallable() It's a small, easy thing for dispatchers to do (for the ones that want to!)

c) Furthermore, I don't think we should be trying to standardize a dispatcher interface in anyway. The practices are still being worked out, the idomatic path is going to be tooling specific and folks are going to be wiring up their dispatchers directly, so there's no point trying to standardize them. Plus, I don't really see any gain compared to standardizing the middleware itself, which is where the reuse love is.

d) Finally, I had a conversation with Anthony Ferrera earlier today, where he brought up an interesting point: why do we have the Response added in as a parameter? Why can't it be solely a return value? At first, this seemed pretty common sense to me (it's what's in use more broadly) but when we started working through the examples, I realized that PHP's HTTP signature differs in a key way from most other languages: it's immutable. The only Response instances that really count are the ones returned from the app or the error handling middleware, both of which are returned. In most other languages, you're carrying the Response forward so you can write to it but that's just not true in PSR-7. I've looked at a few other use cases like Cache-Control, ETags, Auth, Routing, etc and I just don't see a use case in which passing it forward in the chain really helps (especially since you can just call the next middleware at the start of your middleware to get an instance to write to). I was a bit surprised at first, but the more I look at this, the more it makes sense. If anyone has any use cases that they can't make work otherwise, please let me know and I'll see if they can be adapted.

With regards to other points, I believe we were roughly in agreement with a signature something like "handle(Request $request, Middleware $next): Response" I know that's a bit different but from what's out there now, but as I mentioned, I think we're doing a couple things differently in PSR-7 already, there can be an easy conversion layer and it's worth having a conversation about before Middleware hit the big time everywhere.

Please keep in mind, it's a separate point from the ones raised above, which I think are valid on their own.

Finally, it's good to see folks are ready for this discussion. I believe standardized middleware could be one of the most important architectural shifts in PHP for the foreseeable future and sticking the landing on this one, both technically and marketing wise would be awesome.

Thanks!

Woody Gilk

unread,
May 10, 2016, 3:46:46 PM5/10/16