Minimal HTTP middleware

1,347 views
Skip to first unread message

Rasmus Schultz

unread,
Apr 21, 2017, 11:42:11 AM4/21/17
to PHP Framework Interoperability Group
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?

Beau Simensen

unread,
Apr 21, 2017, 12:40:33 PM4/21/17
to PHP Framework Interoperability Group


On Friday, April 21, 2017 at 10:42:11 AM UTC-5, Rasmus Schultz wrote:

$kernel = new NotFoundMiddleware();
$kernel = new RouterMiddleware(new Router(...), $kernel);
$kernel = new CacheMiddleware($kernel);
$kernel = new ErrorHandlerMiddleware();


Off the top of my head, it feels like this would be difficult to automatically wire with a DI container since each middleware will need to be constructed with the previous middleware already instantiated. Especially given you cannot have consistent known arguments, this will be difficult to automate.

return $this->container->get($id)->process($request);

I think you tried to address this with the ContainerProxyMiddleware but it skips the construction part. How would the container know which $kernel to use in the constructor for the object at $id? This looks nice, API-wise, but it is ignoring how this object would actually be constructed.

One of the things we did with Stack was required the first argument to always be the kernel and any additional arguments (if any at all) would have to follow after that. We used Stack Builder to help try and solve those problems. It still seemed messy and I was never very happy with it.

Rivera, John

unread,
Apr 21, 2017, 1:07:44 PM4/21/17
to php...@googlegroups.com
This is exactly how I handle most of my middleware as well — I have a project that uses the CQRS architectural pattern, and I decorate each command handler using this exact pattern.

I build the dependency graph in my dependency injection container, and use a mediator to dispatch commands to their (decorated) handlers.

This is excellent for fine-tuned control over specific command handlers and their aspects.

However, I can see a use for the PSR-15 style middleware — I see two different kinds of middleware: fine-grained middleware (some requests needs to be authenticated, some needs to be within a database transaction, maybe one or two requests result in an event that you need to fire off, etc), and the general middleware (you want to log all requests, you want to catch any exceptions and handle them appropriately, etc).

I think the approach we are discussing here is ideal for fine-grained middleware — it makes the dependency graph explicit and easy to configure and maintain separately. PSR-15 is ideal for general middleware, which you can take a more functional approach to — there would (or should) only be a few layers, and they would apply to the application as a whole.

Just my two cents.
John

--
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+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/msgid/php-fig/99e602d1-e871-4d19-829a-1330252b508c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Rasmus Schultz

unread,
Apr 21, 2017, 1:37:48 PM4/21/17
to php...@googlegroups.com
How would the container know which $kernel to use in the constructor for the object at $id? This looks nice, API-wise, but it is ignoring how this object would actually be constructed.

Of course this "skips the constructor part", that's what the DI container is for - if you're using one, that's where construction happens.

The current proposal doesn't deal with the creation of middleware either, only with the dispatch.

So I'm not sure I follow.

We used Stack Builder to help try and solve those problems. It still seemed messy and I was never very happy with it.

That's pretty abstract, so can you point to (legacy) code or discussions, or illustrate with code examples maybe?


--
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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
Apr 21, 2017, 1:42:00 PM4/21/17
to php...@googlegroups.com
I'm not sure what you mean by "fine-grained" middleware?

This "fine-grained" middleware performs request-to-response processing the same as "general" middleware - they have the same method signature.

This is pretty abstract, so can you support this point of view with code-samples or scenario/use-case descriptions to illustrate?


To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@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/msgid/php-fig/99e602d1-e871-4d19-829a-1330252b508c%40googlegroups.com.
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Rivera, John

unread,
Apr 21, 2017, 2:03:50 PM4/21/17
to php...@googlegroups.com
Sure — here’s an example.

Let’s say you have a UploadBlogPost and a ViewBlogPost request. Let’s say the UploadBlogPost request takes an unique id and the post’s body and sticks it into a database. And let’s say ViewBlogPost takes an id, and returns the blog post associated with that id.

The UploadBlogPost request shouldn’t be sent in by just anybody. And what if the unique id isn’t so unique after all? So we write up, let’s say, an AuthenticatedRequest decorator and a TransactionalRequest decorator. The ViewBlogPost request, however, can be sent in by anybody, and it is read-only, so it doesn’t have to be transactional, either.

So for UploadBlogPost, you’d have the dependency injection container return a new AuthenticatedRequest(new TransactionalRequest(new UploadBlogPostHandler))); and new ViewBlogPost(); respectively. That’s what I meant by fine-tuned — you do not want none of that if ( $request instanceof UploadBlogPost) { … } else if ( $request instanceof ViewBlogPost ) { … } nastiness.

On the other hand, we want to log all requests that comes in, right? And what if that AuthenticatedRequest decorator throws an UserNotAuthenticatedException? We’d want to handle that before that thing bubbles up beyond the router, right? So those are things that apply to the application in its entirety — no matter what request comes in, we’ll always need those middlewares ready for action.

That’s where PSR-15 may make more sense. Let’s say we have this in the index.php file — a simplified example, not necessarily correctly implementing PSR-15, but you get the idea:

$stack = new Stack();
$stack->add(function ($request, $response) { */ does the logging goodness */});
$stack->add(function ($request, $response) { */ sends the request off to its handler. If the handler doesn’t like it, catch the exception and… */ });
$stack->add(function ($request, $response) { */ … send it to here, where the exception is handled. */ });

It wouldn’t make sense for a framework to provide fine-tuned middleware support, but a ‘one-size-fits-all’ middleware inter-op does make sense. PSR-15 allows a framework to “run” any middleware you may build without a mess of if-else statements.

The above example is probably not how I’d write it ‘in the real world’, but I hope you get the idea of what I’m trying to say :)

John

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
Apr 21, 2017, 3:09:03 PM4/21/17
to php...@googlegroups.com
I'm sorry, I don't really follow this explanation at all.

The purpose of middleware, as I understand it, is a means of structuring the processing of HTTP requests into discrete (reusable) units.

Authenticating a user is probably a domain concern.

Uploading or viewing a blog post are definitely domain concerns.

Those things belong in controllers - the glue between your domain model and the HTTP model.

Middleware should end with e.g. routing and the controller framework - at that point we're out of the HTTP domain, into the application domain.

What you describe, in my opinion, sounds like overuse of middleware, or a pretty big stretch at least?

The way I understand middleware, this is where systematic processing of HTTP requests take place, where routing (in some form) determines which controller to run, and then at that point, the controller provides the integration between the HTTP world and your domain - but this integration-layer is isn't systematic, it doesn't deal with HTTP requests at large, it only deals with one very specific HTTP request.

In other words, middleware classifies and filters requests - whereas controllers take a classified, filtered request, to which they must respond.

Middleware that involves domain knowledge, such as blog posts and transactions on those, can't be applied to HTTP requests in general, because they're coupled to your domain - from the description, I would classify those as controllers, which do not belong in a middleware stack.

I mean, that's my understanding - I realize all this stuff is pretty fluent, but I do get the impression that there's bit of a tendency to try to use middleware as an "application framework", turning everything into middleware in the name of reuse. But the way I understand it (from the way I've seen it used on other platforms) it's an HTTP abstraction, not a general-purpose framework for anything and everything you can think of.

I think that middleware fills a gap by providing a modular abstraction for classifying and filtering requests, which we didn't have before.

I've never viewed it as any kind of framework for more than that.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Rivera, John

unread,
Apr 21, 2017, 3:21:00 PM4/21/17
to php...@googlegroups.com
That makes sense — my view of middleware is more broad than that; it’s the application layer between the front end and the back end (domain) that controls access to the backend and how the data is conveyed to the front end. Hence ‘middle’ in the name :)

But your interpretation is just as valid, and after reading your reply, I remembered PSR-15 is called “HTTP Middleware”, not just “Middleware” *slaps forehead*

With that said, PSR-15 is the ‘cooler’ way of doing things, while your approach is more pragmatic. :)
John

To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
Apr 21, 2017, 3:22:51 PM4/21/17
to PHP Framework Interoperability Group
What you're proposing is known as a variant of the Chain of Responsibility Pattern – it's a GangOfFour-Pattern; hence should be well known.

Calling it Middleware disregards the fact that the Middleware pattern is known as a Functional Programming Pattern – every single Middleware Framework I'm aware of uses callables/lambdas/… (in PHP, Python, JS, you name it). Even StackPhp supports callables from the very first day (https://github.com/stackphp/CallableHttpKernel).

Except PSR-15 of course (which proposes `Middleware::process(…, DelegateInterface $delegate)`) – ugh.

You've encountered OO-Design at its best: A handler object knows the next handler(s) in the stack/queue/…, directly, and delegating request to it.
The problems of this approach are well known (Design Patterns: Elements of Reusable Object-Oriented Software was published more than 20 years ago).

The main disadvantage in this context I see: As a handler objects holds a reference to the next handler object, it is not reusable anymore.

For example if you have:
  1. a Middleware, which is used by multiple routes (but not all)
  2. you have internal redirects or gather responses from multiple routes
then you will create multiple instances of you middleware – even if your middleware is just an expensive to create idempotent functional component.

Bests,
Michael Mayer

Rasmus Schultz

unread,
Apr 21, 2017, 4:12:12 PM4/21/17
to php...@googlegroups.com
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?

you will create multiple instances of you middleware – even if your middleware is just an expensive to create idempotent functional component.

Creating an instance of a middleware component is likely (and should be) very inexpensive.

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:


Micro-performance concerns in my opinion don't really belong in a scripting language context to begin with, but if this helps you feel at ease... :-)

Creation overhead is most likely irrelevant in either case, in relation to anything useful the component could potentially do.

The practical difference between an instance of a (possibly anonymous) class implementing a single-method interface and a "real" callable/lambda, which you can't be type-hint in PHP, is largely cosmetic - both result in an object representing a function you can invoke, with slightly different syntax, but similar performance, which isn't typically where these features differ in scripting languages.

I feel you on that missing feature in PHP, but micro-performance and cosmetics aside, let's talk about real, practical, tangible differences between these approaches.

My main interest is to find the simplest, most correct, most versatile approach.

I think this is definitely simpler, right? One interface, no framework necessary. Granted, a better understanding of the middleware pattern is needed to make good use of it - but the need to understand the mechanics of a dispatcher (and "pipes" for non-linear compositions) is removed.

More correct? I think so:

1. The interface accurately describes the net transaction you intend to have with an HTTP kernel: takes a request, returns a response - and that happens to fit for middleware.

2. Constructors naturally indicate whether or not a middleware component may need/want to delegate control elsewhere.

More versatile? Looks like you can more freely compose trees or graphs, not just flat stacks, without also needing a "pipe" implementation - probably some research/experimentation is needed to evaluate this in practice.

Can we point at anything about this pattern that leads to more complexity, practical problems? Anything for which you can demonstrate a clear advantage when putting two implementations of the same HTTP kernel / stack side-by-side for comparison?

That's probably where we need to go next, if you think this is worth exploring.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Matthieu Napoli

unread,
Apr 21, 2017, 4:17:55 PM4/21/17
to PHP Framework Interoperability Group
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

Woody Gilk

unread,
Apr 21, 2017, 4:40:41 PM4/21/17
to PHP Framework Interoperability Group

Rasmus,

I get where you are coming from here. On the one hand it makes a lot of sense and *appears* to greatly simplify things.

However, I'm not sure that it really does in the long term. Creating a dependency graph of middleware complicates things significantly at the injection level. We go from having a flat list of middleware to an arbitrarily (from execution standpoint) nested collection. This makes it nearly impossible to rely on reflection based injection, and it makes it harder to rearrange to order of middleware.

There's also the issue of constructor parameters... There is no guaranteed place in which the "next" will be placed. Since it is impossible to enforce constructors via interface this will mean inspecting each middleware to determine the correct parameter. Injection systems might be able to help here, but it will still be messy.

Overall I think going this route will make things more complicated, more coupled, and harder to explain.

Regards,
Woody


--
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+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--

Rasmus Schultz

unread,
Apr 21, 2017, 4:48:14 PM4/21/17
to php...@googlegroups.com
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:


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?


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
Apr 21, 2017, 5:06:13 PM4/21/17
to php...@googlegroups.com
Since it is impossible to enforce constructors via interface this will mean inspecting each middleware to determine the correct parameter

That problem is not specific to middleware though.

You're just describing a general problem in dynamic languages, for which you can either use an IDE (or other static analysis tools) - or put up.

We go from having a flat list of middleware to an arbitrarily (from execution standpoint) nested collection

Valid.

I'll have to sleep on that :-)


To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@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/msgid/php-fig/d4483c85-4cea-41c2-b46b-9af86df9de63%40googlegroups.com.
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Matthieu Napoli

unread,
Apr 21, 2017, 5:30:10 PM4/21/17
to PHP Framework Interoperability Group
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:


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

OK I've started to re-read the mega-thread (https://groups.google.com/forum/#!topic/php-fig/vTtGxdIuBX8%5B101-125%5D - 7 pages long) but most of the debate was indeed about lambda-style/double-pass, and I'm clearly not masochistic enough to read all of it again ^^

So yes maybe we should discuss that (to me it feels like "again" but maybe that's just me).
 
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.

So yes even if it was discussed or not, the fact that it lacks from the metadocument is a sign there is something to improve here.

My 2 cents about the Stack approach:

- 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

That's all I have on my mind right now. And we may or may not have discussed it before, but the PHP community has already tried it. That kind of middleware has existed for a long time, and right now what works and what's spreading is not that kind of middlewares. I believe that's because of good reasons and we should not ignore that and start all over again.

Matthieu

Rasmus Schultz

unread,
Apr 22, 2017, 5:28:31 AM4/22/17
to php...@googlegroups.com
Having slept on it, I see that you have valid concerns to which I don't have good solutions, at least not at this time.

However, it seems that all of those concerns stem from two fixed ideas: 

1. Creating individual middleware components must happen on-the-fly, possibly coupled to a DI container.

2. The composition of a middleware stack is somehow easier to understand or work with when represented as a flat stack or list.

Neither of those ideas are fixed in my head, perhaps that's where our perspectives differ.

I think we have very different ideas about what middleware can or should be used for.

To me, it's a modular approach for classification and filtering of HTTP requests - it's not a new kind of application framework.

The projects I've been building with middleware the past two years largely consist of something like:

1. Error handler
2. Application dispatch (router, controller dispatch)
3. 404 page

In some cases, there's a middleware for caching etc. in there, or a logger - so with that, I think the most I've ever used is 5 components.

I long since gave up on trying to put things like routing and session-management in there as separate components from application dispatch - the component I call application dispatch handles both routing, sessions and a cookie abstraction.

If that sounds like a lot to put in a single component, it's not - we're literally talking about 25 lines of (trivial) code: invoke the router, create the session and cookie abstraction and inject them into a request context (a DI container), create the controller, and run it.

Trying to spread those concerns across several components feels like "doing the right thing", because separation of concerns, right?

But the more I thought about it, the more I found that this point of view is actually contrived.

Classifying the request actually ends with the router - once the path/method have been mapped to a controller, the request has been classified, and we're ready to dispatch - we're out of the HTTP domain into the application domain.

At that point, things like the cookie and session abstractions are dependencies of my controllers - they're how I choose to model and integrate with the HTTP domain on a high level.

There's no reason that needs to be middleware. It's contrived, and only works because you put those middlewares in the correct order, so they can communicate - which is to say, they're really acting as a single component, they can't operate correctly in isolation (or out of order) and the apparent separation is only skin deep.

Components like this one, for example, don't make sense to me:


It's a vestigial router integration that doesn't actually route to anything - it doesn't dispatch anything.

In fact, it doesn't really do anything - not directly - it only produces side-effects, by injecting the result into attributes, which is a means of communicating it's result to the component that is going to actually dispatch the controller. Clearly one is a dependency of the other.

It's a clever way to hide your dependencies, but they're still there - only hidden, and much harder to debug. What does that accomplish?

I mean, I get it, you want everything to be modular - you want things such as different router implementations to "plug and play" so you can just throw them on the stack and have them work.

But that's not really possible - it doesn't really work that way, and implementing it as middleware doesn't change that fact, it just hides that fact. You haven't managed to abstract from anything - the dependency of some other component, on the result produced by this one, is still there, you've only managed to hide that fact. It's throwing complexity after more complexity - fighting fire with fire.

You don't get modularity - what you get is fragmentation.

I'm stomping on this particular case because it's a perfect example of what I would call middleware abuse.

If you'd stop trying to do that, your typical application would have much fewer middleware components.

It wouldn't matter (much) if you were creating the individual middleware components on-the-fly or not.

Your application would bootstrap a single HTTP kernel (in a DI container) composed of some number of proxies/decorators.

I think where we differ is, you want to actually build the application itself out of middleware components? I think that's contrived. The DI container and abstractions is a better way to structure the application itself.

To me, middleware is just a better, more structured way to classify and filter HTTP requests - it's a well-structured approach to building front-controllers. 

It's not a "framework" or "architecture" for applications - that's a different layer.

To me, a simple transaction like "takes a request, returns a response" perfectly describes a front-controller, which is all I need to do at this layer.

I don't know, maybe I just have a very different idea of middleware from most people :-)


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
Apr 23, 2017, 8:44:27 AM4/23/17
to php...@googlegroups.com
I'd like to highlight a practical example of a case where the proposed middleware standard creates problems.

I had this one middleware already built:


It basically takes a root-path and, if a given path matches a file in that folder, it renders a markdown-file.

I had this other project, which also implements a piece of middleware, and it needs to do that for markdown files, but only under certain conditions - sometimes it renders a markdown file, sometimes it does something else.

Could I refactor and extract a service or something to make that possible? Sure, but the component was already there, and it already does exactly what I need.

The only problem was it's interface, which requires me to pass a delegate as the second argument, even though I know it won't need to delegate - the component that invokes it has already checked for the existence of the file; if it had to delegate, that would mean there's an error in the component that calls it.

If I want to reuse that middleware, I have two choices:

1. Implement a "fake" instance of DelegateInterface - it won't do anything, it'll just satisfy the delegate argument.

2. Pull out a middleware stack (or "pipe") and dispatch the middleware through it.

The first solution is an ugly work-around. Having to write a fake implementation of DelegateInterface and having to explain why it's there points at something being wrong, right?

The second solution is probably the more correct, but has it's own problems: the middleware that calls the markdown middleware now needs to depend on a specific implementation of a middleware-stack/pipe.

Essentially, the markdown middleware is a lambda - a function - it's responsibility is to take a request and return a response, but it's method signature makes it difficult to compose it by simply running it.

The proposed middleware interface effectively declares "there will always be a delegate", which we know to be false - there is sometimes nothing to delegate to, and attempting to delegate will trigger an exception, or some other behavior defined by the middleware stack.

In other words, one of the reasons we need the middleware stack in the first place, is to account for incidents where reality doesn't match what was declared by the formal interface.

We've effectively managed to hide and important piece of information from middleware components: we've made it impossible for a middleware component to know if there's another component to delegate to or not. It has to assume that there is - even though we know this to be false.

We've also made it mandatory for middleware components that don't delegate to receive an unused delegate argument - the 404-middleware, for example, has to consider a given delegate and ignore it.

The fact is that, some middleware depends on a delegate, and some middleware does not - but we've effectively removed the ability for middleware to declare it's own dependency, or non-dependency, on a delegate.

We've also removed the ability for middleware to depend on multiple delegates, except by clumsy work-arounds as described above, for a middleware component that conditionally depends on one of two other middleware components. (if you think that's an unusual or exotic scenario, it's because that's not really practical or possible with the current middleware proposal, so you likely wouldn't even consider it.)

Contrast this with the simple interface, which places the responsibility on the middleware component itself to declare it's dependencies, via the constructor, you know, like normal components. I could have used a constructor like:

    public function __construct(MarkdownMiddleware $mm, Middleware $next = null)

That is, this middleware optionally takes a delegate - it's up to the middleware to define it's own default behavior (404 page? exception? who knows) since it has made the delegate optional. If somebody doesn't like the default behavior, they can pass in a delegate and define their own.

Internally, the middleware can simply call either of these middlewares, without constructing a fake delegate or depending on a middleware stack to do it.

It's not just "lambda-style", it's a true lambda: takes a request, returns a response.

The reason we were able to turn the "onion" structure into a flat list, is because we're ignoring two facts:

1. It's not always a list (and when it's not, the complexity that hides that fact just breeds more complexity)

2. There is not always one delegate (sometimes zero, sometimes one, sometimes zero or one, sometimes more)

It would be nice if it was always a flat list, and every component had exactly the same dependencies, but that's just not reality - software in general tends to be more structured than that.

It's an onion structure alright, but sometimes an onion looks more like this:


I'm afraid we're trying to over-simplify - I'm just not convinced we'll make things simpler for anyone by attempting to hide complexity with more complexity.

I also think we have a tendency to overrate the importance of making it easy to consume middleware. How much of your time is spent creating or modifying a middleware stack? You created one per project, right? So like, 1% of your time? And I think that's high.

Hopefully, most of your time is spent building the actual domain, not the middleware in front of it.

I'm a big believer in simple over easy - for something like database interactions or rendering/validating forms, stuff you do all day long, I think you can justify some complexity in favor of making things easy, but for this?

You're going to be spending very little time actually bootstrapping middleware day-to-day - you'll spend a lot more time writing middleware and the stuff behind it than you will simply configuring the middleware components for your project.

I'm afraid we may be doing ourselves a huge disservice in the long run by favoring easy over simple.

Michael Mayer

unread,
Apr 24, 2017, 7:38:45 AM4/24/17
to PHP Framework Interoperability Group
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 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:


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): $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 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 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:



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

Rasmus Schultz

unread,
Apr 24, 2017, 7:57:07 AM4/24/17
to php...@googlegroups.com
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...


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
Apr 24, 2017, 7:59:35 AM4/24/17
to php...@googlegroups.com
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 :-)


David Négrier

unread,
Apr 25, 2017, 6:14:25 AM4/25/17
to PHP Framework Interoperability Group
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.


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.

Rasmus Schultz

unread,
Apr 25, 2017, 7:55:38 AM4/25/17
to php...@googlegroups.com
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:


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:


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.


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

David Négrier

unread,
Apr 25, 2017, 11:05:06 AM4/25/17
to PHP Framework Interoperability Group
> 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'm only proposing an additional interface that allows creating a middleware when the "next" middleware is known. It allows for DI containers to deal with an array of middlewares (actually an array of middleware factories) rather than having to compose middlewares manually.

Without this factory interface, you are the one actually dictating that the users of middleware MUST set it up manually in their DI container (and it's truly not an easy  task). As you said, you are going to do this only once for your project, but what seems "simple" to you will be a task overwelmingly complex for the majority of PHP users out there (not everybody is a middleware specialist). You and I will have no problem configuring a DI container in this special "onion" shape, but most developers will be lost.

I strongly agree with Matthieu when he says that the "pipe" pattern was a huge help IMO in spreading the concept. It is "easy". I know you hate "easy", I know you prefer "simple" but seriously, "easy" is important (otherwise, Laravel would not have millions of downloads)

So there, you have my proposal:

- "ServerMiddlewareInterface" => simple interface
- "StackedMiddlewareFactoryInterface" => allows for easy "piping" of middlewares (very important for the adoption!)

You don't have to use the factories if you don't like them. You can stay with the bar MiddlewareInterface, but I'm pretty sure that being able to pipe middlewares as we currently do in PSR-15 is important.

++
David.

Rasmus Schultz

unread,
Apr 25, 2017, 11:53:44 AM4/25/17
to php...@googlegroups.com
You're still working from the assumption that each individual middleware needs to be bootstrapped individually in the DI container.

Yes, creating the "onion shape" is much harder this way, and the resulting code will be quite difficult to read.

If you can instead wrap your head around the idea of a single registered handler - a single entry in the DI container - this is much simpler than what you're describing, and much simpler than what the current middleware interface dictates.

It's really very simple - you just need to let go of the idea of middleware as individual components, because they're not. What the dispatcher accomplishes currently, is it makes individual middleware components effectively act like a single handler - with that being the end goal, it's much simpler to start from there instead of starting somewhere else and entirely and trying to get there.

$container->register(HandlerInterface::class, function () {
    return new ErrorHandlerMiddleware(
        new CacheMiddleware(
            new RouterMiddleware(
                new Router(...),
                new NotFoundMiddleware()
            )
        )
    );
});

Easier or harder than this?

$container->register(ErrorHandlerMiddleware::class, function () { .... });

$container->register(CacheMiddleware::class, function () { .... });

$container->register(RouterMiddleware::class, function () { .... });

$container->register(NotFoundMiddleware::class, function () { .... });

$container->register(Dispatcher::class, function (Container $c) {
    return new Dispatcher([
        $c->get(ErrorHandlerMiddleware::class),
        $c->get(CacheMiddleware::class),
        $c->get(RouterMiddleware::class),
        $c->get(NotFoundMiddleware::class),
    ]);
});

This doesn't look easier or simpler to me - it looks a whole lot more complicated.

It doesn't reflect the onion structure at all - and if any of those middlewares have multiple delegates and internally use pipes/dispatchers, it's going to be far more complicated than this.

Is it okay for middleware that needs a pipe/dispatcher internally to create their own pipe/dispatcher, or do we need a DispatcherFactoryInterface as well, so that middleware can abstract from the pipe/dispatcher implementation? Where does it end??

You can't mask complexity with more complexity.

We should make things as simple as possible, but no simpler.

Middleware is not quite as easy as you'd like it to be - in order to make it more palatable to the casual programmer, you want to make it easy, I get that.

But in my opinion, you're designing a framework - those concerns are unrelated to interop and don't belong in a PSR.

You can still make a framework that makes this easier and more palatable for casual programmers if you want - dictating that extra complexity by mandating the use of pipes/dispatchers (and dependency issues) doesn't need to be a thing.

At the very least, we should standardize on a HandlerInterface, so that PSR-15 doesn't ship with an identical, incompatible interface for delegates.

At that point, developers would at least be able to choose whether or not they ship their middleware with a handler.


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
Apr 25, 2017, 4:44:14 PM4/25/17
to PHP Framework Interoperability Group
At the very least, we should standardize on a HandlerInterface, so that PSR-15 doesn't ship with an identical, incompatible interface for delegates.
 
I couldn’t agree more.

Rasmus Schultz

unread,
Apr 26, 2017, 1:39:46 AM4/26/17
to php...@googlegroups.com
I've set aside friday to write a draft of the handler PSR. We'll see how FIG and the community responds and then work from there. 


On Apr 25, 2017 22:44, "Michael Mayer" <mic...@schnittstabil.de> wrote:
At the very least, we should standardize on a HandlerInterface, so that PSR-15 doesn't ship with an identical, incompatible interface for delegates.
 
I couldn’t agree more.

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
Apr 26, 2017, 4:30:19 AM4/26/17
to PHP Framework Interoperability Group
Well, some work is already done:
Currently, the main difference, which is still up for discussion(!):
  • ServerAction:__invoke instead of Handler:handle
Some arguments for and against __invoke:
I know, Rasmus and I disagree on __invoke, but I'm willing to go with the majority. For this reason, I invite Rasmus as an organisation member and everybody else who is interested to participate.

Bests,
Michael


On Wednesday, April 26, 2017 at 7:39:46 AM UTC+2, Rasmus Schultz wrote:
I've set aside friday to write a draft of the handler PSR. We'll see how FIG and the community responds and then work from there. 

On Apr 25, 2017 22:44, "Michael Mayer" <mic...@schnittstabil.de> wrote:
At the very least, we should standardize on a HandlerInterface, so that PSR-15 doesn't ship with an identical, incompatible interface for delegates.
 
I couldn’t agree more.

--
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.

Rasmus Schultz

unread,
Apr 26, 2017, 5:19:50 AM4/26/17
to php...@googlegroups.com
Man, I knew something like this had been started - I looked everywhere and couldn't find it.

Thanks :-)


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Beau Simensen

unread,
Apr 28, 2017, 12:08:40 PM4/28/17
to PHP Framework Interoperability Group
On Friday, April 21, 2017 at 1:37:48 PM UTC-4, Rasmus Schultz wrote:
How would the container know which $kernel to use in the constructor for the object at $id? This looks nice, API-wise, but it is ignoring how this object would actually be constructed.

Of course this "skips the constructor part", that's what the DI container is for - if you're using one, that's where construction happens. 

The current proposal doesn't deal with the creation of middleware either, only with the dispatch.

So I'm not sure I follow.


Since construction using this model implies that each middleware has to know about the previous, it makes sense to consider how a DI container would be able to handle that case. Unless you can show how it can easily be done, I think it makes this proposal difficult to swallow for people who want to be able to middleware as a basis for a framework.

 
We used Stack Builder to help try and solve those problems. It still seemed messy and I was never very happy with it.

That's pretty abstract, so can you point to (legacy) code or discussions, or illustrate with code examples maybe?

It looks like I never completed STACK-1, but it comes down to the rules of building a Stack middleware:
  1. Extend HTTP Kernel Interface
  2. Constructor MUST take an HTTP Kernel Interface as the FIRST argument
This worked for the simple case you are describing where you can have full control over every piece of middleware and you can construct it inline.

Since we had the constructor rule defined, we were able to make Stack Builder construct a stack somewhat dynamically. It wasn't, however, able to leverage the containers very easily to build out any dependencies the middleware themselves might have.

Both Laravel and Drupal 8 started to use Stack PHP. Laravel ended up ditching Stack-compatible middleware because they wanted to be able to build the middleware from the container. Drupal 8 doesn't use Stack Builder as they also want to be able to construct the middleware from the container (Symfony's, in this case).

Any framework that has extensions, modules, bundles, plugins or whatever that wants to implement PSR-15 middleware is likely going to want to allow the extensions, modules, bundles, plugins or whatever to provide middleware without requiring the application to manually configure the middleware. This is a bold statement, and feel free to call me out on this, but experience in this domain has shown me that this is the case.

I'm probably also biased. I'll admit that.

As for a real-world and easy-to-consume example of this in action for a primitive PSR-7 framework I put together a year and a half ago using Laravel's container, it looked like this. It allowed the application to register its own private middleware, it allowed for 3rd-party service providers to add additional middleware, and it allowed for the core framework to specify its core middleware.


class PrivateApiApp extends App
{
    public function registerServiceProviders(Container $container)
    {
        parent::registerServiceProviders($container);

        $container->tag(UserAuthentication::class, WebApp::MIDDLEWARE_EARLY);
        $container->tag(OrganisationAuthorization::class, WebApp::MIDDLEWARE_EARLY);
        $container->tag(UserIdMetadataEnrichment::class, WebApp::MIDDLEWARE_EARLY);
    }
}


class RelayServiceProvider {
    public function register(Container $container)
    {
        $container->bind(Relay::class, function (Container $container) {
            /** @var RelayBuilder $relayBuilder */
            $relayBuilder = $container->make(RelayBuilder::class);

            $queue = array_merge(
                [
                    $container->make(NikicFastRoute::class, [
                        'actionAttributeName' => WebApp::ACTION_ATTRIBUTE_NAME,
                        'parametersAttributeName' => WebApp::PARAMETERS_ATTRIBUTE_NAME,
                    ]),
                ],
                $container->tagged('nimble.middleware.error_handler'),
                $container->tagged('nimble.middleware.early'),
                $container->tagged('nimble.middleware'),
                $container->tagged('nimble.middleware.late'),
                [
                    $container->make(ResponseAssertion::class),
                    $container->make(ViewTransformer::class),
                    $container->make(ActionHandler::class, [
                        'actionAttributeName' => WebApp::ACTION_ATTRIBUTE_NAME,
                    ]),
                ]
            );

            return $relayBuilder->newInstance($queue);
        }
    }
}

Matthew Weier O'Phinney

unread,
May 9, 2017, 1:07:27 PM5/9/17
to php...@googlegroups.com
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.
> $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()
> )
> )
> );
>
> --
> 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+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/msgid/php-fig/674917bb-623a-4cea-b934-add8887acc65%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Matthew Weier O'Phinney
mweiero...@gmail.com
https://mwop.net/

Rasmus Schultz

unread,
May 9, 2017, 4:11:15 PM5/9/17
to php...@googlegroups.com
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:


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:


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:


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.



> To post to this group, send email to php...@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
mweiero...@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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Alessandro Pellizzari

unread,
May 9, 2017, 5:54:25 PM5/9/17
to php...@googlegroups.com
On 09/05/17 21:11, Rasmus Schultz wrote:

> 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?

This is a draft meta-code of the mw stack we use in out API (based on
Slim3):

```
$app
->add(Cors)
->group('/public', function() {
$this->add(Format)
})
->group('/sys', function() {
$this
->add(Version)
->add(Format)
->add(SysAuth)
})
->group('/v1', function() {
$this
->add(Version)
->add(Format)
->add(V1Auth)

// This is still in planning:
$this
->get('/files/{name}')->add(BinaryFormat)
})
;
```

A little explanation:

Cors checks the CORS headers before routing, and refuses the request
altogether if wrong, or adds headers to the response when needed.

Format checks the Accept header in the request before routing, and
transforms the output in JSON or XML in the Response after routing.

Version checks the middle and minor version in the Accept-Version in the
Request, and sets some Request parameters that some actions use to adapt
the response.

SysAuth authenticates automatic system via special tokens (for
health-like endpoints), and naturally returns 401 on unauthorized.

V1Auth autenticates "real" users of the APIs.

BinaryFormat is planned to override Format by checking if the file has
been requested in html or pdf, or in png or jpeg, or in other binary
formats in the future, and it's applied only on some specific routes.

This is very simple to achieve with the current `function($req, $res,
$next): $res` architecture. For example, this is (roughly) the Format mw:

```
$format = $this->parseAccept($req->getHeader('Accept'));

if (!in_array($format, ['json','xml'])) {
return $res->withStatus(406);
}

$response = $next($req, $res);

return $this->format($response, $format);
```

I am a bit confused by all the proposed alternatives, to be honest, and
would love to understand how this would be implemented in both.

Thanks.
Bye.

Beau Simensen

unread,
May 10, 2017, 9:19:53 AM5/10/17
to PHP Framework Interoperability Group

On Tuesday, May 9, 2017 at 4:54:25 PM UTC-5, Alessandro Pellizzari wrote:
On 09/05/17 21:11, Rasmus Schultz wrote:

> 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?


From my example earlier, my effective stack of middleware was:

    NikicFastRoute::class, // routing
    FixBrokenDiactorosCookieHeaderBehaviour::class, // workaround until a cookie fix to diactoros could be tagged
    CorsCompletelyOpenForDevelopment::class, // utility middleware that was soon to be swapped for a better proper impl
    UserAuthentication::class, // ensured that the request was made by an authenticated user
    OrganisationAuthorization::class, // ensured that the authenticated user was authorized to access organisation resources
    UserIdMetadataEnrichment::class, // used to ensure that the authenticated user would be added to event envelopes
    ResponseAssertion::class, // make sure that by this point any non-response views returned were now responses
    ViewTransformer::class, // transform non-response values into responses
    ActionHandler::class, // dispatcher


The first and last middleware were provided by the underlying framework and the other middleware were added as a part of the application itself.
 

Rasmus Schultz

unread,
May 10, 2017, 12:04:09 PM5/10/17
to php...@googlegroups.com
What's a "non-response value"?

You have "middleware" that doesn't return a Response object?


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Beau Simensen

unread,
May 10, 2017, 1:59:58 PM5/10/17
to php...@googlegroups.com
What's a "non-response value"? You have "middleware" that doesn't return a Response object?

It was something I was playing with at the time. It worked fine w/ Relay as Relay wasn't paying attention at all to what was returned from the middleware.

This allowed actions run from the action handler middleware to return values that were not response values that the view transformer middleware could pick up. Provided the view transformer middleware was able to handle whatever type object it got back from the delegate, it would transform it into a response. The response assertion middleware would throw an exception if a the response from its delegate was still not yet a ResponseInterface instance.

This was experimental on my part. I remember talking to Matthew about it at one point while I was working on it. I think it was an interesting idea but definitely not a mainstream way to do things. I felt as long as my framework was handling everything from ActionHandler to ResponseAssertion, I'd be safe.

I'll be the first to say that not all of my ideas are the best ones. :)

I've since run into other middleware pipeline projects that ensure every response is indeed a ResponseInterface instance. So I've adjusted this behavior so that this work is done elsewhere. If I could apply this new logic to the code I shared above, my stack would look like this:

    NikicFastRoute::class, // routing
    FixBrokenDiactorosCookieHeaderBehaviour::class, // workaround until a cookie fix to diactoros could be tagged
    CorsCompletelyOpenForDevelopment::class, // utility middleware that was soon to be swapped for a better proper impl
    UserAuthentication::class, // ensured that the request was made by an authenticated user
    OrganisationAuthorization::class, // ensured that the authenticated user was authorized to access organisation resources
    UserIdMetadataEnrichment::class, // used to ensure that the authenticated user would be added to event envelopes
    ActionHandler::class, // dispatcher



This goes into the discussion on creating a "handler" interface, I suppose. Requiring the handler to always return a response object means that the handler is going to have to do a lot. At the very least, the handler is going to need to have a ResponseFactory injected into it. Having the option to make my handlers support a hypothetical handler PSR would be nice, but I would probably end up using ad-hoc handlers that could return DTO/DomainObjects and have another layer that could convert those responses into HTTP Responses. But I think that is a discussion for another thread at this point.


Rasmus Schultz

unread,
May 10, 2017, 2:54:28 PM5/10/17
to php...@googlegroups.com
I think the case where you're returning the wrong (for middleware) result type, that's a pretty clear-cut case of non-middleware being shoehorned in as "middleware".

It sounds like you're doing that less now, but it honestly sounds like you're trying to make middleware your framework for a lot more than just the HTTP request/response processing abstraction.

In my opinion, if a middleware component doesn't work when taken out of context from other middleware, or is dependent on related middleware and execution order etc., it's no longer middleware, strictly speaking - at that point, you're overloading the middleware mechanism with responsibilities that don't belong to that domain and don't naturally fit there.

I suspect there is still some of that going on in your remaining stack? Like maybe you're using attributes to communicate between some of these middleware components?

As I've argued before, I think that, when several middleware components depend on execution order (side-effects) and on attributes as a means of hiding coupling and dependency between them, you don't get modularity - you only get fragmentation.

I don't think that discussing this is off topic - it's important we think about what's valid use-cases, what's reasonable or appropriate, because I have the strong sense that a lot of people in the PHP community are currently embracing the middleware concept a little too firmly.

I'm honestly starting to feel like a lot of the demands that people make of middleware stem from the misunderstanding that the middleware pattern is some kind of universal replacement for frameworks, and I think we have to be watchful of creating complexity to support scenarios for which middleware is actually inappropriate.

The point of having middleware, I think, is simply to abstract from processing an HTTP request and creating a response - if turning something into a middleware component isn't easy and natural, if it requires "creative" liberties, bending or breaking the rules, it's most likely the wrong abstraction for what you're trying to do. Having a pair of middleware components that implement two halves of your application, for example, does not provide any reuse or real modularity.

Not everything needs to (or should) be a middleware component. 


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Woody Gilk

unread,
May 10, 2017, 5:37:14 PM5/10/17
to PHP Framework Interoperability Group
On Wed, May 10, 2017 at 1:54 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:
In my opinion, if a middleware component doesn't work when taken out of context from other middleware, or is dependent on related middleware and execution order etc., it's no longer middleware, strictly speaking - at that point, you're overloading the middleware mechanism with responsibilities that don't belong to that domain and don't naturally fit there.

That's purely an opinion and I definitely don't agree about the execution order. Would this make sense:

middleware = [
  ActionHandler,
  ClientIp,
  Authorization,
]

Of course not! Some middleware will always need to be early or late in processing. Authentication and authorization will need to be early, and action handling will need to be late. I don't think it is at all natural to say "all middleware must be context free and middleware order never matters". That's simply unrealistic.

Rasmus Schultz

unread,
May 12, 2017, 6:49:36 AM5/12/17
to php...@googlegroups.com
I don't think it is at all natural to say "all middleware must be context free and middleware order never matters". That's simply unrealistic.

That's not what I'm saying.

Of course middleware components are going to communicate facts about the state of the Request and Response - that's the whole point.

Of course middleware order matters, as far as getting a meaningful result, I'm not trying to say otherwise.

What I'm saying is, if you use the Request object as a transport mechanism for your _domain_ objects, stuffed into reserved attributes, you're first of all using the Request object as a service locator, which is bad practice.

You're no longer communicating facts about the state of the Request or Response, you're just overloading the Request model as a means to avoid dealing with a dependency issue. Worse, you're hiding the fact that dependency exists, as is the general issue with service location.

There are always going to be better, cleaner, simpler, more transparent patterns that don't hide your dependencies or solve the problem by using the Request model as a kind of "container" for your domain objects.

IN MY OPINION, attributes in the Request object are widely abused as a general solution to all sorts of dependency issues, when, really, they probably ought not to be used for much else other than an additional source of parameters.

Per the PSR-7 definition, attributes hold "parameters derived from the request" - I find that stuffing your domain objects in there under reserved keys is a bit of a stretch, even if your domain objects were somehow "derived from the request", with examples given such as "the results of path match operations; the results of decrypting cookies; the results of deserializing non-form-encoded message bodies; etc.", I think it's quite a long reach.

Bottom line, if it doesn't really function like middleware, why did you implement it as middleware? Wrong abstraction for the job.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

David Négrier

unread,
May 13, 2017, 6:38:14 AM5/13/17
to PHP Framework Interoperability Group
Hey guys,

Catching up on the thread. Just read Matthew's and Beau's post as well as Rasmus ones.

I'm still surprised because I feel like I proposed a solution to the problem 2 weeks ago and nobody noticed (or cared to point me that I'm wrong).

So I'll try to explain it again.

Rasmus is annoyed by the current proposal because a middleware might have 0 or many delegates (but the current proposal only allows for one "main" delegate.
Yet, if we remove the delegate and go the "Stack" way, working with DI containers becomes a real mess. In particular, it becomes almost impossible to write "bundles" that ship middlewares that should be applied by a priority index. Or we need to rely on a "convention" (like next middleware being passed as first argument of the constructor), and we know conventions suck.

But we can really solve this:

interface ServerMiddlewareInterface // aka the HandlerInterface
{
    public function __invoke(ServerRequestInterface $request) : ResponseInterface
}


interface MiddlewareFactoryInterface
{
    public function createMiddleware(ServerMiddlewareInterface $next) : ServerMiddlewareInterface
}

The first interface is the "HandlerInterface" Rasmus is talking about. It is the "purest" thinkg we can imagine. A class that takes a request and returns a response.
But as we know, it does not play well with Pipes/Stacks. So rather that stacking middlewares (or handlers), I'm proposing we are stacking "Middleware factories". Factories are objects that create a middleware given the "next" middleware.

So this:

```php 
$app->route('/api/blog', [ 
    Authentication::class, 
    Authorization::class, 
    Blog::class, 
], ['GET', 'POST']); 
``` 

becomes this:

```php 
$app->route('/api/blog', [ 
    AuthenticationFactory::class, 
    AuthorizationFactory::class, 
    BlogFactory::class, 
], ['GET', 'POST']); 
``` 

From the list of factories, we can create back the middlewares.

Look at the signature of the factory:
    public function createMiddleware(ServerMiddlewareInterface $next) : ServerMiddlewareInterface

The factory will be passed the "next" middleware/handler (or a delegate/proxy, this is an implementation detail) and return the created middleware.

Doing things this way has a ton of advantages:

- it answers Rasmus concerns:
    - we are standardizing the "HandlerInterface" (or MiddlewareInterface, whatever the name)
    - Middlewares can have 0, 1 or many delegates, it does not matter regarding this interface
    - you can use only "Handlers/Middlewares" directly in your application and disregard middleware factories completely if you don't use them
- yet, it answers Matthew's concerns too:
    - it plays well with DI, as you can stack/pipe middleware factories
    - it does not rely on a convention
    - depending on the way you implement your framework, you can have lazy instantiation of the middlewares (for instance using proxies)

Even better, regarding performance, it can be quite quick too. Just imagine a scenario where you use ReactPHP (and therefore, middlewares are reused). Using middleware factories, the middleware "tree" is constructed once at the beginning of the application and then, the request is piped from one middleware to the next one without going through the "delegate" we currently have (so it should be faster in the case of ReactPHP).

This proposal looks a lot like Stack, but it removes the convention part and replaces it with a factory interface.

I'd be interested in having Beau and Matthew feedback on this idea. Do you see the benefit? Is something not clear? Or is there a flaw I did not see?

++
David.

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.

Rasmus Schultz

unread,
May 13, 2017, 8:00:38 AM5/13/17
to php...@googlegroups.com
Very good summary and well argued, thanks David!

The factory pattern you're proposing is very similar to what Shelf does in Dart - the only real difference is they're doing it with typedefs, but yes, we can accomplish effectively the same thing with interfaces.

In my opinion, this approach will provide considerably better modularity than the current proposal, because it will make it very easy to composer a few middlewares insides a middleware-factory, which in turn will make it very easy to host several of these multi-middlewares ("modules" if you like) as part of an application, whether manually bootstrapped or auto-magically by priority using some kind of framework.

I think this will provide more architectural freedom, and will make it much easier to write handlers or middleware that composes multiple handlers - e.g. being able to compose two or more low-level handlers or middleware into a more high-level handler or middleware, without needing to depend on a dispatcher or "pipe" implementation. This helps alleviate dependency issues too - if I ship a middleware internally composed of one or more internal handlers, I don't have to ship that component with a dependency on a specific dispatcher/pipe.

The only thing I disagree with is the term "middleware" - the handler-interface need not be designated as middleware-specific, it has many much more versatile uses. For example, a dispatcher or framework can provide both a handler for direct use, as well as factory that makes it easy to compose an instance of a sub-stack as part of your application-stack.

So we really do need to standardize on handlers first.



To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Woody Gilk

unread,
May 13, 2017, 12:52:19 PM5/13/17
to php...@googlegroups.com

David,

What I don't underhand about your proposal is how a middleware that does not return a response would be implemented. For instance, a middleware that sets parsedBody?

I also don't understand how this would solve the DI concern. It looks like any middleware that wants to continue processing would need a reference to the next middleware factory, as in:

return $this->next->createMiddleware($this)->process($request);

Perhaps you could provide some implementation details to help me understand how this would work?



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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--

Rasmus Schultz

unread,
May 13, 2017, 1:12:15 PM5/13/17
to php...@googlegroups.com
I don't understand the question.

What do you mean by "middleware that does not return a response"?

Middleware always returns a response - the interface return-type stipulates that.

I've never heard of middleware that does not return?


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

--
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+unsubscribe@googlegroups.com.
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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Woody Gilk

unread,
May 13, 2017, 1:31:20 PM5/13/17
to PHP Framework Interoperability Group
On Sat, May 13, 2017 at 12:12 PM, Rasmus Schultz <ras...@mindplay.dk> wrote:
What do you mean by "middleware that does not return a response"?

Ultimately it will. In PSR-15 this is done via delegate. My question is more about how the interfaces proposed by David would do this delegation. Right now I don't see how it would be possible without passing a factory to a middleware, which (I thought) he was attempting to avoid.

Rasmus Schultz

unread,
May 13, 2017, 2:22:06 PM5/13/17
to php...@googlegroups.com
Ultimately it will. In PSR-15 this is done via delegate.

What? How?

How does that even make sense? If it doesn't return a Response, how is it middleware? How would the Dispatcher or other middleware deal with a missing response?

Is there a change planned for PSR-15 or something? Can you point to an issue on github?

I don't get it.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
May 13, 2017, 3:26:50 PM5/13/17
to PHP Framework Interoperability Group
On Saturday, May 13, 2017 at 7:31:20 PM UTC+2, Woody Gilk wrote:
My question is more about how the interfaces proposed by David would do this delegation. Right now I don't see how it would be possible without passing a factory to a middleware, which (I thought) he was attempting to avoid.


interface RequestHandlerInterface
{
   
public function dispatch(ServerRequestInterface $request) : ResponseInterface;
}


interface Middleware {
   
function dispatch(RequestHandlerInterface $next) : RequestHandlerInterface;
}


class ResetParsedBody implements Middleware {
   
function dispatch(RequestHandlerInterface $next) : RequestHandlerInterface
   
{
       
return new class($next) implements RequestHandlerInterface {
           
private $next;


           
public function __construct(RequestHandlerInterface $next)
           
{
                $this
->next = $next;
           
}


           
public function dispatch(ServerRequestInterface $request) : ResponseInterface
           
{
               
return $this->next->dispatch($request->withParsedBody(null));
           
}
       
};
   
}
}

Would this answer your question?

Beau Simensen

unread,
May 13, 2017, 6:25:13 PM5/13/17
to PHP Framework Interoperability Group
I don't think that was what he was attempting to avoid. I think his was an attempt to solve the DI problem (the thing I care about) while still not having to pass the delegate to every middleware handle/__invoke call (I believe the idea that Rasmus is exploring). From what I see, this is done by injecting a factory concept into the workflow.

The factory would receive the next/delegate at runtime and could thus be instantiated by an autowiring DI container upfront without any problems.

It still requires the individual middleware & factory combo to have a way to ensure that the middleware instance stores the next/delegate prior to being invoked. Which means, most likely, that the middleware themselves cannot be autowired and must be created manually by the factory.

In my mind, it moves the magic around in a way that is slightly more automated (meaning, I'd only be able to whine about DI concerns at a fraction of the volume of before because the middleware factory can still be autowired...) at the expense of adding an explicit factory to the workflow of every middleware.

While I think this is a great way to move the discussion forward, I'm not happy about the idea that every middleware is going to effectively require two classes be written. I would guess that most factories are going to end up being proxies and will have all of the dependencies of the original middleware just to pass them on via constructor injection.

Here is what this would look like using an actual middleware I've used in the past. I've kept the delegate naming for now just to show what we're actually talking about here in practical terms.

BEFORE

class UserIdMetadataEnrichment implements MiddlewareInterface
{
private $metadataEnricherChain;
private $userIdentityMapper;

public function __construct(
MetadataEnricherChain $metadataEnricherChain,
UserIdentityMapper $userIdentityMapper
)
{
$this->metadataEnricherChain = $metadataEnricherChain;
$this->userIdentityMapper = $userIdentityMapper;
}

public function __invoke(RequestInterface $request, DelegateInterface $delegate)
{
// ... do stuff

}
}


AFTER

class UserIdMetadataEnrichmentFactory implements MiddlewareFactoryInterface
{
private $metadataEnricherChain;
private $userIdentityMapper;

public function __construct(
MetadataEnricherChain $metadataEnricherChain,
UserIdentityMapper $userIdentityMapper
)
{
$this->metadataEnricherChain = $metadataEnricherChain;
$this->userIdentityMapper = $userIdentityMapper;
}

public function createMiddleware(DelegateInterface $delegate)
{
return new UserIdMetadataEnrichment(
$delegate,
$this->metadataEnricherChain,
$this->userIdentityMapper
)
}
}


class UserIdMetadataEnrichment implements MiddlewareInterface
{
private $metadataEnricherChain;
private $userIdentityMapper;
private $delegate;

public function __construct(
DelegateInterface $delegate,
MetadataEnricherChain $metadataEnricherChain,
UserIdentityMapper $userIdentityMapper
)
{
$this->metadataEnricherChain = $metadataEnricherChain;
$this->userIdentityMapper = $userIdentityMapper;
$this->delegate = $delegate;
}

public function __invoke(RequestInterface $request)
{
// ... do stuff

}
}





At the end of the day it comes down to whether the delegate/next of a middleware should be a runtime dependency or an instance dependency. I think that is the core of what Rasmus is exploring here. It seems like he has decided it should not be a runtime dependency. I disagree.

If the same middleware class is used in several points throughout an application, why might each individual instance know about the delegate it is responsible for? In which situations would this impact how the middleware will actually run?

I've neither seen nor written middleware where the same instance mounted several places throughout an application did not work as intended and expected.


TL;DR?

The original proposal treats the delegate as a runtime dependency along with the request in question at that point in time. Rasmus is proposing the delegate becomes an instance dependency tying each instance of the middleware explicitly to a specific point in the application when it is constructed, requiring every instance of a middleware to be instantiated individually instead of reusing a shared instance and finding some way to get access to the delegate by some other means (constructor or setter injection?). David's proposal tries to resolve these two by providing a factory that allows a single factory instance to receive the delegate at runtime from any point in the application with the intention to create a middleware instance on the spot that ties the request and delegate to each other at that point in time.

Rasmus Schultz

unread,
May 14, 2017, 12:13:46 PM5/14/17
to php...@googlegroups.com
the middleware themselves cannot be autowired and must be created manually by the factory.

Are you highlighting this as a problem?

Of course the factory would need to create the middleware - that's what a factory is for. (?)

At the end of the day it comes down to whether the delegate/next of a middleware should be a runtime dependency or an instance dependency. I think that is the core of what Rasmus is exploring here. It seems like he has decided it should not be a runtime dependency

I don't feel it's a decision so much as a conclusion.

I'd like it not to be the case, because it would surely be simpler, but the fact is that there isn't always precisely one delegate - which leads me to conclude it can't be a runtime dependency, or at least not if you want the model to reflect reality, which is something I always strive for.

The fact that dispatchers need to generate a "mock" delegate that throws a run-time exception, for example, is a symptom of this being incorrectly modeled.

The fact that you can build "valid" middleware stacks with components that can never be run, to me is a symptom of modeling things in an opinionated manner.

The fact that you need a dispatcher in the first place, solely to work around "mechanical" issues created by the abstraction itself, to me points at something being "wrong".

Our dispatchers aren't solving any real problem - they exist solely to work around problems created by the abstraction itself.

I think that the problem is worth exploring, and I think we can do better.

> I'm not happy about the idea that every middleware is going to effectively require two classes be written

Let's explore that problem.

I think, first of all, you need to not think of this as "middleware consisting of two classes" - it's really two different classes, one is a handler, the other is middleware: they have different purposes in different contexts.

In some cases, this separation is going to be useful in practice - in other cases, the only interesting use of the handler is as middleware, and in those cases, it's perhaps not very interesting to have this separation.

Can we make handlers work better in a DI container context, where we want them created dynamically?

If the only problem is deferring the loading and creation of handlers, and the issue is "not knowing" which constructor-argument is the delegate, maybe we could solve this by marking that argument somehow...

I went ahead and tried it:


Look at the test, and as you can see, the handlers are bootstrapped in the DI container, and are loaded only as needed.

I wanted to use a tag/marker interface, but that's not really possible in PHP, since we have neither type unions, nor tags - and a class alias is likely the closest thing we have to a typedef.

It uses a proprietary factory-method from unbox, and this could of course be abstracted further, so as to be decoupled from the DI container - this is just a quick prototype, but it works, right?

Not saying this is the solution, but I think this at least demonstrates that you can create a middleware-framework around a simple handler-interface, with similar performance characteristics, and similar ease of use.

The class alias feels a bit unorthodox, perhaps mostly because it's a rarely-used feature... there may be another similar approach that's even better...? I'm out of ideas right now :-)


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Beau Simensen

unread,
May 14, 2017, 8:43:27 PM5/14/17
to PHP Framework Interoperability Group
On Sunday, May 14, 2017 at 11:13:46 AM UTC-5, Rasmus Schultz wrote:
the middleware themselves cannot be autowired and must be created manually by the factory.

Are you highlighting this as a problem?

Of course the factory would need to create the middleware - that's what a factory is for. (?)

I think there is confusion between your solution and the middle-ground David was proposing. I had hoped I kept that post more clear but it appears I failed. :( My bad.

First, yes, it is a problem because a middleware that could previously be autowired can now only be created manually by a factory, even though the middleware has not changed much at all.

Second, with the existing proposal we don't need a factory. Yes, David's proposal has a factory so yes, obviously, it should be used to create the middleware. If we ended up going with something like David proposed, I do not disagree that this is where the middleware should be created. :)

My entire set of example code was intended to show what would happen to one of my real world middleware if we implemented Davdi's proposal.

 
At the end of the day it comes down to whether the delegate/next of a middleware should be a runtime dependency or an instance dependency. I think that is the core of what Rasmus is exploring here. It seems like he has decided it should not be a runtime dependency

I don't feel it's a decision so much as a conclusion.

I'd like it not to be the case, because it would surely be simpler, but the fact is that there isn't always precisely one delegate - which leads me to conclude it can't be a runtime dependency, or at least not if you want the model to reflect reality, which is something I always strive for.

This is the same reasoning I would use to argue for why the delegate should be a runtime dependency. There is never precisely one delegate. The state that is important at the point in time that the middleware is executed is the request and the delegate. The middleware instance itself does not depend on the delegate any more than it does on the request.

 
> I'm not happy about the idea that every middleware is going to effectively require two classes be written

Let's explore that problem.

I think, first of all, you need to not think of this as "middleware consisting of two classes" - it's really two different classes, one is a handler, the other is middleware: they have different purposes in different contexts.

Again, this line of thought was directly related to David's proposal.

I'm confused by what you said here, though, so maybe you can elaborate. Not sure if this is talking about something else or about David's proposal or....?

What I had said was not that "middleware would consist of two classes", but that "writing middleware would require writing two classes." Since this was about David's proposal, this was a factory and a middleware. Where I previously had one middleware class, it turned into a middleware class and a factory class. The example I shared showed the BEFORE code with one MiddlewareInterface implementation and AFTER showed two classes, a MiddlewareFactoryInterface and a MiddlewareInterface.

What you said is something different. What do you mean by "one is a handler, the other is a middleware?" Also, "they have different purposes in different contexts." Can you show me what that looks like using the example code I shared with BEFORE / AFTER on the User ID metadata enricher middleware? Because I'm starting to wonder if I've completely missed something major here. Probably easiest to just start with the simple BEFORE middleware and then I'd be able to compare against what I came up with for my AFTER to see if I can follow this better.

 
If the only problem is deferring the loading and creation of handlers, and the issue is "not knowing" which constructor-argument is the delegate, maybe we could solve this by marking that argument somehow...

I think that has been the biggest sticking point for me. We never had a good solution for this for Stack except for the conventions that allowed Stack Builder to be successful. It suffered drawbacks as discussed previously in this thread.

If this bit of code is doing what I think it is, it might be an interesting idea:


The only problem is that I'm not sure how many containers would support this. I *think* this is the first time I've seen this in a container.



I'm still hung up on the runtime dependency vs not. I don't agree with the conclusion. That doesn't mean I cannot be convinced otherwise. :)

I think I'm starting to better understand what you're getting at, though. Is this an accurate summary of your conclusion?

The only difference between a handler and a middleware is the delegate. If we can find a way to remove the delegate from the handler method entirely, then there is no difference between the two as far as `handle(req) : res`.


David Négrier

unread,
May 15, 2017, 4:06:52 AM5/15/17
to PHP Framework Interoperability Group
Hey Beau,

Thanks a lot for your detailed posts.

You understood my idea completely and you are expressing (in better words) exactly what I wanted to suggest. I take my hat of to you.

Also, you are 100% right in the critics you make. Indeed, middleware authors would have to write 2 classes (one middleware and one factory). You are also right to state that the factory class is mostly a boilerplate class with a constructor that takes the same arguments as the middleware, except the "next" middleware that is provided at runtime to the factory.

I've been looking for ways to write a "generic" factory, but to do this, we have to go back to a convention (like in Stack) so I've not yet found something sexy enough to speak about it.

Anyway, I'm glad I could help move the discussion forward. I completely forgot to state it but I have really no strong feeling about which way PSR-15 go. I'm really ok with the way PSR-15 is right now. I understand what Rasmus says regarding the fact that not all middlewares need a "next" delegate (or that some need more than one). I would be glad if we can find a way to improve this, but I also understand Beau whan he says he does not want this to be at the expense of simplicity (my proposal requires writing 2 classes and only one class to write is a plus as it will help widen adoption). It looks to me like so far, unless we can come up with a better idea, there is a tradeoff to be done. Whatever way this is going, I'll be happy ;)

++
David.

Rasmus Schultz

unread,
May 15, 2017, 6:24:47 AM5/15/17
to php...@googlegroups.com
I would be glad if we can find a way to improve this, but I also understand Beau whan he says he does not want this to be at the expense of simplicity

Please don't mix up "simplicity" with "ease of use".

If we proceed with PSR-15 as-is, it's for ease of use, in spite of added complexity.

That's the only reason I'm even pursuing this alternative - it's simpler.

Either way, if we can all agree that a general handler-interface should be defined first, I'd be happy, because it leaves all doors open, and allows PSR-15 to move ahead as-is, without creating any road-blocks for possible alternatives in the future.

That is, PSR-15 should not define it's own DelegateInterface - we should have a general HandlerInterface instead, which will have a lot more uses, not least in the context of the current PSR-15 proposal as-is, where at least Dispatchers will be able to implement them, which provides an extra degree of high-level abstraction, which is currently missing from that proposal.

As said, my first priority at this time is not to block PSR-15, but merely to make sure it doesn't create roadblocks down the line.

If PSR-15 went ahead with an identical interface for delegates that is incompatible with a general handler-interface, that would be very bad, as this will be non-interoperable with other types of handlers.

If we can agree to do something about that, I'd be happy with PSR-15 proceeding essentially as-is, only with the middleware-interface type-hinting the delegate with a general handler-interface, rather than a middleware-specific one.

And perhaps with an additional clause that suggests dispatcher implementations also implement that interface.

My main priority at this time is to make sure we don't unnecessarily cut ourselves off from architectural options in the future.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Stefano Torresi

unread,
May 15, 2017, 7:42:51 AM5/15/17
to php...@googlegroups.com
Hey folks,

To be honest, I don't like any of the alternative approaches proposed so far; I somewhat recognise the concerns raised here, but I think the strive to a "purer" design poses a very high risk of being counterproductive. We have already pivoted from one design to another and, as far as I can tell, it was worth it.
Pivoting once again, though? That could be too far reaching, leaving alone the fact I personally don't find the alternatives appealing at all: they may be simpler in theory, but they completely fail on communicating intent, having to point at convoluted implementation details to outline where the dispatching mechanism is, while the dispatching mechanism was outside the scope of the proposal in the first place.

To be more concise, I think the current proposal is "good enough"(tm).
It is definitely *easier*. It may be not *simpler*, but frankly I don't think *simpler* is worth the effort, here.

The mission is to move the ecosystem forward, so: would PSR-15 accomplish that as it is?
Considering the sheer amount of framework-agnostic middlewares already out in the wild, and the enthusiastic comments I hear around and about on what people are accomplishing with them, I am pretty sure the current proposal would result in a great success, both in terms of adoption and objective improvement over the current status quo (which, may I remember to you all, is mostly fragmented in a lot of framework-specific-half-custom-backed-unportable-mess-of-over-engineered-mvc-doom).

Having said that, I like the idea of generalising the "Delegate" interface into a separate "Handler" one.

This alone would cause a considerable set-back for PSR-15, though, as it would need to be put on hold while formalising a separate PSR with the due process. OTOH, as Rasmus noted, this would leave a door open for a different specification in the future, both for something with not too much departure from what will eventually be delivered with PSR-15, and something not even related to middleware at all.

I think allowing for this change, but leaving the overall design as it is (i.e. the delegate being a runtime dependency), could be a good compromise between the parts.

I understand where you're coming from, Rasmus, but the current design is far from being inadequate, and lots of people are already enjoying the results of implementing it. This probably amounts to something.

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.

--
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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Marco Perone

unread,
May 15, 2017, 9:05:54 AM5/15/17
to PHP Framework Interoperability Group
I'd like to add my 2cc to the discussion...

I think that all the interfaces proposed up to now are really meaningful and have their own place inside an application.

In the most general sense, a web application is something that receives a request and returns a response. This is exactly the HandlerInterface proposed by Rasmus. I like to call this

Application: Request -> Response

The second interface proposed by David represents nothing else than an edit to an Application

ApplicationModifier: Application -> Application

These ApplicationModifiers could be very easily composed to build a complex application just by modifying in several steps a simpler one. This seems to me more generic than a middleware approach. Nothing in this definition of the interface suggests that we are building an application adding layer after layer to our core domain. I'm not saying this is bad, but I guess it goes a little outside the scope of defining a middleware interface.

Now, if we want to represent a middleware, or a layer of our application, I guess that it should be something like

Layer: Request -> Application -> Response (could easily also be Layer: Application -> (Request -> Response), i.e. an ApplicationModifier)

which is exactly what the current proposal describes. In this way it is very explicit that what we are doing is having a layer around an application and not an entire application.

At this point a "framework" should be something like this

Framework: Application -> [Layer] -> Application

where the input Application represents our Domain which does not worry about external layers, the list of Layers are the middleware which surrounds the application. The framework would then modify the input Application step by step with the provided Layers to obtain a new Application. This suggests that the core of the application should not be a Layer, but an Application (in other words, it should not receive a DelegateInterface)

From this point of view, I am tempted to propose to keep all three the proposed interfaces. Then some of them (Application and ApplicationModifier) could be easily reused also in a non-middleware application.

Just my 2cc... hope it was clear and somehow helpful

Rasmus Schultz

unread,
May 15, 2017, 9:32:25 AM5/15/17
to php...@googlegroups.com
the dispatching mechanism was outside the scope of the proposal in the first place

Well, it is and it isn't.

That's one of the first problems that lead me to explore alternatives: the current design dictates the use of a dispatcher, because middleware, as defined by the current proposal, cannot be run without this framework mechanism in place, except using terrible work-arounds, such as "fake" or "mock" implementations of the delegate-interface.

However, this situation would be somewhat alleviated by the existence of a handler-interface, and the use of that in PSR-15 - since you could then use *any* handler as the delegate; it no longer has to be a full-blown dispatcher, you can use whatever you want, without resorting to "fake" or "mock" implementations. So the situation in that regard is much improved.

I think allowing for this change, but leaving the overall design as it is (i.e. the delegate being a runtime dependency), could be a good compromise between the parts.

I agree.

With a standard handler in place, and supported by PSR-15, it becomes developer's choice whether it's meaningful to ship a component/package as both a handler and/or middleware - without forcing the decision on anyone. It gives us a lot more freedom.

For example, most routers are already handlers in some sense, with middleware adapters - and basically all dispatchers are handlers too, so this provides high-level interop for all of those components, and probably others.

It's not necessarily a huge set-back for PSR-15, if we push for a very small and focused PSR for the handler-interface - I've proposed to do so here:


If we can agree on process() as the method-name for the handler-interface, we may even be able to provide legacy-support for current middleware with a class alias - that would certainly be a lot less disruptive to the already sprawling PSR-15 community.


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

--
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+unsubscribe@googlegroups.com.

--
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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
May 15, 2017, 10:00:53 AM5/15/17
to PHP Framework Interoperability Group
On Monday, May 15, 2017 at 12:24:47 PM UTC+2, Rasmus Schultz wrote:
That is, PSR-15 should not define it's own DelegateInterface - we should have a general HandlerInterface instead, which will have a lot more uses, not least in the context of the current PSR-15 proposal as-is, where at least Dispatchers will be able to implement them, which provides an extra degree of high-level abstraction, which is currently missing from that proposal.
 
I just want to add some additional examples to underpin that statement. Currently, I prefer to name it ActionInterface, but I will use name HandlerInterface for simplicity below.

Multiple interfaces can be based on a more general HandlerInterface:

1. PSR-15 Middlewares: Middleware:process(ServerRequestInterface $req, HandlerInterface $next):ResponseInterface
2. Factories similar to what David have proposed:  F:createHandler(HandlerInterface $next):HandlerInterface
3. Middlewares with multiple nexts: Middleware:process(ServerRequestInterface $req, HandlerInterface $success, HandlerInterface $error):ResponseInterface

1.-3. are middleware related: one knows in advance the number and meaning of successors. But the HandlerInterface is useful beyond that:

5. Reusable Handlers/Actions, where $next does not make sense at all, like ContactPost::class, CommentDelete::class etc.
6. Reusable route groups; $next does not make sense here too:

class Blog extends RouteGroup implements HandlerInterface
{
   
public function __construct()
   
{
        $this
->post('/comment', ContactPost::class);
       
// etc.

        $this
->add(SomeMiddleware::class);
   
}
}

// setup
$app
->route('blog/*', new Blog()); // Blog would be typically installed via composer

7. The last example can be extended up to reusable applications, e.g. consider a Slim and an Expressive application:

$app = new // either Slim or Expressive or what ever
$app
->route('slim/*', $slimApp);
$app
->route('expressive/*', $expressiveApp);

Can we achieve the same by using only MiddlewareInterface and simply ignoring $next/$delegate? Matthew wrote:

```php 
$app->route('/api/blog', [ 
    Authentication::class, 
    Authorization::class, 
    Blog::class, 
], ['GET', 'POST']); 
``` 



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. 
 
Honestly, I cannot image how implementing Blog::class as middleware would _simplify_ anything. Neither for applications nor for users.

For applications it would be bad, because:
1. if the stack runs empty, the application must provide a mechanism/convention to deal with that
2. the application must create a delegate object to call Blog::class, even if it is not used

For users it would be bad, because:
1. it is easy to misconfigure your stack: either by _forgetting_ to add Blog::class or by adding middlewares after Blog::class
2. it would be confusing if Blog::class is called with a $delegate, when it is not allowed to use it

If Blog::class implements HandlerInterface (or DelegateInterface) instead, then things will become simpler IMO:
1. the stack can be verified earlier; thus the app can fail before creating middlewares or at least before Blog::class is called
2. the last Middleware will be called with the Blog::class as $next – no EmptyStackHandler/Delegate or similar is needed

Larry Garfield

unread,
May 15, 2017, 10:26:37 AM5/15/17
to php...@googlegroups.com
I'm starting to get a bit lost in this thread, as while I have seen some valid concerns I also am seeing a lot of "but that's not how it works".

In particular, with typed parameters (which we are certainly going to have) I don't see how an uber-generic "handler" interface would, erm, work.  Points 1-3 in Michael's post below are incompatible signatures.  The "Handler" shown in #6 is so generic as to be meaningless, or at least I don't know what it's supposed to do.  It looks almost like it's trying to be a router, which is not the job of a middleware.  (A middleware could bridge to a router, which stores data in request attributes; that's fine.  But the middleware stack itself shouldn't be responsible for that.

I still don't really grok what this alternative is supposed to be...

That said, the most compelling argument that's been made against the current PSR-15 draft is essentially "branching" the middleware logic.  My question to Beau (or anyone from the PSR-15 team) would then be, how would the current draft handle that?

As a concrete example, say I want to apply:

- AuthenticationMiddleware only to paths /admin/*
- CacheMiddleware only to paths /api/*
- RouterMiddleware to all paths. 
- ActionMiddleware to all paths, which is what calls out to a controller/action/app domain/thing.

Ignoring the internal mechanics of their actual tasks, how would I do that in the current draft without hard-coding /api and /admin in the corresponding middlewares?  (Other proposals, feel free to offer what the above scenario would look like in your case for comparison.)

--Larry Garfield
--
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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
May 15, 2017, 3:08:20 PM5/15/17
to PHP Framework Interoperability Group
Sorry Larry, if I have confused you.

My aim was to show that the HandlerInterface itself can be used in many different ways, not that these examples may lead to PSRs or be compatible in some way. Although, it should show that a HandlerInterface CAN be used to specify middleware interfaces, like PSR-15.


On Monday, May 15, 2017 at 4:26:37 PM UTC+2, Larry Garfield wrote:
The "Handler" shown in #6 is so generic as to be meaningless, or at least I don't know what it's supposed to do.  It looks almost like it's trying to be a router, which is not the job of a middleware.

It should show that a HandlerInterface can be used to encapsulate a Blog component with its (sub-)routes (e.g. 'blog/comment', 'blog/author', …) and make such a component shareable e.g. at packagist, so it can be used in multiple applications – And yes, it is an example of a non-middleware: implementing it as a middleware makes no sense, because of the unnecessary $next/$delegate.
 
That said, the most compelling argument that's been made against the current PSR-15 draft is essentially "branching" the middleware logic.  My question to Beau (or anyone from the PSR-15 team) would then be, how would the current draft handle that?

As a concrete example, say I want to apply:

- AuthenticationMiddleware only to paths /admin/*
- CacheMiddleware only to paths /api/*
- RouterMiddleware to all paths. 
- ActionMiddleware to all paths, which is what calls out to a controller/action/app domain/thing.

PSR-15 and all middleware proposals in this thread are about the question, how a middleware component should look like. I.e. how should a component look like, which have to return a response and utilizes zero or more successor components.
Hence, routing (like _only to pahts /admin/*_) is not defined anywhere; it is up to frameworks and it is not needed to define it for reusable middlewares. 

Geert Eltink

unread,
May 16, 2017, 1:27:43 AM5/16/17
to PHP Framework Interoperability Group
On Monday, May 15, 2017 at 4:00:53 PM UTC+2, Michael Mayer wrote:
 
Can we achieve the same by using only MiddlewareInterface and simply ignoring $next/$delegate? Matthew wrote:

```php 
$app->route('/api/blog', [ 
    Authentication::class, 
    Authorization::class, 
    Blog::class, 
], ['GET', 'POST']); 
``` 



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. 
 
Honestly, I cannot image how implementing Blog::class as middleware would _simplify_ anything. Neither for applications nor for users.

For applications it would be bad, because:
1. if the stack runs empty, the application must provide a mechanism/convention to deal with that

In Expressive 1 there was a mechanism (FinalErrorHandler) to deal with empty stacks. It was confusing for users. In Expressive 2 this got changed and the last middleware on the stack is always a NotFoundMiddleware. Basically if the stack runs empty and it gets to the NotFoundMiddleware it means no Response is returned and you can return a 404.
 
2. the application must create a delegate object to call Blog::class, even if it is not used

For users it would be bad, because:
1. it is easy to misconfigure your stack: either by _forgetting_ to add Blog::class or by adding middlewares after Blog::class 
2. it would be confusing if Blog::class is called with a $delegate, when it is not allowed to use it

Isn't this where PHPUnit comes in? If you create the right tests for this you know your stack is configured correctly and that the url `/blog/some-post` actually can return a blog post.

I think it's more confusing if you have a lot of different middleware interfaces. Why is the Blog::class not allowed to use the delegate? I've actually seen people doing it. I can't remember the use case or if it was a good solution, but they had 3 ActionMiddlewares stacked. If some conditions were met the next one would be executed and otherwise it would return a Response.
 
If Blog::class implements HandlerInterface (or DelegateInterface) instead, then things will become simpler IMO:
1. the stack can be verified earlier; thus the app can fail before creating middlewares or at least before Blog::class is called 
2. the last Middleware will be called with the Blog::class as $next – no EmptyStackHandler/Delegate or similar is needed
 
So let's say you have a RouterMiddleware. It takes care of parsing the url and redirect to the configured Action::class. However that Action class needs some specific middleware as well which are added to the stack when the route is determined. So you can't check for a valid stack until all middleware before the RouterMiddleware is executed and the RouterMiddleware itself.

I'm getting the impression that people want to regulate to much with interfaces and that would restrict how an application (could) work. And at the end of the day you end up with 10 middleware frameworks which do all the same thing in the same way.

Woody Gilk

unread,
May 16, 2017, 9:29:35 PM5/16/17
to PHP Framework Interoperability Group
I reviewed what Rasmus and Michael (et al) are saying about different kinds of handlers and I _think_ I'm starting to understand what they are talking about. It goes way beyond what is proposed in PSR-15. I would even say that's an entirely different way of thinking about middleware. (Please correct me if any of the following is wrong. ;)

For the most part, we have thought about middleware as being basically a list of things that the request drops through and something inside the list will produce a response:

req -> [set-client-ip, require-auth, parse-body, handle-req] -> res

And if something goes awry during that sequence, the rest of the middleware will be skipped by returning early:

req -> [set-client-ip, require-auth (failed), parse-body, handle-req] -> res

This works perfectly well for simple systems and not so well for more complex systems. For instance, if we have a complex application where there is a "user" part and an "admin" part, the admin part needs a different sequence:

req -> [set-client-ip, require-auth, require-admin-role, parse-body, handle-req] -> res

In the case of something like Slim Framework, this is accomplished at the routing level, where any route can have middleware attached to it: https://www.slimframework.com/docs/concepts/middleware.html#route-middleware

What Michael seems to be thinking about is these sorts of complex scenarios where having the same sequence of middleware executed for every request is insufficient. Instead, the middleware becomes more of a tree structure, where each middleware might do 3 possible things:

1. modify the request
2. generate a response (possibly by delegating to another middleware chain!)
3. modify the response

Now, to be clear, there's nothing in PSR-15 that says this is not possible. In fact, several dispatchers already support nested middleware. From what I can tell, Michael's objection (and maybe Rasmus' too) is more about that fact that the three possible actions are not distinct. With distinct interfaces for the three actions, this becomes a thing:

(Side note, I am using my own made up interfaces here, not the ones that Michael has proposed.)

$foo = new FooMiddleware(); // implements ModifyRequestInterface
$request = $foo->modify($request);

With just one interface, this is impossible because the middleware has to return a response or delegate to something else that does. So rather than having a dispatching system that runs X middleware, one of which is assumed to return a response by handling the request, it becomes possible to use a simple routing system that just points at a handler class:

class AdminCreatePage implements ServerRequestHandlerInterface
{
    public function __construct(
        SetClientIp $ip,
        RequireAuth $auth,
        RequireAdminRole $admin,
        ParseBody $body
    ) { ... }

    public function handle(ServerRequestInterface $req)
    {
        try {
            $req = $this->ip->process($req);
            $req = $this->auth->process($req);
            $req = $this->admin->process($req);
            $req = $this->body->process($req);
        } catch (Exception $e) {
            return $this->errorResponse($e);
        }
        
        // do whatever domain stuff
        return $this->successResponse();
    }
}

This a pretty contrived example and the various modifying middleware would probably be wrapped into a single class, but I think it illustrates the difference between PSR-15 and what Michael is proposing.

Honestly, I see a lot of value in this approach. It works better for complex applications and is much more flexible. It also makes it easy to share various kinds of middleware as stand alone components, or even collections of middleware grouped into a single class that implements one of the three interfaces. With variadic declarations and some functional tricks, it could be a really powerful approach. Is it middleware? Not by the definition that we have been using. Is it interesting? Definitely. I am definitely coming around to the idea.

--
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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Rasmus Schultz

unread,
May 17, 2017, 1:42:29 AM5/17/17
to php...@googlegroups.com
I think it's great that you're coming around to the idea, Woody :-)

But I am no longer trying to view this as an alternative to PSR-15, because, assuming we standardize on that handler-interface first, there is nothing about PSR-15 that prevents us from exploring other options.

Middleware developers can ship packages with middleware and handler implementations if and when that makes sense. So this doesn't need to be an ultimatum.

I can see the handler-interface having 3 uses in the context of PSR-15:

1. As the common interface of middleware dispatchers.

2. As the type-hint on delegates.

3. As type-hint for the "final" handler supported by many dispatchers.

That interface has an endless number of uses, and I'm sure we'll provide interop on a lot of points we haven't even thought of yet.

Now, one could debate whether PSR-15 has anything to do with interop in the first place, or is it mostly a "framework in disguise", given that PSR-15 middleware doesn't work without a dispatcher.

I don't stop much care anymore. There are already a lot of people using PSR-15 and depending on it, and there is no good reason to disrupt that more than necessary.

I do think it's in everybody's best interest to disrupt it a little, to make sure it does what it does _and_ is itself interoperable with a wider ecosystem. I think that standardizing on the handler interface will do that.

Let's push for the standardization of the handler interface ASAP so we can finalize PSR-15 and bring it to a close? :-)


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+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Beau Simensen

unread,
May 17, 2017, 10:40:13 AM5/17/17
to PHP Framework Interoperability Group
On Wednesday, May 17, 2017 at 12:42:29 AM UTC-5, Rasmus Schultz wrote:
I think it's great that you're coming around to the idea, Woody :-)

But I am no longer trying to view this as an alternative to PSR-15, because, assuming we standardize on that handler-interface first, there is nothing about PSR-15 that prevents us from exploring other options.

Indeed, if the end result of this discussion is extracting DelegateInterface into its own proposal as its own handler interface that can be used as the delegate in PSR-15, I'm happy with that.

Larry Garfield

unread,
May 17, 2017, 1:03:31 PM5/17/17
to php...@googlegroups.com
I'm still a bit unclear on the "handler" as an uber-generic thing.  Isn't that entirely separate from what Woody described, which is a 3-part pipeline?  (Request modifiers, response modifiers, and request->response mappers.)

I admit the 3-part pipeline is appealing, and I've considered it before.  I especially like that it keeps a shallower call stack.  However, the problem is conditional request modifiers.  Eg, an Auth modifier that may:

1) Add a user token to the request based on authentication.
2) Fail authentication and short circuit the entire pipeline with a 401/403 Response itself.

If the signature only allows returning a Request, then it can't do that.  It has to be a nested mapper instead and do the delegate approach anyway.  And if it's doing that anyway... what's the purpose of having the separate modifiers if they can't be used half the time anyway?

I'm afraid I'm still confused here about what is being proposed and what it solves...

--Larry Garfield

Rasmus Schultz

unread,
May 17, 2017, 4:59:15 PM5/17/17
to php...@googlegroups.com
I think he was describing the other interfaces that are part of that proposal. (?)

Since these aren't directly related to the problem at hand, and could be specified in a separate PSR, I'm rooting for a separate PSR for just the handler interface.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Nicholas Ruunu

unread,
May 18, 2017, 1:06:42 PM5/18/17
to PHP Framework Interoperability Group
I like the idea, but the thing is, middleware is not the end-all of this approach.
What might be missing is:

interface Resource {
    public function response(ServerRequestInterface $request): ResponseInterface
}

I use this in my framework I'm building as anything that provides a response from a request, like a Page (Controller).
This of course could also be used for middlewares as you stated.

Is it worth it to split up all these PSR's with just for the benefit of having different names of the interface?
I don't know, maybe it is, to me it seems more and more like it doesn't.


Den onsdag 17 maj 2017 kl. 22:59:15 UTC+2 skrev Rasmus Schultz:
I think he was describing the other interfaces that are part of that proposal. (?)

Since these aren't directly related to the problem at hand, and could be specified in a separate PSR, I'm rooting for a separate PSR for just the handler interface.

On May 17, 2017 19:03, "Larry Garfield" <la...@garfieldtech.com> wrote:
On 05/17/2017 09:40 AM, Beau Simensen wrote:
On Wednesday, May 17, 2017 at 12:42:29 AM UTC-5, Rasmus Schultz wrote:
I think it's great that you're coming around to the idea, Woody :-)

But I am no longer trying to view this as an alternative to PSR-15, because, assuming we standardize on that handler-interface first, there is nothing about PSR-15 that prevents us from exploring other options.

Indeed, if the end result of this discussion is extracting DelegateInterface into its own proposal as its own handler interface that can be used as the delegate in PSR-15, I'm happy with that.


I'm still a bit unclear on the "handler" as an uber-generic thing.  Isn't that entirely separate from what Woody described, which is a 3-part pipeline?  (Request modifiers, response modifiers, and request->response mappers.)

I admit the 3-part pipeline is appealing, and I've considered it before.  I especially like that it keeps a shallower call stack.  However, the problem is conditional request modifiers.  Eg, an Auth modifier that may:

1) Add a user token to the request based on authentication.
2) Fail authentication and short circuit the entire pipeline with a 401/403 Response itself.

If the signature only allows returning a Request, then it can't do that.  It has to be a nested mapper instead and do the delegate approach anyway.  And if it's doing that anyway... what's the purpose of having the separate modifiers if they can't be used half the time anyway?

I'm afraid I'm still confused here about what is being proposed and what it solves...

--Larry Garfield

--
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.

Rasmus Schultz

unread,
May 18, 2017, 2:18:52 PM5/18/17
to php...@googlegroups.com
As you say, middleware is not the end-all of this approach, and that's why we want to split it off as a separate PSR - the interface isn't middleware-specific at all.


To unsubscribe from this group and all its topics, send an email to php-fig+unsubscribe@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Matthieu Napoli

unread,
May 20, 2017, 4:33:33 PM5/20/17
to PHP Framework Interoperability Group
Hi everyone,

I've been following that discussion, I just want to react to a few things:

Woody:

This works perfectly well for simple systems and not so well for more complex systems. For instance, if we have a complex application where there is a "user" part and an "admin" part, the admin part needs a different sequence: […] Instead, the middleware becomes more of a tree structure

I agree with that, that's the approach I've been following. However I disagree with you that's it's sort-of incompatible with the current middlewares. Here is an example of an application that can be built with middlewares (link to the gist):

$application = pipe([
    ErrorHandlerMiddleware::class,
    ForceHttpsMiddleware::class,
    
    prefix([
        
        // API with authentication
        '/api/' => pipe([
            HttpAuthenticationMiddleware::class,
            router([
                '/api/articles' => /* controller */,
                '/api/articles/{id}' => /* controller */,
            ]),
        ]),
        
        // Public website
        '/' => pipe([
            MaintenanceModeMiddleware::class,
            SessionMiddleware::class,
            DebugBarMiddleware::class,
            router([
                '/' => /* controller */,
                '/article/{id}' => /* controller */,
            ]),
        ]),
        
    ]),
]); 

Don't mind the functions (pipe(), router(), …) these are just helpers to instantiate Pipe/Router/PrefixRouter middlewares.
But as you can see this is a tree, and the middleware approach with $next doesn't restricts us to "just a list of middlewares". A router for example creates a node with several sub-items. And yes the router will be invoked with a $next, but IMO that's a good thing: the router can call $next if the URL doesn't match any route. That allows, for example, to use several routers in a single pipe (e.g. if you have modules).
Also a quick note: if you want to point out that a router should not dispatch, fine, just add the router as a middleware higher up the pipe and replace "router" with dispatcher. The example of the tree above still stands.

And by the way this is a bit what Slim and IIRC Zend Expressive do: route handlers can be controllers or can be pipes, that's how you can add "route middlewares" (I hope I'm not wrong here).

My conclusion is that the current signature is very simple and powerful, are the "cons" are only little details, so we should definitely stay with that interface and not the handler interface or some other limited interface like what you suggest: "$request = $foo->modify($request);".
The middleware interface is simple and extremely powerful, if we want to cover all scenarios with more specific interfaces we would need a ton of different interfaces, and it would be really hard to connect them all together.

Rasmus:

I can see the handler-interface having 3 uses in the context of PSR-15:
1. As the common interface of middleware dispatchers.
2. As the type-hint on delegates.
3. As type-hint for the "final" handler supported by many dispatchers.

Agreed, but:

1. those dispatchers still need to implement the middleware interface because, thanks to that interface, they can be composed with other middlewares (inserted into a pipe or router, example below) -> the dispatcher signature (without $next) is useful for end users, not interoperability (the $next parameter is the key for composability/interoperability), as such it's not a matter for the PHP-FIG IMO.
$application = prefix([
    '/api/' => $expressiveApplication, // The API is done with Zend Expressive
    '/' => $slimApplication,           // The website is done with Slim
]);

2. yes but delegates are just a tool/implementation detail to connect middlewares together, so I don't think that pushes for a whole PSR for that interface

3. I agree this is a bit of an ugly detail and it would be better if we could do without, but honestly this is just a detail, and it works, and it's not really a practical problem, I don't see how a PSR would matter here

As such, I'm not really against a HandlerInterface as a separate PSR but I don't think it's worth it. Also if packages start shipping "handlers" that are not middlewares, then they cannot be put inside middleware architectures and there goes the whole plan of interoperability.

I hope I'm making sense :)

Michael Mayer

unread,
May 21, 2017, 4:43:57 AM5/21/17
to PHP Framework Interoperability Group
For clarification: I'm still undecided about the usefulness of the two modifier interfaces (described in this thread as "$request = $foo->modify($request);" and "$response = $foo->modify($response);"). I've added them to the hms proposal to make them discussable and to explore their pros and cons. As Rasmus suggested, they will be removed within the next few days because the ActionInterface/HandlerInterface can stand on its on.

On Saturday, May 20, 2017 at 10:33:33 PM UTC+2, Matthieu Napoli wrote: 
Rasmus:

I can see the handler-interface having 3 uses in the context of PSR-15:

3. As type-hint for the "final" handler supported by many dispatchers.
3. I agree this is a bit of an ugly detail and it would be better if we could do without, but honestly this is just a detail, and it works, and it's not really a practical problem, I don't see how a PSR would matter here

That PSR would also allow interop of routers, controllers and final handlers, and indeed solve practical problems, e.g. if you use it with your pipe function:

// diff in pseudo code:
- function pipe(MiddlewareOrHandler[] stack) : Middleware {}
+ function pipe(Middleware[] stack, Handler $final) : Handler {}
- function router(array routes) : Middleware {}
+ function router(array routes) : Handler {}

Thus you do not need to fabricate handlers like the one at your Router::dispatch, and the code of your example becomes bit more explicit:

$application = pipe([
   
ErrorHandlerMiddleware::class,
   
ForceHttpsMiddleware::class,
], prefix([

   
'/api/' => pipe([
       
HttpAuthenticationMiddleware::class,

   
], router([

       
'/api/articles' => /* controller */,
       
'/api/articles/{id}' => /* controller */,

   
])),

   
   
'/' => pipe([
       
MaintenanceModeMiddleware::class,
       
SessionMiddleware::class,
       
DebugBarMiddleware::class,

   
], router([

       
'/' => /* controller */,
       
'/article/{id}' => /* controller */,

   
])),
]));

As you can see, there is no essential difference for the end user setup. Although, we do not need to create those meaningless $next delegates: the prefix and router functions return them.

And yes the router will be invoked with a $next, but IMO that's a good thing: the router can call $next if the URL doesn't match any route. That allows, for example, to use several routers in a single pipe (e.g. if you have modules).

I disagree, that's not a good thing, this means $next would be used differently by different middlewares:
For example for your HttpAuthenticationMiddleware, calling $next means: everything is fine; lets carry out the real task and if that fails I will try to handle that.
For the router you're suggesting, calling $next means: something went wrong; lets delegate the task to someone else and if that fails I do not care.

Thus, using $next to signal "404 Not Found" is bad for many reasons:
1. 404 is one error of many, e.g. what about routing related errors like "405 Method Not Allowed" and "415 Unsupported Media Type"?
2. What kind of final handler do I need to dispatch middlewares? For router middlewares I probably want a NotFoundHandler, but for a controller action I probably want a handler which returns a plain response object.
3. Because of 2., you need some conventions, i.e. framework specific conventions(!), which means it would neither be possible to reuse your '/api/' middleware stack across multiple frameworks nor and more importantly your RouteMiddleware itself.
 
And by the way this is a bit what Slim and IIRC Zend Expressive do: route handlers can be controllers or can be pipes, that's how you can add "route middlewares" (I hope I'm not wrong here).

Slim controller actions are not middlewares, they get an $args argument instead of a $next.
Zend Expressive and Zend Stratigility:

$app->pipe('/', function (ServerRequestInterface $request, DelegateInterface $delegate) {
   
return $delegate->process($request);
});

$app
->pipe('/', function (ServerRequestInterface $request, DelegateInterface $delegate) {
    $response
= new Response();
    $response
->getBody()->write('Wtf?');
   
return $response;
});

Zend Expressive is based on Stratigility and returns a 404 for '/'. Interestingly, Zend Stratigility itself returns a 200 (content: 'Wtf?') – yes, exactly the same code snippet can be used in both frameworks.
IMO, these setups are hard to debug, and to me, it is another reason why I dislike routers/controllers which call $next: I can never be sure what $next will return (without further knowledge about the framework).

Rasmus Schultz

unread,
May 21, 2017, 5:52:55 AM5/21/17
to php...@googlegroups.com
If I had to summarize all of my thinking about this subject into a single statement, it would be this:

The interface that describes any component that takes a Request and must return a Response, is the most fundamental transaction we can meaningfully describe.

That is, at the highest level, the processing of any server-side transation can be described in this way - no matter what else happens below that level, how you describe it, or what other details of the net transaction you may be able to describe with other interfaces, this is the fundamental.

Starting with middleware and ignoring that fact would be remiss, not least because we've already seen numerous examples of how this same contract occurs in in at least three or four cases within the scope of (the proposed concept of) middleware.

Michael Mayer has already started the PSR for this interface, and has already indicated he would agree to reduce the scope of the first PSR to just the interface required for PSR-15 middleware.

We need to do this, and I think we could get through the process rather quickly - the interface and the proposal should be trivial.

Actually, the only thing we're still debating is the naming of the interface and method, which seems to be something people have different ideas about - perhaps because everyone has encountered different use-cases for this interface.

I've used it for several things myself, including middleware, dispatchers/delegates and controllers - all of these could be said to "handle" or "process" a request in some way, the preferred terminology is probably largely a matter of opinion, and your personal relationship and experience with those words.

One thing I would submit is that, in the name of moving forward, I'd like us to stick with "process" as the method-name, as it is in the DelegateInterface in PSR-15 at the moment - because this will create the least possible disruption. Existing middleware would just need to change the implements-clause from DelegateInterface, nothing else - we'd be able to move past this breaking change and towards a final PSR-15 with as little detour as possible.

As for the name of the interface itself, that's not as important to me. Earlier I proposed "ControllerInterface", which was not well received, and later I proposed "HandlerInterface", which I think sounds general/neutral enough to cover all the use-cases. The current proposal says "ActionInterface", which I think is just as ambiguous as "ControllerInterface".

I'd love to hear other suggestions.


--
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+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Oscar Otero

unread,
May 21, 2017, 6:43:33 AM5/21/17
to php...@googlegroups.com
I just read this discussion, and if I understand correctly, you’re proposing something like this:

PSR-15
Psr\Http\ServerMiddleware\MiddlewareInterface::process(ServerRequestInterface $request, ServerRequestHandlerInterface  $handler): ResponseInterface

PSR-18 ? (naming under discussion)
Psr\Http\Strategy\ServerRequestHandlerInterface::process(ServerRequestInterface $request): ResponseInterface

I like it. The PSR-18 (or the number that is assigned) is a good example of how use semver in PSR to publish new versions of the specification. In order to move fast in the PSR-15 release, PSR-18 v1.0 should be released with this unique interface. But new versions can add new strategies to the namespace (Psr\Http\Strategy\), keeping the backward compatibility (PSR-25 can release v1.1, PSR-56 the v1.2, etc).




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+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

Matthieu Napoli

unread,
May 21, 2017, 6:46:46 AM5/21/17
to PHP Framework Interoperability Group
Hi Michael, those are interesting points, my answers inline:


On Sunday 21 may 2017 10:43:57 UTC+2, Michael Mayer wrote :

On Saturday, May 20, 2017 at 10:33:33 PM UTC+2, Matthieu Napoli wrote: 
Rasmus:

I can see the handler-interface having 3 uses in the context of PSR-15:

3. As type-hint for the "final" handler supported by many dispatchers.
3. I agree this is a bit of an ugly detail and it would be better if we could do without, but honestly this is just a detail, and it works, and it's not really a practical problem, I don't see how a PSR would matter here

That PSR would also allow interop of routers, controllers and final handlers, and indeed solve practical problems, e.g. if you use it with your pipe function:

// diff in pseudo code:
- function pipe(MiddlewareOrHandler[] stack) : Middleware {}
+ function pipe(Middleware[] stack, Handler $final) : Handler {}
- function router(array routes) : Middleware {}
+ function router(array routes) : Handler {}

Thus you do not need to fabricate handlers like the one at your Router::dispatch, and the code of your example becomes bit more explicit:

[…]

As you can see, there is no essential difference for the end user setup. Although, we do not need to create those meaningless $next delegates: the prefix and router functions return them.

That's an interesting solution and yes it makes sense. However it is more limiting than the solution where everything is a middleware. When you have middlewares and handlers you can't add a handler into a middleware pipe. You are forced to put it as the "last handler". This is less interoperable.

You cannot, for example, do this:

$application = pipe([
    ErrorHandlerMiddleware::class,

    // Resolve the controller and store it in the request
    router([
        '/' => /* controller */,
        '/article/{id}' => /* controller */,
    ]),

    Firewall::class,
    ControllerDispatcher::class, // actually invoke the controller
]); 

In the example above the controller dispatcher (or "invoker") is extracted from the router so that we can put middlewares in-between them, for example here to check permissions to access to each route.

Yes, the dispatcher could be the final handler, but my point is that the separation between handlers and middlewares is fuzzy, there are plenty of scenarios where a handler could actually be used as a middleware, so most handlers might actually need to implement both interfaces.

To sum up more clearly:

- MiddlewareInterface: standard for interoperability
- HandlerInterface: standard just because "this is a common signature"

IMO the middleware interface is the key for interoperability.

 

And yes the router will be invoked with a $next, but IMO that's a good thing: the router can call $next if the URL doesn't match any route. That allows, for example, to use several routers in a single pipe (e.g. if you have modules).

I disagree, that's not a good thing, this means $next would be used differently by different middlewares:
For example for your HttpAuthenticationMiddleware, calling $next means: everything is fine; lets carry out the real task and if that fails I will try to handle that.
For the router you're suggesting, calling $next means: something went wrong; lets delegate the task to someone else and if that fails I do not care.

Nope that's not what I'm saying. Here is an example to illustrate:

$application = pipe([
    // Module
    Blog::class,

    // Routes of my application
    router([
        '/' => /* controller */,
        '/article/{id}' => /* controller */,
    ]),
]); 

The "Blog" module could be a router (or a pipe, we don't really care) that provides "blog" routes. Imagine also all the modules/bundles that provide authentication routes (login/logout, OAuth), asset routes (Glide comes to mind), whole application parts (e.g. SonataAdminBundle), etc. The router of that module simply calls $next if no route matched.

Calling next means: I delegate handling the request.

It doesn't intrinsically mean something went right (authentication) or wrong (404 not found).


And by the way this is a bit what Slim and IIRC Zend Expressive do: route handlers can be controllers or can be pipes, that's how you can add "route middlewares" (I hope I'm not wrong here).

Slim controller actions are not middlewares, they get an $args argument instead of a $next.

Slim routes are actually middleware pipes: https://github.com/slimphp/Slim/blob/3.x/Slim/Route.php#L26 


Reply all
Reply to author
Forward
0 new messages