I reviewed what Rasmus and Michael (et al) are saying about different kinds
of handlers and I _think_ I'm starting to understand what they are talking
about. It goes way beyond what is proposed in PSR-15. I would even say
that's an entirely different way of thinking about middleware. (Please
correct me if any of the following is wrong. ;)

For the most part, we have thought about middleware as being basically a
list of things that the request drops through and something inside the list
will produce a response:

req -> [set-client-ip, require-auth, parse-body, handle-req] -> res

And if something goes awry during that sequence, the rest of the middleware
will be skipped by returning early:

req -> [set-client-ip, require-auth (failed), parse-body, handle-req] -> res

This works perfectly well for simple systems and not so well for more
complex systems. For instance, if we have a complex application where there
is a "user" part and an "admin" part, the admin part needs a different
sequence:

req -> [set-client-ip, require-auth, *require-admin-role*, parse-body,
handle-req] -> res

In the case of something like Slim Framework, this is accomplished at the
routing level, where any route can have middleware attached to it:
https://www.slimframework.com/docs/concepts/middleware.html#route-middleware

What Michael seems to be thinking about is these sorts of complex scenarios
where having the same sequence of middleware executed for every request is
insufficient. Instead, the middleware becomes more of a tree structure,
where each middleware might do 3 possible things:

1. modify the request
2. generate a response (possibly by delegating to another middleware chain!)
3. modify the response

Now, to be clear, there's nothing in PSR-15 that says this is not possible.
In fact, several dispatchers already support nested middleware. From what I
can tell, Michael's objection (and maybe Rasmus' too) is more about that
fact that the three possible actions are not distinct. With distinct
interfaces for the three actions, this becomes a thing:

(Side note, I am using my own made up interfaces here, not the ones that
Michael has proposed.)

$foo = new FooMiddleware(); // implements ModifyRequestInterface
$request = $foo->modify($request);

With just one interface, this is impossible because the middleware has to
return a response or delegate to something else that does. So rather than
having a dispatching system that runs X middleware, one of which is assumed
to return a response by handling the request, it becomes possible to use a
simple routing system that just points at a handler class:

class AdminCreatePage implements ServerRequestHandlerInterface
{
    public function __construct(
        SetClientIp $ip,
        RequireAuth $auth,
        RequireAdminRole $admin,
        ParseBody $body
    ) { ... }

    public function handle(ServerRequestInterface $req)
    {
        try {
            $req = $this->ip->process($req);
            $req = $this->auth->process($req);
            $req = $this->admin->process($req);
            $req = $this->body->process($req);
        } catch (Exception $e) {
            return $this->errorResponse($e);
        }

        // do whatever domain stuff
        return $this->successResponse();
    }
}

This a pretty contrived example and the various modifying middleware would
probably be wrapped into a single class, but I think it illustrates the
difference between PSR-15 and what Michael is proposing.

Honestly, I see a lot of value in this approach. It works better for
complex applications and is much more flexible. It also makes it easy to
share various kinds of middleware as stand alone components, or even
collections of middleware grouped into a single class that implements one
of the three interfaces. With variadic declarations and some functional
tricks, it could be a really powerful approach. Is it middleware? Not by
the definition that we have been using. Is it interesting? Definitely. I am
definitely coming around to the idea.

--
Woody Gilk
http://about.me/shadowhand

On Tue, May 16, 2017 at 12:27 AM, Geert Eltink <geert.elt...@gmail.com>
wrote:

> On Monday, May 15, 2017 at 4:00:53 PM UTC+2, Michael Mayer wrote:
>
>
>> Can we achieve the same by using only MiddlewareInterface and simply
>> ignoring $next/$delegate? Matthew wrote:
>>
>> ```php
>>> $app->route('/api/blog', [
>>>     Authentication::class,
>>>     Authorization::class,
>>>     Blog::class,
>>> ], ['GET', 'POST']);
>>> ```
>>>
>>> …
>>>
>>> Now, does innermost middleware need to receive the delegate? No, but by
>>> having a
>>> single interface to describe any link in the chain, versus two
>>> interfaces (one
>>> for middleware that delegates, one for the final dispatched "things" --
>>> controller, action, what have you), we simplify how application
>>> stacks/kernels
>>> are built, as everything can be injected exactly the same.
>>>
>>
>> Honestly, I cannot image how implementing Blog::class as middleware would
>> _simplify_ anything. Neither for applications nor for users.
>>
>> For applications it would be bad, because:
>> 1. if the stack runs empty, the application must provide a
>> mechanism/convention to deal with that
>>
>
> In Expressive 1 there was a mechanism (FinalErrorHandler) to deal with
> empty stacks. It was confusing for users. In Expressive 2 this got changed
> and the last middleware on the stack is always a NotFoundMiddleware.
> Basically if the stack runs empty and it gets to the NotFoundMiddleware it
> means no Response is returned and you can return a 404.
>
>
>> 2. the application must create a delegate object to call Blog::class,
>> even if it is not used
>>
>> For users it would be bad, because:
>> 1. it is easy to misconfigure your stack: either by _forgetting_ to add
>> Blog::class or by adding middlewares after Blog::class
>>
> 2. it would be confusing if Blog::class is called with a $delegate, when
>> it is not allowed to use it
>>
>
> Isn't this where PHPUnit comes in? If you create the right tests for this
> you know your stack is configured correctly and that the url
> `/blog/some-post` actually can return a blog post.
>
> I think it's more confusing if you have a lot of different middleware
> interfaces. Why is the Blog::class not allowed to use the delegate? I've
> actually seen people doing it. I can't remember the use case or if it was a
> good solution, but they had 3 ActionMiddlewares stacked. If some conditions
> were met the next one would be executed and otherwise it would return a
> Response.
>
>
>> If Blog::class implements HandlerInterface (or DelegateInterface)
>> instead, then things will become simpler IMO:
>> 1. the stack can be verified earlier; thus the app can fail before
>> creating middlewares or at least before Blog::class is called
>>
> 2. the last Middleware will be called with the Blog::class as $next – no
>> EmptyStackHandler/Delegate or similar is needed
>>
>
> So let's say you have a RouterMiddleware. It takes care of parsing the url
> and redirect to the configured Action::class. However that Action class
> needs some specific middleware as well which are added to the stack when
> the route is determined. So you can't check for a valid stack until all
> middleware before the RouterMiddleware is executed and the RouterMiddleware
> itself.
>
> I'm getting the impression that people want to regulate to much with
> interfaces and that would restrict how an application (could) work. And at
> the end of the day you end up with 10 middleware frameworks which do all
> the same thing in the same way.
>
> --
> 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+unsubscr...@googlegroups.com.
> To post to this group, send email to php-fig@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/
> msgid/php-fig/42102e4a-ffeb-46ce-aa51-b117dbf6975c%40googlegroups.com
> <https://groups.google.com/d/msgid/php-fig/42102e4a-ffeb-46ce-aa51-b117dbf6975c%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> For more options, visit https://groups.google.com/d/optout.
>

-- 
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+unsubscr...@googlegroups.com.
To post to this group, send email to php-fig@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/CAGOJM6KFn4Gc3X7mLnvJp9c_fobmtMmcXj70ht7rWe05oHcJJw%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to