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 StackedMiddlewareFactoryInterface { 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 > <javascript:>> 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 >> <javascript:>> 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/topic/php-fig/B3jtdJA7-6w/unsubscribe. >>> To unsubscribe from this group and all its topics, send an email to >>> php-fig+u...@googlegroups.com <javascript:>. >>> To post to this group, send email to php...@googlegroups.com >>> <javascript:>. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/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 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/b03dbdfc-8fb8-4e7a-9a5b-597fe22f0316%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.