The problem with this is you're doing what the current proposal does:
you're defining a framework, albeit in the abstract, but you're defining
interfaces that describe not only what middleware is on the surface, but
how it must be implemented and dispatched.

I haven't had time to directly address each of the points in the points
Mattheieu raised, although some of those have been addressed indirectly in
my other posts - but I will try to do that here.

> - AFAIK it didn't work.

StackPHP, as far as I know, was the first attempt at middleware that
garnered any real attention in the community. So I don't think it's fair to
say that it failed?

> And it was not tried in some weekend project: it was built on Symfony's
interfaces, so pretty solid stuff and it got a good chance for success.

It was built on Symfony's interfaces, which are, yes, solid stuff, but
they're also proprietary and only later became sort-of interoperable by
adding PSR-7 adapters. (Correct me if I'm wrong?)

> On the other hand PSR-7 middlewares were incompatible with most of the
existing architectures at the time (incompatible with Symfony for example)
and without a standard interface (rather a "callable" convention) and it
worked out! So to me that's a clear sign.

I would argue that much of this success is due to the fact that PSR-7
brought neutral ground.

Just because the first attempt at middleware worked out doesn't necessarily
mean we're done - it could just be riding on the success of PSR-7. I'm not
saying it is - I'm just saying, it could be a sign of one thing or the
other; it doesn't prove anything.

Success in itself doesn't prove anything at all - if it did, McDonald's
would be making the world's best burger.

We need to keep our discussion in the concrete - we can't make decisions
based on "signs" or "feelings".

> - it's harder to compose middlewares because they are all nested: the
"pipe" pattern was a huge help IMO in spreading the concept

I would argue it's easier to compose middleware - it composes readily,
without needing a dispatcher or pipe.

I would argue the "pipe" pattern was necessary only because the abstraction
depends on dispatchers and therefore didn't really work all that well.

> - it's harder to write reusable middlewares because you need to
standardize the constructor (see the Stack "convention" about the
constructor which is less than ideal)

I would argue it's easier to write reusable middleware - you don't need
to (can't and shouldn't) standardize the constructor, because, as discussed
previously, middleware does not always depend on precisely one delegate.

The only thing that makes a constructor meaningful or interesting in the
first place, is that this is how a class defines it's dependencies. The
need for delegates in the first place arises from trying to remove control
from the middleware - but middleware components are going to have other
dependencies besides the delegate, and, as discussed, sometimes do not
depend on a delegate, and at other times depend on multiple delegates.

You can make it seem as though there's always a delegate, but you're
actually trying to hide important facts - and you pay for that with
run-time exceptions, extra complexity, and middleware stacks that silently
malfunction because you didn't order the components correctly.

> - it's harder to wire in DI containers
> - lazy middlewares are doable but not as easy

Both of these kind of revolve around the same concern.

Yes, it's harder wire in DI containers for individual middleware components.

It's a matter of changing your thinking from the idea of middleware as
individual components.

If you really think about it, the dispatchers for the current proposal's
middleware exists mainly to implement a means of making individual
middleware components work as a single unit.

What I'm proposing makes middleware compose naturally as a single unit,
rather than having to "simulate" this composition at run-time by
synthesizing delegates inside a dispatcher.

I can still only see one potential draw-back to what I'm proposing - that
middleware needs to be constructed up front in order to compose it.

To me, given the real use-cases for middleware, that's a very small
draw-back, and I did already post very simple workarounds that you could
apply in those rare cases where a middleware component is rarely used and
expensive to construct.

The large majority of middleware won't need those workarounds - but they
are very simple, and could be included in the standard package for
completeness.

We will need the handler interface eventually, regardless of the current
middleware proposal - as discussed here:

https://groups.google.com/forum/#!topic/php-fig/HD5meon7TX0

This need has been widely discussed in issues and threads related to PSR-15
and PSR-17 as well.

We've been trying to avoid it, because we want PSR-15 to close, and as a
result, we ended up including it in the middleware proposal itself:

https://github.com/http-interop/http-middleware/blob/master/src/DelegateInterface.php

As you can see, this interface is precisely identical to that of any
general-purpose handler.

Including this interface in the middleware proposal is bad, because the
handler-interface is by no means useful only in a middleware context.

And the reason it's even possible to dispatch a middleware instance behind
this delegate-interface, is because that interface describes the only real
need and true purpose of the underlying implementation: takes a request,
returns a response. That's the only real transaction here. Everything else
is framework and artifacts.

The thing is that any existing middleware can be ported to the
handler-interface, without losing any functionality at all, and while
making it's dependency on a delegate (or absence thereof) explicit.

Take a trivial example like this middleware that adds a header to the
response:

class XHelloMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request,
DelegateInterface $delegate) {
        return $delegate->process($request)->withHeader("X-Hello", "Hello
World");
    }
}

We can extract a handler from this middleware, and the actual middleware
becomes just a dumb dispatcher:

class XHelloHandler implements HandlerInterface {
    private $delegate;
    public function __construct(HandlerInterface $delegate) {
        $this->delegate = $delegate;
    }
    public function handle(RequestInterface $request) {
        return $this->delegate->handle($request)->withHeader("X-Hello",
"Hello World");
    }
}

class XHelloMiddleware implements MiddlewareInterface {
    public function process(ServerRequestInterface $request,
DelegateInterface $delegate) {
        $handler = new XHelloHandler($delegate);
        return $handler->handle($request);
    }
}

Note that this won't work because the interface names (DelegateInterface,
HandlerInterface) are different - but the method signatures are identical,
so ignore that for a moment and consider the facts.

The XHelloMiddleware in this example does nothing of any relevance to the
task at hand - the XHelloHandler is doing all the work.

The difference is form - not function.

Which I think demonstrates pretty clearly that PSR-15, as it stands, is
dictating more than function - it's dictating form.

It's a framework in disguise.

That is to say, you could factor away the extra interface, factor away the
dispatcher, and still have it perform an identical function.


On Tue, Apr 25, 2017 at 12:14 PM, David Négrier <david.negr...@gmail.com>
wrote:

> Hey guys,
>
> I think the main concerns with the approach highlighted by Rasmus have
> been raised by Matthieu:
>
>
> - AFAIK it didn't work. And it was not tried in some weekend project: it
>> was built on Symfony's interfaces, so pretty solid stuff and it got a good
>> chance for success. On the other hand PSR-7 middlewares were incompatible
>> with most of the existing architectures at the time (incompatible with
>> Symfony for example) and without a standard interface (rather a "callable"
>> convention) and it worked out! So to me that's a clear sign.
>> - it's harder to compose middlewares because they are all nested: the
>> "pipe" pattern was a huge help IMO in spreading the concept
>> - it's harder to write reusable middlewares because you need to
>> standardize the constructor (see the Stack "convention" about the
>> constructor which is less than ideal)
>> - it's harder to wire in DI containers
>> - lazy middlewares are doable but not as easy
>
>
>
> Yet, I understand concerns raised by Rasmus, especially regarding the fact
> that some middlewares may very well have several delegate (just imagine a
> middleware that dispatches on several middlewares based on the domain name
> for instance).
>
> I just came up with another idea that might solve both problems. I'd be
> interested in your feedback.
>
> The idea is this:
>
> - let's adopt a MiddlewareInterface the way StackPHP was doing it.
> - let's provide a StackedMiddlewareFactoryInterface that can build a
> middleware when passed the "next" middleware. Those
> StackedMiddlewareFactoryInterface could be stacked/piped.
>
>
> interface ServerMiddlewareInterface
> {
>     public function __invoke(ServerRequestInterface $request) :
> ResponseInterface
> }
>
>
> interface StackedMiddlewareFactoryInterface
> {
>     public function createMiddleware(ServerMiddlewareInterface $next) :
> Middleware
> }
>
>
> Middlewares that have several "next" middlewares (because they are
> actually dispatchers) would only provide an implementation for the
> ServiceMiddlewareInterface.
>
> A typical implementation would look like this:
>
> class SessionMiddleware implements ServerMiddlewareInterface
> {
>     private $next;
>
>     public function __construct(ServerMiddlewareInterface $next)
>     {
>         $this->next = $next;
>     }
>
>     public function __invoke(RequestInterface $request)
>     {
>         // Do some stuff
>
>         // Then call the next middleware
>         return $this->next($request);
>     }
> }
>
> class SessionMiddlewareFactory implements StackedMiddlewareFactoryInterf
> ace
> {
>     public function createMiddleware(Middleware $next) : Middleware
>     {
>         return new SessionMiddleware($next);
>     }
> }
>
>
> This solves both issues raised by Matthieu and by Rasmus.
>
> A typical "server" would accept an array of middleware factories (we are
> piping factories instead of piping middlewares). It would then be able to
> wire all the middlewares together starting with the last in the array and
> going up.
>
> This plays nice with DI containers, and is easy to understand.
>
> What do you think?
> David.
>
>
>
>
> Le lundi 24 avril 2017 13:59:35 UTC+2, Rasmus Schultz a écrit :
>>
>> Having just replied, I now realize what you probably meant, haha ;-)
>>
>> You're saying the name of the interface should be handler, not
>> middleware, right?
>>
>> So yeah, I agree - some handlers may be middleware, but that's the role
>> of the component in some context, it's not what makes it a handler.
>>
>> I honestly hadn't thought much about the interface name - was more
>> concerned with presenting the concept :-)
>>
>>
>>
>> On Mon, Apr 24, 2017 at 1:57 PM, Rasmus Schultz <ras...@mindplay.dk>
>> wrote:
>>
>>> Hi Michael,
>>>
>>> I'm at work right now, so I'll just comment on middleware pattern issue.
>>>
>>> Yeah, I think shelf is what I was looking at when I picked up the idea.
>>>
>>> From the documentation:
>>>
>>> > A handler is any function that handles a shelf.Request and returns a
>>> shelf.Response. It can either handle the request itself--for example, a
>>> static file server that looks up the requested URI on the filesystem--or it
>>> can do some processing and forward it to another handler--for example, a
>>> logger that prints information about requests and responses to the command
>>> line.
>>> >
>>> > The latter kind of handler is called "middleware", since it sits in
>>> the middle of the server stack.
>>>
>>> So I'm not the only who thinks it's fair to call this "middleware".
>>>
>>> The fact that many other types of handlers would be able to adhere to
>>> the same interface signature should be additional huge benefit - a router,
>>> for example, could implement that interface directly, making it useful as a
>>> handler anywhere, not just as middleware.
>>>
>>> It's one of the things I like about this pattern - it fits for so many
>>> different scenarios, which means these components will potentially useful
>>> in so many cases.
>>>
>>> We'll need that interface under any circumstances - I would be totally
>>> fine with terming it a handler rather than middleware interface, because it
>>> is incredibly useful, not just for building a middleware stack...
>>>
>>>
>>> On Mon, Apr 24, 2017 at 1:38 PM, Michael Mayer <mic...@schnittstabil.de>
>>> wrote:
>>>
>>>> On Friday, April 21, 2017 at 10:12:12 PM UTC+2, Rasmus Schultz wrote:
>>>>>
>>>>> Hi Michael :-)
>>>>>
>>>>> > As a handler objects holds a reference to the next handler object,
>>>>> it is not reusable anymore.
>>>>>
>>>>> Well, the instance itself isn't "reusable" - but the code is, that's
>>>>> what matters, isn't it?
>>>>>
>>>>
>>>> Not always, I believe the current PSR-15 is superior in a React
>>>> <https://github.com/reactphp/react> environment. On first glance:
>>>>
>>>>    1. less memory usage
>>>>    2. better GC
>>>>
>>>>  – but maybe I'm wrong on that.
>>>>
>>>>
>>>>> Callables in PHP are also objects, and a quick benchmark indicates
>>>>> literally no difference between a closure and an idempotent functional
>>>>> component - the creation overhead is more or less identical:
>>>>>
>>>>> https://gist.github.com/mindplay-dk/0d4e073179fedb9bb10663c3ffe22336
>>>>>
>>>>
>>>> At first, you did it wrong :-) – see my gist comment. Secondly, you've
>>>> mixed my two arguments, but my main concern was about naming things right,
>>>> not about performance. To me Middleware is a pattern of Functional
>>>> Programming:
>>>>
>>>>    1. Middlewares are defined by lambdas/__invoke/…
>>>>    2. Middlewares use Continuation-Passing Style (CPS)
>>>>    <https://en.wikipedia.org/wiki/Continuation-passing_style>:
>>>>    $next encapsulates the remaining computation
>>>>
>>>> You know, I like the pattern you are proposing, but calling it
>>>> Middleware does not fit to me: it is pure OOP. Calling it Middleware might
>>>> sound sexy, but would need intense mental gymnastics to see a FP-Pattern.
>>>>
>>>> I'm only aware of shelf <https://pub.dartlang.org/packages/shelf> in
>>>> the Dart world:
>>>>
>>>> Middleware can be thought of as a function that takes a handler and
>>>>> wraps it in another handler to provide additional functionality.
>>>>
>>>>
>>>> Hence, I'm not sure which Dart framework you are referring to (Btw,
>>>> that is the same idea as Pythons Django
>>>> <https://docs.djangoproject.com/en/1.11/topics/http/middleware/> 
>>>> Middlewares,
>>>> only the names differ).
>>>>
>>>> Applying this idea to PHP, one probably comes up with these interfaces:
>>>>
>>>> interface RequestHandler {
>>>>     function __invoke(ServerRequestInterface $request);
>>>> }
>>>>
>>>> interface Middleware {
>>>>     function __invoke(RequestHandler $handler) : RequestHandler;
>>>> }
>>>>
>>>> That Middleware interface also solve some __constructor issues, but
>>>> obviously come with other disadvantages.
>>>>
>>>> *tl;tr:* I love that pattern, but calling it Middleware sounds wrong.
>>>>
>>>> About all your one-to-many successor concerns: I fully agree, and a
>>>> Router is a good example which should not be implemented as a Middleware
>>>> and moreover should not dispatch Middlewares. The current design I'm using,
>>>> can be pictured as:
>>>>
>>>>
>>>> <https://lh3.googleusercontent.com/-aKVlR3yNCRk/WP3g7-1mscI/AAAAAAAAAGw/CRqiwo7l0SEWSN2KQShftL0mljCTsF47QCLcB/s1600/RequestHandlerVsMiddleware.png>
>>>>
>>>>
>>>> IMO, treating everything as Middleware is just as wrong as treating
>>>> everything as RequestHandler. I want to use Middlewares to implement
>>>> cross-cutting concerns, which justifies that they sit in the middle of the
>>>> application stack, e.g. the LogMiddleware in the picture above. However, it
>>>> would be strange to create multiple LogMiddleware instances: we have only
>>>> one single log file. And it would also be strange, if LogMiddleware has a
>>>> successor property, because that way it could only log things of a single
>>>> successor.
>>>>
>>>> Michael
>>>>
>>>> --
>>>> 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/to
>>>> pic/php-fig/B3jtdJA7-6w/unsubscribe.
>>>> To unsubscribe from this group and all its topics, send an email to
>>>> php-fig+u...@googlegroups.com.
>>>> To post to this group, send email to php...@googlegroups.com.
>>>> To view this discussion on the web visit https://groups.google.com/d/ms
>>>> gid/php-fig/267f5a3d-2e5a-4db3-b41e-f90cdf149e37%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/php-fig/267f5a3d-2e5a-4db3-b41e-f90cdf149e37%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 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/b03dbdfc-8fb8-4e7a-9a5b-597fe22f0316%40googlegroups.com
> <https://groups.google.com/d/msgid/php-fig/b03dbdfc-8fb8-4e7a-9a5b-597fe22f0316%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_iF6gS5ZWKZ2B1Dz7zq776U_vcUzPwE3fy1w_aR07wYEA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to