> This is Symfony's HttpKernelInterface and StackPHP and it has already
been discussed at length

Same principle, yes, but what I recall being discussed at length was the
lambda-style vs double-pass aspect, which seems unrelated to this
discussion.

I found one or two older threads on this subject, here's one:

https://groups.google.com/d/msg/php-fig/Ew36Ng5EwXE/52dAzZAbAQAJ

The discussion quickly turns to the other, at the time dominant subject
though.

> I'm not sure why we are starting it all again?

Are you're saying all of the concerns I described here have been discussed
and are all invalid?

According to the PSR-15 meta:

- "There are currently two common approaches to server middleware that use
HTTP Messages", single-pass and double-pass.

- There's no mention of the fact that HttpKernelInterface doesn't have the
delegate in the interface.

- The section on "Delegate Design" talks about the design of that
interface, but doesn't say why it exists in the first place.

I recall there being lengthy discussions about how to name and describe the
delegate interface, but skimming back through the discussion threads that
are listed in the meta, it seems that the discussions all start from the
assumption that a middleware interface has the delegate argument.

Can you point to a discussion about whether or not the delegate is
necessary or beneficial?


On Fri, Apr 21, 2017 at 10:17 PM, Matthieu Napoli <matth...@mnapoli.fr>
wrote:

> Hi,
>
> This is Symfony's HttpKernelInterface and StackPHP and it has already been
> discussed at length, I'm not sure why we are starting it all again?
>
> Matthieu
>
>
> Le vendredi 21 avril 2017 17:42:11 UTC+2, Rasmus Schultz a écrit :
>>
>> 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 a topic in the
> Google Groups "PHP Framework Interoperability Group" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/
> topic/php-fig/B3jtdJA7-6w/unsubscribe.
> To unsubscribe from this group and all its topics, 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/d4483c85-4cea-41c2-b46b-9af86df9de63%40googlegroups.com
> <https://groups.google.com/d/msgid/php-fig/d4483c85-4cea-41c2-b46b-9af86df9de63%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/CADqTB_gTc17%3DaRT-xWdHToFzWACW91m1EbhiyTAnKfZGj3%3Dj1w%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to