<snip>
> > When
> > the middleware is responsible for calling the next middleware, you
> > gain specific benefits:
>
> Yes, but it's also a pattern most modern languages are getting away
> from. Go takes ResponseWriter,Request. Rust takes Request,Response.
> Django on Python takes Request,Response
>
> Basically the only other language using next is Javascript.
You're making the assumption that approaches never evolve.
As somebody who ported Connect/Express to PHP, I'll weigh in on why I've adopted
the argument, and why I think it's an important part of the interface.
When you don't have the argument, the way to build your middleware application
is to pass the stack/application to each middleware via either the constructor,
a setter, or a property. While DI containers help with this, it requires that
you know all paths through the application and define this via the dependency
graph. Many frameworks thus have a conventions-based approach for injecting the
stack into middleware (e.g., the application injects itself as a property),
which means that middleware is tied to specific middleware stacks. When you have
a single, language-supported approach, such as WSGI in Python or Rack in Ruby,
then this isn't a problem. When you don't, such as in JS or PHP, it *is* a
problem, as you cannot re-use middleware between the different stacks.
Connect/Express could have taken this approach, but instead opted for passing
the stack as an argument to the middleware during invocation, via a "next"
argument. This approach meant that when Express split from Connect, *middleware
written for Connect continued to work with Express, and vice versa*.
Interoperability works best when it's not based on conventions, but rather
signatures and strict types. Passing the stack as an argument enforces exactly
that.
Another thing to consider is: does your middleware really need to store a
reference to the stack *as a property*? In particular, does this make sense in
an async environment? In Connect/Express, your middleware likely shouldn't keep
a reference to the stack, *as it will change between requests*. When we consider
that one target for PHP middleware are async libraries such as ReactPHP and
Icicle, we definitely want to consider this.
> > - being able to terminate the request
>
> This is done through exceptions.
Exceptions should be used for exceptional circumstances, not expected
application workflows.
<snip>
> So, the only thing you lose is the ability to change the Request,
> which, as I say, is a plus, not a downside.
It's a downside. One aspect of middleware is that you can *nest* middleware in
order to re-use it. As such, I can write middleware that routes for:
- /news
- /news/:date
- /news/:date/:article
and nest it in another application such that it attaches and triggers when under
the path "/devteam", and it will now respond to the following:
- /devteam/news
- /devteam/news/:date
- /devteam/news/:date/:article
*without needing to change any routing internally*. The reason it can do this is
that the middleware stack can strip the subpath "/devteam" before passing the
request on to the nested middleware.
Removing this ability would make such nesting far more difficult, making re-use
less palatable.
--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/