I don't have time to post a complete reply right now, but will just clarify
a couple of things to keep the dialog going.

First, you seem to be under the impression that a constructor convention is
implied as part of what I'm proposing - it's not, and I wouldn't like the
idea if it was, I have a strong preference for contracts that can be
enforced (by interfaces, type-hints, etc.) rather than "implied" by
documentation.

I don't believe a constructor convention is neither required or necessary,
nor meaningful.

As I've argued, the contract defined by the PSR-15 interfaces states that
middleware will get precisely one delegate, and this is incorrect - there
is not always one delegate, there are sometimes zero, and sometimes
multiple delegates.

You talk of dependencies, and seem to have a problem with the constructor
being used for the delegate. I don't understand why. The delegate _is_ a
dependency - assuming it even exists and that there is always only
precisely one delegate, which isn't true.

Your "routed" middleware is a perfect example of multiple delegates:
filtering something by path, and deciding which middleware to delegate to,
in my opinion does not belong in a middleware stack, but can more naturally
be implemented as middleware - as can anything that conditionally delegates
based on the content of the request; that's what middleware is for. The
fact that you'd need/want something distinctly "middleware-like" made
available to you by framework (the middleware stack) to me is a symptom of
the limitations (or implications) of trying to composer a tree (or graph)
model as a flat list/stack: you over-generalized middleware, and this
doesn't fit, so now you need something else that falls outside the
definition of middleware. I find that extremely disconcerting.

You're also still working from the fixed mindset that middleware must be
bootstrapped as individual components in a DI container - in my opinion,
this is neither going to be simpler, nor better in terms of performance. I
think you're assuming complexity where there isn't necessarily any real
complexity to be found - which creates more net complexity than is
essentially necessary to solve the problem.

Even if that were the case, if middleware wasn't useful without a
dispatcher and DI container, these would be implications with far more
wide-reaching consequences e.g. a constructor convention (which I don't
even believe we need) as this implies a much more complex facility
consisting of at least a dispatcher (with a very specific set of
conventions) and a DI container, e.g. dictates a fairly complex framework
with a very specific architecture.

Have you seen any of these big complex middleware stacks you're designing
and optimizing for? I haven't. My personal biggest middleware stack so far
had five components. I'd like to see one, to get a real idea of the
complexity of the use-case you're considering.

I'd like to encourage anyone reading this to please post the biggest, most
complex middleware stack they've ever used. Just the stack, not the
implementations or individual component bootstrapping necessarily. Let's
see what we're really dealing with?

Perhaps it's difficult to imagine that the complexity and implied framework
requirements you think should be built into the standard aren't actually
necessary in order to achieve the architecture you envision for very large,
very complex applications. I was curious what Shelf (for Dart) does, and
it's actually interesting - I encourage you to take a look:

https://github.com/dart-lang/shelf/tree/master/lib/src

As you can see, they start with a simple Handler abstraction: takes a
Request, returns a Response.

But then they build the Middleware and Pipeline abstractions on top of that
- but as concrete model components of the framework, not as abstractions.

That is, the Handler remains the core abstraction of anything that
processes a Request and creates a Response - yet, they manage to build an
appendable, linear middleware stack on top of it.

So with this approach, they get both benefits: a simple Handler abstraction
that can be dispatched from any context without mandating any framework or
conventions, as well as the "easy" middleware stack for linear compositions.

I haven't tried to imagine what this would look like in PHP, and because
Dart has language features that make this approach elegant, it may be not
be possible to do this as elegantly in PHP, but it's possible none the less.

But either way - and this is the most important point I'm going to make
today - if we could standardize on the Handler interface _before_ we
standardize on PSR-15, we won't have closed any doors: we'll have a much
stronger, more flexible standard with much greater architectural and
implementation freedoms.

The work has been started:

https://github.com/http-message-strategies-interop/fig-standards/blob/http-message-strategies/proposed/http-message-strategies/http-message-strategies.md

The Handler interface (currently called ActionInterface in this document)
is precisely identical to the PSR-15 DelegateInterface, save for the
method-name, which I'm proposing we change, along with some other changes:

https://github.com/http-message-strategies-interop/fig-standards/issues/6

If we can get this to align better with PSR-15, the Delegate interface
should then be replaced by the Handler interface, and developers can then
freely choose to ship Handler implementations and/or Middleware
implementations, or both (with a Middleware implementation wrapping a
Handler, as discussed and demonstrated in my previous post) - whatever
makes the most sense.

If we can streamline this, I would be much less opposed to PSR-15, because
it ensures I can have simplicity and the architectural freedom to forego
using a Dispatcher (and DI container) in simple cases - it's win/win for
everyone, and has additional benefits to middleware not currently provided
by PSR-15 without a Handler standard:

1. Dispatchers can themselves implement the Handler interface, making
Dispatchers interoperable at the high level.

2. The "final" handler often supported by Dispatchers can be type-hinted as
a Handler too.

The two in combination would make it possible to compose several
Dispatchers, which makes it possible to ship entire modules with
ready-bootstrapped sub-middleware stacks, effectively obliterating the need
for proprietary "pipe" implementations in most Dispatcher packages as well.

If we standardize PSR-15 without the Handler interface in place first,
we'll be in a pretty terrible situation when the Handler proposal does get
standardized, as we'll have identical but non-interoperable types in
Middleware and Dispatcher implementations, for no practical reason.

PSR-15 would stand much stronger with the Handler interface in place first,
as the Handler interface has far more uses than just middleware.

At this point, my campaign is much less against middleware as proposed by
PSR-15, and more for standardizing Handlers, which have at least two
important uses in PSR-15 as proposed, making it overall much simpler to
integrate middleware, dispatchers, controllers, filters and practically any
other HTTP-related concepts you can think of.

We're in this group working to create interoperability among frameworks,
not to dictate architecture or impose the use of frameworks.

In my opinion, we would be remiss to move ahead with something that doesn't
provide the deepest possible interoperability and the greatest degree of
freedom.


On Tue, May 9, 2017 at 7:07 PM, Matthew Weier O'Phinney <
mweierophin...@gmail.com> wrote:

> I'm late to the thread (been a busy few weeks), but here goes...
>
> The chief problem I see with this approach is the same problem I've
> observed in
> Rack and Stack; namely: it's a convention-based approach, versus a
> fuctional one.
>
> In Rack, you use a specially named property (app), and assign it the
> application
> and/or "next middleware" instance. As it's a _convention_, there's no way
> to
> ensure it's populated, nor that it's populated with what's expected.
>
> In Stack, you pass that value as the first constructor argument. My
> understanding is that even if you will not be invoking it, you _must_ pass
> it
> this way, or else it's not compatible. It also means that in any given
> middleware class, the name of the property holding this value might be
> different.
>
> The reason Connect and ExpressJS started passing `next` around was
> precisely to
> circumvent the issue of convention. By making it a direct requirement when
> invoking middleware, any middleware has immediate and direct access to it.
> There's no guesswork involved. (I'll note that it suffers from the same
> problem
> as Stack, in that if you're not using it, it's a useless argument; however,
> it has the benefit that the constructor is then used strictly for
> _dependencies_, and not _runtime_.)
>
> You noted another issue with injecting via the constructor, and Beau noted
> this
> already: autowiring. How would a container know _which_ middleware to
> inject at
> any given point? It _could_, if you use concrete class names, but that goes
> against the whole point of middleware, which is _composability_. If you
> typehint
> on the interface, though, you then need to go to some effort to ensure
> containers can pull the middleware correctly.
>
> The answer to that, according to your write-up, is that middleware in this
> particular case forms an HTTP kernel for your application, and, as such,
> going
> to the effort of creating the workflow is minimal. That argument breaks
> apart,
> however, when you want to start re-using middleware as part of
> route-specific
> workflows.
>
> Just so everyone is following, this is what I mean by a route-specific
> workflow:
>
> ```php
> $app->route('/api/blog', [
>     Authentication::class,
>     Authorization::class,
>     Blog::class,
> ], ['GET', 'POST']);
> ```
>
> In the above, the middleware piped into the application is a stack itself.
> Doing
> this allows me to re-use things like authentication and authorization, but
> also
> compose them such that they only apply to specific routes.
>
> Following from the above example, I might write authentication middleware.
> This
> _could_ be used for the entire application, but it might be better to only
> put
> it in front of routes that actually require authentication. If we go the
> constructor injection path, now I have to apply that to my _routed_
> middleware
> as well, which likely has me instantiating my entire object graph for my
> entire
> application on each and every request, and creating _duplicate_ instances
> of
> common middleware (due to the need to compose different _nested_ middleware
> depending on the route) — a problem that's been solved by using DI
> containers as
> part of dispatchers for many years now. Is that expensive? It can be. It's
> less
> expensive under PHP 7 than it was in PHP 5, but if we can avoid
> instantiating
> something unless we actually use it, why shouldn't we? If we can avoid
> multiple
> instances of the same class due to different inner middleware, shouldn't
> we?
> Particularly if it's _easier_ (e.g., using a class name in the above
> examples,
> instead of instantiating objects and providing dependencies manually).
> Beau
> does a nice job of illustrating this in his most recent post to this
> thread.
>
> In the above example, authentication and authorization are not
> _controllers_,
> they are _middleware_; the idea is that they would return a response if
> conditions fail, but otherwise delegate deeper into the stack. But they are
> being composed as part of _routed_ middleware, which you would term
> "controllers", in order to have HTTP workflows that are specific to a given
> route.
>
> 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.
>
> The power of this is seen, again, in the above example. I can route these
> final
> handlers that are guaranteed to return a response in the same way I might
> route
> any middleware, and that allows me to in turn route a stack/pipeline in
> place of
> such final handlers. Without that ability, the story becomes far more
> complicated - these stacks then either need to open up what the accept (in
> other
> words, _remove typehints_), or have multiple methods for routing things
> into
> them, and then also worry about how to handle stacks versus final
> middleware.
>
> My argument is that your proposal may be simpler in _some_ contexts, but
> loses a
> ton of flexibility and power outside of those contexts. The proposed
> PSR-15 as
> it currently stands addresses these issues, and does so in a way that does
> not
> require specific conventions.
>
>
> On Fri, Apr 21, 2017 at 10:42 AM, Rasmus Schultz <ras...@mindplay.dk>
> wrote:
> > 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.
>
>
>
> --
> Matthew Weier O'Phinney
> mweierophin...@gmail.com
> https://mwop.net/
>
> --
> 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/CAJp_myWNHthoiYbcmCb51Dh%3DC7fpqaaHYSA8yJPijbU88xtx5g%
> 40mail.gmail.com.
> 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_gf3FvbkXbWWUp4GM9z-cy3Mz%2Bk-U4e6Yc-On4JPN9%3DtQ%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to