On Wed, Aug 2, 2017 at 10:52 AM, Simon Holloway <[email protected]> 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
[email protected]
https://mwop.net/
--
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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/php-fig/CAJp_myVPNLwXexJs8U_NLdw9Kp1jo3%3Dcei5uh6cr-6ds%2BTwv-Q%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.