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.