PSR-15 Generators

193 views
Skip to first unread message

Simon Holloway

unread,
Aug 2, 2017, 11:52:58 AM8/2/17
to PHP Framework Interoperability Group
I haven't been keeping up with this mailing list so maybe this has already come up and I missed it.

Did anyone consider using generators/yield instead of a delegate/frame?

So instead of:

interface MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        DelegateInterface $delegate
    ): ResponseInterface
}


class Timer implements MiddlewareInterface
{
   
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
   
{
        $startTime
= microtime(true);
        $response = $delegate->process($request);
       
return $response->withHeader('X-Timer', sprintf('%2.3fms', (microtime(true) - $startTime) * 1000));
   }
}

We could do

interface MiddlewareInterface
{
    public function process(ServerRequestInterface $request): Generator
}


class Timer implements MiddlewareInterface
{
   
public function process(ServerRequestInterface $request)
   
{
        $startTime
= microtime(true);
        $response = (yield $request);
       
return $response->withHeader('X-Timer', sprintf('%2.3fms', (microtime(true) - $startTime) * 1000));
   }
}

Then to dispatch middleware, you would do the following:

function dispatch(ServerRequestInterface $request, ResponseInterface $response, array $middleware)
{
    $postProcessors = [];
    foreach ($middleware as $middleware) {
        $frame = $middleware($request);
        if ($frame->valid()) {
            $request = $frame->current();
            $postProcessors[] = $frame;
        } else {
            $response = $frame->getResponse();
        }
    }

    $postProcessors = array_reverse($postProcessors);
    foreach ($postProcessors as $frame) {
        $frame->send($response);
        $response = $frame->getReturn();
    }
    return $response;
}


This would require >= PHP 7 due to needing Generator::getReturn() and you loose some type hinting. Terminating middleware has to be a bit hacky, it would have to use `yield from []` to create an empty generator, then return a response. Also no catching thrown objects further down the app.

On the positive side, I think it reduces the mental overhead a bit, makes PSR-15 only 1 interface, keeps middleware out of stack traces, and promotes a great feature of PHP.

Woody Gilk

unread,
Aug 2, 2017, 1:01:34 PM8/2/17
to PHP Framework Interoperability Group
This would require >= PHP 7

That's a non-starter. There are plenty of people stuck on PHP 5.x for various reasons and I don't want to shut them out.

you loose some type hinting

Also a non-starter for me, static typing is good and we should keep it.

Your dispatching example is pretty complicated, I don't think it "reduces mental overhead" at all. Compare to:

--
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+unsubscribe@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/8303b4eb-38af-4e2a-b63a-b0f45eafa697%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Simon Holloway

unread,
Aug 2, 2017, 1:15:55 PM8/2/17
to php...@googlegroups.com
Dispatcher is a bit more complicated (I knocked it together in 10 minuets, it could be refined) but what I was referring to was the middleware implementation e.g. the timer example I provided.

I'm not going to aggressively push this idea, I just thought it was worth floating.


--
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/lkpuaZPcBsY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

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

Matthew Weier O'Phinney

unread,
Aug 2, 2017, 3:41:23 PM8/2/17
to php...@googlegroups.com
On Wed, Aug 2, 2017 at 10:52 AM, Simon Holloway <hollo...@gmail.com> wrote:
> I haven't been keeping up with this mailing list so maybe this has already
> come up and I missed it.
>
> Did anyone consider using generators/yield instead of a delegate/frame?
>
> So instead of:
>
> interface MiddlewareInterface
> {
> public function process(
> ServerRequestInterface $request,
> DelegateInterface $delegate
> ): ResponseInterface
> }
>
>
> class Timer implements MiddlewareInterface
> {
> public function process(ServerRequestInterface $request,
> DelegateInterface $delegate)
> {
> $startTime = microtime(true);
>
> $response = $delegate->process($request);
> return $response->withHeader('X-Timer', sprintf('%2.3fms',
> (microtime(true) - $startTime) * 1000));
>
> }
> }
>
>
> We could do
>
> interface MiddlewareInterface
> {
> public function process(ServerRequestInterface $request): Generator
> }

This is an interesting idea, but I feel it falls in the category of
dictating architecture.

One reason the DelegateInterface (soon to be "HandlerInterface") was
chosen is to allow implementations to vary. While the majority of
middleware frameworks have the delegate act as a stack, they don't
_need_ to. The delegate passed could be simply something guaranteed to
return a response, or it could use some sort of filtering algorithm to
locate a suitable handler, etc.

Additionally, as Woody noted, we lose out on the ability to guarantee
that _something_ yields a response. By having the middleware be a
generator, we have to somehow specify that eventually one of them
needs to return a response; currently, PHP has no way of doing that.
We also have no way of forcing the generator to yield a request to
allow moving on to the next in the stack. If somebody yields a
non-request, or returns a non-response, there's no guarantee operation
will continue as expected.

> class Timer implements MiddlewareInterface
> {
> public function process(ServerRequestInterface $request)
> {
> $startTime = microtime(true);
>
> $response = (yield $request);
> return $response->withHeader('X-Timer', sprintf('%2.3fms',
> (microtime(true) - $startTime) * 1000));
>
> }
> }
>
>
> Then to dispatch middleware, you would do the following:
>
> function dispatch(ServerRequestInterface $request, ResponseInterface
> $response, array $middleware)
> {
> $postProcessors = [];
> foreach ($middleware as $middleware) {
> $frame = $middleware($request);
> if ($frame->valid()) {
> $request = $frame->current();
> $postProcessors[] = $frame;
> } else {
> $response = $frame->getResponse();

This should be `getReturn()`, no?

> }
> }
>
> $postProcessors = array_reverse($postProcessors);
> foreach ($postProcessors as $frame) {
> $frame->send($response);
> $response = $frame->getReturn();
> }
> return $response;
> }

One thing I wonder about when looking at this is how error handling
would occur. In most stacks I've reviewed, you have middleware that
contains a try/catch block; I have no idea if that would work here, or
if that now becomes a requirement of the dispatcher. If the latter,
that's a non-starter; it's quite useful to be able to have different
error handlers within the middleware stack in order to have granular
error handling.

> This would require >= PHP 7 due to needing Generator::getReturn() and you
> loose some type hinting. Terminating middleware has to be a bit hacky, it
> would have to use `yield from []` to create an empty generator, then return
> a response. Also no catching thrown objects further down the app.
>
> On the positive side, I think it reduces the mental overhead a bit, makes
> PSR-15 only 1 interface, keeps middleware out of stack traces, and promotes
> a great feature of PHP.

I'll have to disagree with you on the "reduces the mental overhead a
bit" statement! I had to go through the examples a few times to
understand how middleware is processed (first outside-in, then
inside-out); for those not familiar with generators, the example is
extremely opaque and difficult to comprehend.

Also, as noted, it makes alternate delegation strategies impossible,
as it essentially dictates one and only one way to implement a
middleware stack. Having the freedom to implement the delegate any way
you wish so long as it returns a response provides a lot of
possibilities for developers, while retaining interoperability between
implementations.

--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/

Matthew Weier O'Phinney

unread,
Aug 2, 2017, 3:50:23 PM8/2/17
to php...@googlegroups.com
On Wed, Aug 2, 2017 at 12:00 PM, Woody Gilk <woody...@gmail.com> wrote:
>> This would require >= PHP 7
>
> That's a non-starter. There are plenty of people stuck on PHP 5.x for
> various reasons and I don't want to shut them out.
>
>> you loose some type hinting
>
> Also a non-starter for me, static typing is good and we should keep it.

Question for the list: what are folks' thoughts on shipping TWO
versions of interfaces in a single specification?

What I mean is: it would be tremendously useful to be able to define
PHP 7 STH and RTH within the interfaces; this essentially ensures that
any implementations that do not return the specified RTH will raise an
engine error.

Clearly, we cannot do this for interfaces that target PHP 5 still, but
considering that we specify the return types for the interfaces
already, it seems we _could_ provide two different implementations of
the interfaces, one targeting each version. These could be released as
separate minor versions of the associated library (e.g., 1.0 would
have the PHP 5 interfaces, 1.1 would have the PHP 7 interfaces).
Consumers and/or implementors would then choose which version they
support.

Thoughts?

Woody Gilk

unread,
Aug 2, 2017, 3:57:10 PM8/2/17
to PHP Framework Interoperability Group
I think two MAJOR versions would be correct, as adding types could be a breaking change:

PHP5+ version 1.0.0
PHP7+ version 2.0.0

Simon Holloway

unread,
Aug 2, 2017, 4:10:48 PM8/2/17
to php...@googlegroups.com
> This should be `getReturn()`, no? 
Yes it should, I'm very bad for sharing code that I've not actually run. Sorry about that.

> In most stacks I've reviewed, you have middleware that 
> contains a try/catch block; I have no idea if that would work here, or 
> if that now becomes a requirement of the dispatcher. If the latter, 
> that's a non-starter;
I'm pretty sure you wouldn't be able to catch any exceptions outside of the yield, so the latter.

> I'll have to disagree with you on the "reduces the mental overhead a 
> bit" statement! I had to go through the examples a few times to 
> understand how middleware is processed (first outside-in, then 
> inside-out); for those not familiar with generators, the example is 
> extremely opaque and difficult to comprehend. 
Again I was talking about the middleware not the dispatcher. 

So yea, message received, non-starter.

--
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/lkpuaZPcBsY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Simon Holloway

Adrien Crivelli

unread,
Aug 3, 2017, 8:17:37 AM8/3/17
to PHP Framework Interoperability Group

On Wednesday, 2 August 2017 19:01:34 UTC+2, Woody Gilk wrote:
This would require >= PHP 7

That's a non-starter. There are plenty of people stuck on PHP 5.x for various reasons and I don't want to shut them out.

PHP 7+ was more than 53% of composer usage last May, and PHP 5.6 only at 31%. This PSR is yet to be released and by the time it is those numbers will be even more in favor of PHP 7. Nowadays I would rather see PSR pushing innovation on new tech (including language syntaxes), rather than being stuck in the past and spending more time to deal with compatibility than what it is worth.

Also this specific PSR is all about new way to do things. I don't think future adopter would be the guys who are stuck with PHP 5, but rather the one who are already on PHP 7.1 today.

As I am sure you are aware, major projects are moving to PHP 7 only. Doctrine will require 7.2, ZF3 requires 7.1, Symfony 4 will require 7.1TYPO3 8 requires 7.0, to name only a few.

Rivera, John

unread,
Aug 3, 2017, 10:21:19 AM8/3/17
to php...@googlegroups.com
I don’t want to put words in anyone’s mouths, but PHP 5.6 still receives security support — and will be until December 31, 2018. And 31% is nonetheless a rather sizable number (a little under 1 in every 3 PHP developers use 5.6). As a de facto standards body, I think the FIG should follow the core team in this.

Once the time comes, in a year and four months, the FIG definitely should update all non-depreciated PSRs with PHP 7 gloriousness. Funnily enough, PHP 7.0 actually will be EOL’d shortly before 5.6, so we can even go directly to void return types and all that goodness.

Just my 5 cents though. :)

John 

--
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.

Larry Garfield

unread,
Aug 3, 2017, 11:37:43 AM8/3/17
to php...@googlegroups.com
I have to agree here.  The market is moving much faster than it used to, and the likely adopters of a new PSR that will necessitate changing their existing code base are those that are also most likely to be running on newer versions.

If someone has a legacy app sitting on a PHP 5.x server and isn't interested in updating it for PHP 7, I have a hard time believing they'd be eager to update it for PSR-15.  Upgrading to PHP 7 would be *less* effort and *more* value.

We tried to be backward-looking for PSR-6 on the PHP version, and in hindsight I think that was a mistake.

Let's go ahead and target PHP 7.0 for PSR-15 outright.

--Larry Garfield

Rivera, John

unread,
Aug 3, 2017, 11:51:01 AM8/3/17
to php...@googlegroups.com
The question, I think, is how far ahead should we look? For example, if we decide to target PHP 7, but not 7.1, that will be EOL’d in a year and four months. PSR-15 has been in limbo for nearly that long…!

PHP 7.1 will be EOL’d in two years and four months — that’s coming faster than you’d think.

Is it our aim to have a bunch of PSRs arbitrarily targeting various versions of PHP and in random degrees of ‘feature decay’?

Incidentally, 5.6 isn’t really ‘backwards-looking’ — it’s still supported. I concede we’d be going into semantics and pedantry with this point so I’ll not push it any further :)

John

--
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.

Woody Gilk

unread,
Aug 3, 2017, 12:26:37 PM8/3/17
to PHP Framework Interoperability Group
On Thu, Aug 3, 2017 at 10:37 AM, Larry Garfield <la...@garfieldtech.com> wrote:
Let's go ahead and target PHP 7.0 for PSR-15 outright.

I'm going to say no to this. There is already a significant ecosystem of middleware that supports PHP 5.6 and I want those early adopters to be able to use PSR-15 right away. It will hurt adoption of PSR-15 to force the jump to PHP 7+.

That said, I *do* think that PSR-15 should immediately ship a version 2.0.0 of `psr/http-server-middleware` that enforces strong typing. (No need for PHP 7.1 or 7.2, because there is no "void" return in PSR-15).

Now someone will come along and tell me that such a change requires a whole new PSR... 🙄

Stefano Torresi

unread,
Aug 3, 2017, 1:17:45 PM8/3/17
to PHP Framework Interoperability Group

I think the real problem behind this discussion is PSR versioning. We should address that first, because the process of releasing a new PSR to bump requirements is time consuming and inefficient, as Woody maybe wrote between the lines.


--
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.

Adrien Crivelli

unread,
Aug 4, 2017, 4:54:08 AM8/4/17
to PHP Framework Interoperability Group
I can see that Woody is feeling strongly in favor of support of PHP 5.6. I assume he knows what he is talking about better than myself and will trust him on that. Let's not waste more time debating on something that can be implemented quickly and easily (with a 1.0 for PHP 5.6 and 2.0 for PHP 7+).

Niklas Keller

unread,
Aug 4, 2017, 8:16:42 AM8/4/17
to PHP Framework Interoperability Group
On Friday, August 4, 2017 at 10:54:08 AM UTC+2, Adrien Crivelli wrote:
I can see that Woody is feeling strongly in favor of support of PHP 5.6. I assume he knows what he is talking about better than myself and will trust him on that. Let's not waste more time debating on something that can be implemented quickly and easily (with a 1.0 for PHP 5.6 and 2.0 for PHP 7+).

Something like that is pretty stupid. It prevents having an implementation that's compatible with both, 5.6 and 7. And two versions of a package can't be installed side-by-side with Composer. New major versions of PSRs should definitely use new package names for that reason.

Regards, Niklas 

Niklas Keller

unread,
Aug 4, 2017, 8:16:53 AM8/4/17
to PHP Framework Interoperability Group
On Friday, August 4, 2017 at 10:54:08 AM UTC+2, Adrien Crivelli wrote:
I can see that Woody is feeling strongly in favor of support of PHP 5.6. I assume he knows what he is talking about better than myself and will trust him on that. Let's not waste more time debating on something that can be implemented quickly and easily (with a 1.0 for PHP 5.6 and 2.0 for PHP 7+).

Stefano Torresi

unread,
Aug 4, 2017, 8:59:59 AM8/4/17
to php...@googlegroups.com
Il giorno ven 4 ago 2017 alle ore 14:16 Niklas Keller <nicks.po...@gmail.com> ha scritto:
Something like that is pretty stupid. It prevents having an implementation that's compatible with both, 5.6 and 7. And two versions of a package can't be installed side-by-side with Composer. New major versions of PSRs should definitely use new package names for that reason.

Regards, Niklas 

This has been brought up a number of times already, and disregarding it as "stupid" will not drive to a definitive solution.

At PHPDay unconference, participants in the discussion deemed this as generally not much of an issue, mostly because you already have to comply to this constraint anyway: you can't install packages having incompatible dependency graphs, so why bother trying to circumvent this limitation for PSRs only?

While I understand and agree with the principle, in practice the chance of having dependency issues due to migrations between major versions is very high anyway.

That said, if we want to release entirely different PSRs for updated versions (which would be arguably a cleaner solution), I think we need to streamline the process and make it easier, because at the moment it's so much of a PITA that nobody wants to do it.

Woody Gilk

unread,
Aug 4, 2017, 9:13:35 AM8/4/17
to PHP Framework Interoperability Group
I don't see how it is any different than having two different PSRs. Middleware either depends on version `1.0 - 2.0` if it wants to support PHP 5.6, or `^2.0` if it wants to be strongly typed.

Having two different PSRs means changing the namespace to allow either package to be used, which is (imo) much more stupid.

--
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+unsubscribe@googlegroups.com.

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

Niklas Keller

unread,
Aug 5, 2017, 6:02:28 AM8/5/17
to PHP Framework Interoperability Group
Something like that is pretty stupid. It prevents having an implementation that's compatible with both, 5.6 and 7. And two versions of a package can't be installed side-by-side with Composer. New major versions of PSRs should definitely use new package names for that reason.

Regards, Niklas 

This has been brought up a number of times already, and disregarding it as "stupid" will not drive to a definitive solution.

At PHPDay unconference, participants in the discussion deemed this as generally not much of an issue, mostly because you already have to comply to this constraint anyway: you can't install packages having incompatible dependency graphs, so why bother trying to circumvent this limitation for PSRs only?

It doesn't only affect PSRs, but it's probably worth doing it for them, because they're interfaces used by many packages and conflicts are thus very bad.
 
While I understand and agree with the principle, in practice the chance of having dependency issues due to migrations between major versions is very high anyway.

So why increase the risk with PSRs if it can be avoided by using different package names for future versions of PSRs? It's perfectly fine to have two versions of an interface in different namespaces.
 
That said, if we want to release entirely different PSRs for updated versions (which would be arguably a cleaner solution), I think we need to streamline the process and make it easier, because at the moment it's so much of a PITA that nobody wants to do it.

It's something the FIG needs to solve then.

Regards, Niklas 

Matthieu Napoli

unread,
Aug 5, 2017, 12:40:49 PM8/5/17
to PHP Framework Interoperability Group
I personally agree with the idea of targeting 7.0 and up, Larry made a good case for it.

Release two versions is confusing. A PSR needs to be a simple and clear message: one solution, one version, one recommended way to do it.

It's also (IMO) a good thing that the FIG pushes PHP 7 forward.

Matthieu
Reply all
Reply to author
Forward
0 new messages