I hate to do this at a time when the middleware PSR is probably close to finished, but since this occurred to me, I can't shake the thought, and so I have to bring this up, so that at least others are aware and informed about this option with regards to middleware.
I think I saw some framework in Dart doing this a couple of months ago, and I instinctually rejected the idea, because (A) I have a substantial time investment in PSR-15, and (B) it's easy to dismiss things that appear to be too simple - but I would feel remiss if I didn't at least ponder it, so I have been, and I find (reluctantly) that it makes an awful lot of sense. Consider the most generic interface possible for a contract like "takes a request, returns a response" - this: interface Middleware { public function process(RequestInterface $request): ResponseInterface; } Okay, so that looks more like a front-controller or something - that can't be "middleware", because there's no way to delegate to the next middleware component on the middleware-stack, right? But there is - you just apply normal OOP and use dependency injection class RouterMiddleware implements Middleware { /** * @var Router */ private $router; /** * @var Middleware $next */ private $next; public function __construct(Router $router, Middleware $next) { $this->router = $router; $this->next = $next; } public function dispatch(RequestInterface $request): ResponseInterface { if ($this->router->matches($request)) { return $this->router->handle($request); } return $this->next->process($request); } } The fact that this middleware may not always be able to process the request itself is reflected by it's required constructor argument for another middleware to potentially delegate to. Some other middleware might always return a response, and isn't able to delegate at all - it's constructor signature will correctly reflect that fact: class NotFoundMiddleware implements Middleware { public function __construct() {} // doesn't accept other middleware public function dispatch(RequestInterface $request): ResponseInterface { return new Response(...); // 404 not found } } The dependencies of each middleware is correctly expressed by the individual constructor of each middleware. You compose a "middleware stack" not as a data-structure, but simply by proxying each middleware with another middleware: $stack = new ErrorHandlerMiddleware( new CacheMiddleware( new RouterMiddleware( new Router(...), new NotFoundMiddleware() ) ) ); This visually and logically reflects the "onion" structure that is often used to describe how middleware works, which is not apparent from the flat array-based structure used by current middleware dispatchers. If you're not comfortable with the nested structure, you could of course arrange the code to make it look more like a stack as well, e.g. decorating a kernel layer by layer, from the inside-out: $kernel = new NotFoundMiddleware(); $kernel = new RouterMiddleware(new Router(...), $kernel); $kernel = new CacheMiddleware($kernel); $kernel = new ErrorHandlerMiddleware(); You can't make this stack appear "upside down" the way it does with most existing middleware stacks - while that is visually appealing, because you can imagine the request coming in from the top and moving towards the bottom, that doesn't reflect what's really going on. It's the other way around - the inner-most middleware is a dependency of the next middleware out, and so on. What you're building is an HTTP kernel, which is in fact not a stack, but a series of proxies or decorators - so the code is going to reflect the dependencies of the each component, rather than the flow of a request being processed. Since there is no stack, no "runner" or "dispatcher" is required to dispatch the HTTP kernel at all: $response = $kernel->process($request); In other words, no framework is required to implement the layer-like behavior that current middleware dispatchers "simulate" - the layered structure is inherent in the design, and the requirements of each layer, and whether or not it accepts a delegate, is formally defined by the constructor of each middleware component. This also means you can't compose a middleware stack that might topple over - you won't be able to create such a stack at all, because the only way to construct a valid kernel out of middleware, will be to start with an inner-most middleware, such as a 404-middleware, that doesn't require any delegate middleware. Likewise, you won't be able to compose a middleware stack with unreachable middleware components - putting a 404-middleware before any other middleware, for example, is impossible, since it's constructor doesn't accept a delegate middleware. Any HTTP kernel you can compose is pratically guaranteed to be complete and meaningful, which isn't true of the traditional middleware architecture we've been discussing. Some middleware components might even compose multiple other components, and delegate to them based on file-extension, domain-name, cache-headers, or anything else. $stack = new PathFilterMiddleware( [ "*.html" => new RouterMiddleware(...), "*.json" => new APIMiddleware(...), ], new NotFoundMiddleware() ); No middleware "pipe" is required to compose a forked (tree) structure as in this example. If I have to be completely honest, compared with anything we've done with PSR-15 or similar frameworks, I find that this is both simpler, easier to understand, more explicit, and far more flexible in every sense of the word. The only thing I find perhaps not appealing about this, is the fact that all middleware needs to be constructed up-front - in PHP, that may be a problem. However, in my experience, middleware is generally cheap to initialize, because it doesn't typically do anything at construction-time - it doesn't do anything, initialize or load any dependencies etc, until the process() method is invoked. And, most middleware stacks aren't actually very complex when it comes down to it - so this problem may be (at least in part) imagined. There would be ways around that though, such as using a simple proxy middleware to defer creation: class ProxyMiddleware implements Middleware { private $callback; public function __construct($callback) { $this->callback = $callback; } public function dispatch(RequestInterface $request): ResponseInterface { return call_user_func($this->callback, $request); } } This could proxy anything: $stack = new ProxyMiddleware(function (RequestInterface $request) { $expensive = new ExpensiveMiddleware(...); return $expensive->process($request); }); Or use a simple PSR-11 proxy to defer and delegate the actual bootstrapping of the middleware stacj to a DI container: class ContainerProxyMiddleware implements Middleware { private $container; private $id; public function __construct(ContainerInterface $container, $id) { $this->container = $container; $this->id = $id; } public function dispatch(RequestInterface $request): ResponseInterface { return $this->container->get($id)->process($request); } } Both approaches would let you defer the creation of any middleware component and their dependencies until first use. Of course, in a long-running application, these concerns aren't even concerns in the first place - building the middleware stack can be done up-front without problems. But even in a traditional setup with an "index.php" front-controller, the request overhead would typically consist of a few calls to mostly-empty constructors, so we're most likely talking microseconds (if any measurable) difference. I know you will intuitively want to look for reasons to dismiss this idea, but all of this has to make you think? As said, I have a substantial time-investment in PSR-15 myself, and I just spent two weeks trying to dismiss this idea myself. Unfortunately I can't. Trust me, I am *NOT* looking for reasons to shit on my own work, but the appeal of something much simpler, less error-prone, naturally type-safe, more flexible, which doesn't even require any framework at all... it's pretty hard to deny. It has to make you think, right? -- 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/674917bb-623a-4cea-b934-add8887acc65%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.