PSR-7 Alternate Proposal

298 views
Skip to first unread message

John Porter

unread,
Feb 19, 2017, 9:48:02 AM2/19/17
to PHP Framework Interoperability Group

I've re-read everything that was discussed in my last issue #46 and I realise that having an example of an alternative that fixes my myriad of concerns over swapping one flawed implementation for another (no offence meant), is preferable over me just offloading my confusing concerns.


I can clearly see my previous issue asked one question, then rambled about other aspects of my concerns, so in an attempt to separate concerns (see what I did there?) I will focus on a single response to this proposal.


An Alternative Approach to Middleware

I can see why a single pass approach is preferable to the double pass, but in my mind, they are still both flawed and violate the single responsibility principle and also the interface segregation principle.


In both cases, the middleware is allowed to know too much about the entire process of accepting a request, and expecting a response; whether that be the double pass having a pre baked response instance, or the single pass being told about the 'next' item in the chain.

A Middleware should only care about what it is supposed to do in the simplest way possible, enforcing the SRP.


I am proposing that we really look at what the requirement is here, and come up with something that fits the bill properly, rather than rehash previous principles that smell bad. Here is something I started to touch on in my issue, and that @schnittstabil has also touched on.


<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface MiddlewareInterface
{
}


We need a base interface that can allow type hinting a single point of adding middleware to a queue for example.


<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface ResponseFactoryInterface
{
    public function createResponseInstance(): ResponseInterface;
}


We need an interface to allow implementers access to a factory method to provide whatever implementation of a ResponseInterface is desired for that Middleware.


<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;

interface RequestMiddlewareInterface extends MiddlewareInterface
{
    public function parseRequest(RequestInterface $request): MessageInterface;
}


We have an interface that provides Middleware for the inbound flow of a stack. Also note that to allow the Middleware stack to be short circuited, we only type hint a response to be MessageInterface. This means, normal behaviour for the stack is to proceed on a returned instance of RequestInterface, and if an instance of ResponseInterface is given, stop the inbound run of the stack.


<?php

namespace Psr\Http\Middleware;

use Psr\Http\Message\ResponseInterface;

interface ResponseMiddlewareInterface extends MiddlewareInterface
{
    public function parseResponse(ResponseInterface $response): ResponseInterface;
}


We have an interface that provides middleware for outbound flow of the stack. This allows us to have middleware that can, for example, change the cache headers of the response based on business rules.


<?php

namespace Psr\Http;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

interface ExchangeInterface
{
    public function exchange(RequestInterface $request): ResponseInterface;
}


We should also be opinionated on how a request is exchanged with a response, even if only to say "Hey I give you a request, you give me a response!". This would lie a level above in the namespace of course, being that it is not specific to messages or middleware.


Why Another Proposal


I can picture faces of interest and faces of concern over this idea. Let me explain some more detail.

We are violating the SRP and ISP in both implementations thus far, and we need to reimagine this as above to prevent that violation. Let me write the middleware use cases I've seen so far:

  1. I want to make a Middleware to handle requests.
  2. I want to make a Middleware to alter a response.
  3. I need to make an instance of a response to short circuit the exchange.

These are fundamentally three separate responsibilities, and must be separated. There is no valid argument against this, sorry to annoy anyone there, but that's the truth. To fix this from both current implementations, we segregate the interfaces, therefore separating the concerns.


Add to the above the fact we let the Middleware itself choose whether to continue the stack or not, by providing a callback in the form of 'next', we have a messy situation that needs cleaning up.


Why do we allow a Middleware to handle both inbound and outbound flow in one function, and also pass in a hook to the outside world? That hook, if implemented badly could allow a Middleware to cause all sorts of havoc by accessing things it should never know about!


No, let us be strict here and not let the Middleware know anything about the outside world, other than the request/response (dependent on interface) and it's single responsibility.


Benefits of the above proposal


There are the obvious ones being separation of concern and interface segregation, I think I've covered those.

  • The Middleware stack becomes the only place that can control the continued iteration of the stack, not the Middleware.
  • A Middleware can still be responsible for both inbound and outbound flow, by implementing both interfaces (although this is not the proper thing to do).
  • A Middleware can still short circuit the stack by returning a response, which still lies within SRP being that we only require a MessageInterface instance.
  • A Middleware stack can become responsible alone for making a response instance by implementing the factory method. This is also still within the SRP as it is there to exchange a request for a response. (Let's not argue about iteration and factory being two responsibilities, they aren't in this case.)

Also, please take note that I have specifically not used ServerRequestInterface. There is no reason at all that this implementation would not function in a client middleware environment also. Why repeat things by waiting for a client middleware proposal when one will do?


Wrap Up


As I've said before, this is no insult to anyone who has put work in so far, I am just coming at this from a fresh perspective. Please discuss this as I fear for this being finalised as another incorrect design that will be around for years to come; I never liked the double pass, and I don't like the single pass either. Something better is needed.

Michael Mayer

unread,
Feb 19, 2017, 2:11:22 PM2/19/17
to PHP Framework Interoperability Group
Hi John,

many thanks for your contribution. But as far as I can see, there is a big misconceptions about single responsibility principle (SRP) and interface segregation principle (ISP).

At first SRP; Robert C. Martin expresses the principle as follows:

A class should have only one reason to change.

SRP is NOT about objects, it is about classes. Objects are usually always(!) responsible for many things, for example a `car` object is responsible for the acceleration and the honking. SRP says only that a `car` class should not implement both responsibilities by itself! There are many ways we can have SRP in the car example. Delegation, inheritance and partial classes (traits) are the most common ones (e.g. wikipedia presents an example of SoC and partial classes, which is also an example for SRP).


Second, about your ISP considerations. Sorry, I cannot fully follow you on this. ISP is about:
The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use. [Wikipedia]

The current `MiddlewareInterface::process(ServerRequestInterface $request, DelegateInterface $delegate)` is fully dedicated to the situation, when you need both: the request and a way to delegate requests. If you do not need both, then `MiddlewareInterface` is the wrong interface for your use case. And please note: the current PSR does not force you to use it in that cases. It just do not provide some additionally useful interfaces. – Should some of your proposed interfaces be added to PSR-15? I believe not, for example your `ResponseFactoryInterface` has nothing to do with Middlewares, the interfaces instances does not sit in the middle of some input and output. Hence, I believe it is fine to have it in PSR-17.

Btw, ISP is NOT about the PHP `interface` feature! Robert C. Martin uses the word interface in the same sense as in graphical user interfacecommand line interface or network interface – in the same meaning as protocol or contract. You may want to read his blog entry 'Interface' Considered Harmful about the language feature.

Having said all that, thank you again for your thoughts – I share many of your concerns, but IMO most problems are not related the PHP `interfaces`, but to the contracts. For example `MiddlewareInterface::process`:
> * delegating to the next middleware component

Damn it! Why must a Middleware know, that other middlewares may be present? Is a Middleware responsible for the next one too???

Bests, Michael

John Porter

unread,
Feb 19, 2017, 5:04:01 PM2/19/17
to PHP Framework Interoperability Group
Hi Michael,

Thanks for your response. I have read about, and personally discussed the SOLID principles with Uncle Bob, and I do believe that there is more than one final interpretation of those principles. I can see your points, but raise you this:

SRP says only that a `car` class should not implement both responsibilities by itself!

Currently, the middleware proposal and current used implementation ask for exactly that. That the middleware be responsible for the inbound and outbound flow. Even if it is for the middleware to decide that for itself, it is still a breach of SRP allowing a class to decide that. My point is the choice should not be there in the first place, by way of defined interfaces.

My point about ISP is to follow the definition you stated. If we take away the choice of multiple responsibilities from the middleware, then we must separate the interfaces. And I do know that it is not about PHP interfaces, but that is what they are for, along with the same in JAVA, and class abstractions; they are used to define the contract between classes: Can this middleware affect the request? Can this middleware affect the response? That kind of question comes from the interface the class implements.

Should some of your proposed interfaces be added to PSR-15?

Granted on that one, I did not realise about PSR-17.

Korvin Szanto

unread,
Feb 19, 2017, 9:36:20 PM2/19/17
to php...@googlegroups.com
Hi Everyone,

On Sun, Feb 19, 2017 at 2:04 PM John Porter <jo...@designermonkey.co.uk> wrote:
Hi Michael,

Thanks for your response. I have read about, and personally discussed the SOLID principles with Uncle Bob, and I do believe that there is more than one final interpretation of those principles. I can see your points, but raise you this:

SRP says only that a `car` class should not implement both responsibilities by itself!

Currently, the middleware proposal and current used implementation ask for exactly that. That the middleware be responsible for the inbound and outbound flow. Even if it is for the middleware to decide that for itself, it is still a breach of SRP allowing a class to decide that. My point is the choice should not be there in the first place, by way of defined interfaces.

The middleware doesn't necessarily dictate where your behavior takes place. Follow Dependency Inversion and Interface Segregation and you can achieve a single responsibility for each class, even for the extra-strict (in my opinion) version of SRP/ISP you are presenting. If you want to have these concerns be separate, inject the behaviors as dependencies and treat the middleware itself as more of a behavior router. That way each of your individual behaviors are handled in individual classes.

A good example of this in my mind is containers. We have the container PSR that is super minimal (has / get). That doesn't mean that a container won't also try to do dependency injection and automatic instance inflation all kinds of other stuff. In fact most of them WILL. They simply delegate those responsibilities / behaviors to a dependency and there-by avoid SRP/ISP/OCP issues. IMHO 
 
My point about ISP is to follow the definition you stated. If we take away the choice of multiple responsibilities from the middleware, then we must separate the interfaces. And I do know that it is not about PHP interfaces, but that is what they are for, along with the same in JAVA, and class abstractions; they are used to define the contract between classes: Can this middleware affect the request? Can this middleware affect the response? That kind of question comes from the interface the class implements.

The current state of PSR-15 as I see it doesn't prevent tracking what the middleware is doing in the course of a request. Since the DelegateInterface is an instance that a middleware stack is injecting, you can track pretty easily how far the request moves through the stack.

Now if the issue is trying to preemptively know whether or not the middleware does this or that, PSR-15 currently doesn't cover inserting middleware into a stack. In my mind this is something that humans do so they should be aware of what the middleware does and is for when they add it to the stack. 

So with those two arguments in mind, can you provide an example where a middleware stack would need to be able to identify whether a middleware will be returning a response or a request or dieing without simply observing what happens when the request flows through the stack? 

Thanks for the interest and discussion, I'm very pleased with the state of the FIG these days, very constructive and interesting.

John Porter

unread,
Feb 21, 2017, 9:41:28 AM2/21/17
to PHP Framework Interoperability Group
Ok. Getting rid of all the solid fluff that is in the way, the one fundamental thing I want to help try and avoid is having one single function in a Middleware instance be in control (so to speak) of the middleware process.

Woody Gilk

unread,
Feb 21, 2017, 10:14:25 AM2/21/17
to PHP Framework Interoperability Group

On Tue, Feb 21, 2017 at 8:41 AM, John Porter <jo...@designermonkey.co.uk> wrote:
the one fundamental thing I want to help try and avoid is having one single function in a Middleware instance be in control (so to speak) of the middleware process.

Does this mean you want to remove the middleware dispatcher?

John Porter

unread,
Feb 21, 2017, 10:23:19 AM2/21/17
to PHP Framework Interoperability Group
Yes. As I've explained in the proposal, the stack should handle iteration and short-circuits, not each middleware. This is the reason there are two interfaces; one for request and one for response.

Woody Gilk

unread,
Feb 21, 2017, 1:10:06 PM2/21/17
to PHP Framework Interoperability Group

On Tue, Feb 21, 2017 at 9:23 AM, John Porter <jo...@designermonkey.co.uk> wrote:
the stack should handle iteration and short-circuits

Okay, let's test this theory. You have a requirement that all incoming POST requests contain a `Authoriation: Token <sha1-hash>` header. How do you respond with `HTTP 401` in this scenario?

Michael Mayer

unread,
Feb 21, 2017, 1:13:29 PM2/21/17
to PHP Framework Interoperability Group
As far as I can see, the essential difference between PSR-15 and your proposal is about the responsibility for dispatching the next middleware:
1. with your version: it is determined at compile time, by implementing the appropriate interface; thus the stack should be responsible for it only.
2. with the PSR-15 version: it is determined at run time, thus the stack and the middleware are responsible for dispatching the next middleware.

Django, StackPHP and Guzzle for example, have chosen an approach which seems to align better to your version. In essence, their middlewares could be typed as:

(RequestInterface -> ResponseInterface) -> (RequestInterface -> ResponseInterface)

Or, if we want to use PHP interfaces only, then we could type them:

interface ExchangeInterface
{
   
public function __invoke(RequestInterface $request): ResponseInterface;
}

interface MiddlewareInterface
{
   
public function __invoke(ExchangeInterface $next): ExchangeInterface;
}

At first glance that could solve your issues with the current PSR-15, the exchanger does not seem to be responsible for dispatching the next exchanger and middlewares are simple exchange factories. And moreover we do not need middleware dispatchers anymore.

Unfortunately that isn't very attractive in PHP, a middleware would look like:

class UnicornMiddleware implements MiddlewareInterface
{
   
public function __invoke(ExchangeInterface $next): ExchangeInterface {
       
return new class($next) implements ExchangeInterface {
           
private $next;

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

           
public function __invoke(RequestInterface $request): ResponseInterface
           
{
               
return (($this->next)($request))->withHeader('X-PoweredBy', 'Unicorns');
           
}
       
};
   
}
}

That's probably one of the reasons why StackPHP uses `__construct` and Guzzle uses closures. But even worse, the inner ExchangeInterface instance is still responsible for dispatching the next middleware :(

Are the PSR-15 interfaces so bad? From the perspective of a functional programmer, we can do some simple currying to change the typing to get better interfaces:

(RequestInterface -> ResponseInterface) -> (RequestInterface -> ResponseInterface)
// using curry:
(RequestInterface -> ResponseInterface) -> RequestInterface -> ResponseInterface
// and again:
((RequestInterface -> ResponseInterface) -> RequestInterface) -> ResponseInterface

Again, if we use PHP `interfaces` only, the last version could look like this in PHP:

interface ExchangeInterface
{
   
public function __invoke(RequestInterface $request): ResponseInterface;
}

interface MiddlewareInterface
{
   
public function __invoke(ExchangeInterface $next, RequestInterface $request);
}

Aside from the parameter order, the similarities to the PSR-15 version are already obvious (Sadly, the names and implied contracts are very different and PSR-15 does not describe a functional middleware pattern anymore :cry: ).

Anyway, the example above becomes much simpler with the current PSR-15 interfaces:

class UnicornMiddleware implements ServerMiddlewareInterface
{
   
public function process(ServerRequestInterface $request, DelegateInterface $delegate) {
       
return $delegate->process($request)->withHeader('X-PoweredBy', 'Unicorns');
   
}
}

Thus I believe your proposal does not fit better to the middleware pattern.

John Porter

unread,
Feb 21, 2017, 1:21:36 PM2/21/17
to Michael Mayer, php...@googlegroups.com
Sadly, I have not explained very well have I, as no one seems to grasp what I am on about.

The middleware does not need to know about the exchange at all, it is only responsible for receiving a Message and returning a Message. I am currently coding up the example to show you all.

Woody, for your scenario, the RequestMiddlewareInterface would return a ResponseInterface instance, this is why the return value is only typed to MessageInterface; to allow that short circuit to be returned, so that the stack can terminate.

I think my example (with tests) will highlight this better than I can explain; bear with me.
--
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/vCKxyraoqnc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/b0794cd8-089a-4753-bbf5-9a3d0e833b06%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Larry Garfield

unread,
Feb 21, 2017, 1:27:52 PM2/21/17
to php...@googlegroups.com
If I am following you, your issue is more that you would want to see three different types of middleware: Passthrough a request (with modification), turn request to response, and passthrough response (with modification).  Is that more accurate?  The core of a middleware dispacher would then be:

foreach ($requestHandlers as $h) {
  $req = $h->process($req);
}
$res = $coreHandler->process($req);
foreach ($responseHandlers as $h) {
  $res = $h->process($res);
}

Is that more what you're talking about?

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

John Porter

unread,
Feb 21, 2017, 1:37:22 PM2/21/17
to php...@googlegroups.com
That’s the most accurate description thus far Larry, yes. Why can’t I write that simply eh? :)

John Porter

unread,
Feb 21, 2017, 1:38:43 PM2/21/17
to php...@googlegroups.com
The methods called in the example aren’t right though, but the premise is accurate.

John Porter

unread,
Feb 21, 2017, 1:48:15 PM2/21/17
to PHP Framework Interoperability Group
Here's the code I'm testing at present (I know, i know, TDD!) Some of the interface names are obviously wrong but are the same as I proposed earlier.

<?php


namespace Manneken\Http\Middleware;


use Ds\Stack as DsStack;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Interop\Http\Factory\ResponseFactoryInterface as InteropFactory;
use Manneken\Http\ExchangeInterface;
use Manneken\Http\ResponseFactoryInterface;


class Stack implements ExchangeInterface, ResponseFactoryInterface
{
   
private $stack;
   
private $factory;


   
public function __construct(DsStack $stack, InteropFactory $factory)
   
{
        $this
->stack = $stack;
        $this
->factory = $factory;
   
}


   
public function addMiddleware(MiddlewareInterface $middleware)
   
{
        $this
->stack->push($middleware);

   
}


   
public function exchange(RequestInterface $request): ResponseInterface

   
{
        $message
= $this->iterateOverInboundStackWithMessage(
            $this
->getInboundStackFromStack(),
            $request
       
);


       
if (is_a($message, ResponseInterface::class)) {
           
return $message;
       
}


       
return $this->iterateOverOutboundStackWithMessage(
            $this
->getOutboundStackFromStack(),
            $this
->createResponseInstance()
       
);
   
}


   
public function createResponseInstance(): ResponseInterface
   
{
       
return $this->factory->createResponse();
   
}


   
private function getInboundStackFromStack(): array
   
{
       
return array_filter($this->stack->toArray(), function ($middleware) {
           
return is_a($middleware, RequestMiddlewareInterface::class);
       
});
   
}


   
private function iterateOverInboundStackWithMessage(array $stack, MessageInterface $message): MessageInterface
   
{
       
foreach ($stack as $middleware) {
            $message
= $middleware->processRequest($message);


           
if (is_a($message, ResponseInterface::class)) {
               
return $message;
           
}
       
}


       
return $message;
   
}


   
private function getOutboundStackFromStack(): array
   
{
       
return array_reverse(array_filter($this->stack->toArray(), function ($middleware) {
           
return is_a($middleware, ResponseMiddlewareInterface::class);
       
}));
   
}


   
private function iterateOverOutboundStackWithMessage(array $stack, MessageInterface $message): MessageInterface
   
{
       
foreach ($stack as $middleware) {
            $message
= $middleware->processRequest($message);
       
}


       
return $message;
   
}
}

So, the addMiddleware function doesn't care if it's a request, response, or both middleware types, and the stack is split properly at runtime.

I hope this example explains it better.


John Porter

unread,
Feb 21, 2017, 2:09:36 PM2/21/17
to PHP Framework Interoperability Group
Woody,

Here's an example meeting your criteria (I think):

<?php

namespace Example;


use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Middleware\RequestMiddlewareInterface;
use Psr\Http\Middleware\ResponseFactoryInterface;

class AuthHeaderMiddleware implements RequestMiddlewareInterface, ResponseFactoryInterface

{
   
public function parseRequest(RequestInterface $request): MessageInterface

   
{
       
if ($request->getMethod() === 'POST' && $request->hasHeader('authorization')) {
           
return $request; // Passthrough here
       
}

        $response
= $this->createResponseInstance();

       
return $response->withStatus(401); // short circuit handled by middleware runner implementing ExchangeInterface
   
}
}
 

Michael Mayer

unread,
Feb 21, 2017, 2:43:19 PM2/21/17
to PHP Framework Interoperability Group
Ok, despite some prototype related issues, I do not understand how you want to stack multiple RequestMiddlewareInterface instances?

For example one which adds the computation time, but only if the next middleware (e.g. the AuthHeaderMiddleware) returned with status === 200.

John Porter

unread,
Feb 21, 2017, 2:50:10 PM2/21/17
to php...@googlegroups.com
Could you be a little more clear for me?

Can you explain what you mean by prototype related issues?

Also, to aid me in showing accurate examples:

Where would the computation time be added, to a request or response? Can you show me how you’d achieve it with the double pass method, and I can show you the alternative.

I think I understand what you mean, but I want to be sure. Plus it’s late, I’m tired and I’ve had a beer :)
--

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/vCKxyraoqnc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to php-fig+u...@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.

Michael Mayer

unread,
Feb 21, 2017, 3:07:32 PM2/21/17
to PHP Framework Interoperability Group
Am Dienstag, 21. Februar 2017 20:50:10 UTC+1 schrieb John Porter:
Can you explain what you mean by prototype related issues?

I only see some issues with your code which are unrelated to your idea, that's all.

Where would the computation time be added, to a request or response? Can you show me how you’d achieve it with the double pass method, and I can show you the alternative.

No implementation of a another middleware needed, but Middlewares\ResponseTime would be an example. Anyway with a PSR-15 stack dispatcher, like Middlewares\Utils\Dispatcher, we can do:

$dispatcher = new Dispatcher([
   
new ResponseTimeMiddleware(),
   
new AuthMiddleware(),
   

]);

$response
= $dispatcher->dispatch(new ServerRequest());

John Porter

unread,
Feb 21, 2017, 4:55:24 PM2/21/17
to PHP Framework Interoperability Group
I was right about the prototype issues, my tests have been fixing those.

Here's my interpretation then of of the ResponseTime middleware:

<?php

namespace Example;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Middleware\RequestMiddlewareInterface;
use Psr\Http\Middleware\ResponseMiddlewareInterface;


class AuthHeaderMiddleware implements RequestMiddlewareInterface, ResponseMiddlewareInterface
{
   
const HEADER = 'X-Response-Time';

   
private $startTime;


   
public function parseRequest(RequestInterface $request): MessageInterface
   
{

        $server
= $request->getServerParams();
        $this
->startTime = isset($server['REQUEST_TIME_FLOAT']) ? $server['REQUEST_TIME_FLOAT'] : microtime(true);

   
}

   
public function parseResponse(ResponseInterface $response): ResponseInterface

   
{
       
return $response->withHeader(self::HEADER, sprintf('%2.3fms', (microtime(true) - $this->startTime) * 1000));
   
}
}

Because the middleware implements both interfaces, it would be applied (using my Stack example) in both directions. Hope this helps.

John Porter

unread,
Feb 21, 2017, 4:57:20 PM2/21/17
to PHP Framework Interoperability Group
Obviously I would have remembered to rename my example class there if I weren't about to go to bed.

Catch you all tomorrow!

John Porter

unread,
Feb 21, 2017, 4:58:58 PM2/21/17
to PHP Framework Interoperability Group
And I forgot to return $response in the parseResponse function. Not doing great here am I?

Stefano Torresi

unread,
Feb 21, 2017, 5:44:40 PM2/21/17
to php...@googlegroups.com
John, while I appreciate your passion, I should remind you that this is a mailing list, not a chat room, so could you please throttle yourself? We usually try to keep a soft limit of 2 posts per thread per day.

Thanks.

Michael Mayer

unread,
Feb 22, 2017, 6:05:59 AM2/22/17
to PHP Framework Interoperability Group
John, would you mind pushing your interfaces and Stack to github? Thus, everybody interested can play with your code, which probably would reduce mails here.

Josh Di Fabio

unread,
Feb 22, 2017, 6:13:26 AM2/22/17
to PHP Framework Interoperability Group
Hi John,

How would you implement a middleware which catches an exception thrown by an inner middleware and returns a response instead of allowing the exception to bubble up?

On Wed, Feb 22, 2017 at 11:06 AM Michael Mayer <mic...@schnittstabil.de> wrote:
John, would you mind pushing your interfaces and Stack to github? Thus, everybody interested can play with your code, which probably would reduce mails here.

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

John Porter

unread,
Feb 22, 2017, 6:17:37 AM2/22/17
to PHP Framework Interoperability Group
Please point me to examples that I can replicate, otherwise I can't answer your questions accurately.

Thanks

John Porter

unread,
Feb 23, 2017, 5:24:59 AM2/23/17
to PHP Framework Interoperability Group
Hi Josh,

I've been having a good old think about what you asked, as it confused me a little being that I don't code that way myself.

To me, exception handling, to be effective, should happen at your application level, not at such a low level as middleware; in my applications, the function calling $stack->exchange(...); would be wrapped in an application wide exception handler which would return a response based on that.

I'm concerned here for splitting out responsibilities in code, so that the middleware are responsible for creating a response, and if they can't, throw an exception. The stack is not the top level of an application, it is to be used inside an application; the app itself should be responsible for final output whether that be a true response or a handled exception.

If we had a router at the centre of the stack that talked to a domain, then of course that could catch any domain exceptions and translate them into a response or even throw an application level exception.

atanvarno69

unread,
Feb 23, 2017, 9:54:45 AM2/23/17
to PHP Framework Interoperability Group
I am proposing that we really look at what the requirement is here, and come up with something that fits the bill properly, rather than rehash previous principles that smell bad.

[...] `interface MiddlewareInterface {}` [...]


We need a base interface that can allow type hinting a single point of adding middleware to a queue for example.

An empty interface that exists only to extend other interfaces from to me smells bad. (The spec used to have one when it was trying to do both client and server-side middlewares; it smelt bad there too.) It suggests to me that the concerns you are trying to separate are not in fact separate, but part of the same concern (the concern of middleware: take a request, optionally using a delegate to get a response, return a response). YMMV.


We are violating the SRP and ISP in both implementations thus far, and we need to reimagine this as above to prevent that violation. Let me write the middleware use cases I've seen so far:
  1. I want to make a Middleware to handle requests.
  2. I want to make a Middleware to alter a response.
  3. I need to make an instance of a response to short circuit the exchange.
4. I want to make a Middleware that alters a response according to the handled request.

An example using the currently proposed PSR-15 interfaces:
class HttpResponsePreparingMiddleware implements MiddlewareInterface
{
   
public function process(
       
ServerRequestInterface $request,
       
DelegateInterface $delegate
   
): ResponseInterface {
       
/** Do nothing to the request, hand it on to later middleware. */
       
       
/** @var ResponseInterface $response Response received from delegate. */
        $response
= $delegate->process($request);
       
       
/** Check response content is acceptable to recipient. */
       
if ($request->hasHeader('Accept')) {
            $acceptable
= $request->getHeader('Accept');
            $sending
= $response->getHeader('Content-Type');
           
if(!in_array($sending[0], $acceptable)) {
               
// `buildErrorResponse()` is not shown here for brevity.
                $response
= $this->buildErrorResponse(406);
           
}
       
}
       
       
/** Ensure the HTTP protocol is acceptable to recipient. */
        $requestVersion
= $request->getProtocolVersion();
       
if ($response->getProtocolVersion() != $requestVersion) {
            $version
= ($requestVersion == '1.1') ? '1.1' : '1.0';
            $response
= $response->withProtocolVersion($version);
       
}
       
       
/** Return a request I know the client will accept. */
       
return $response;
   
}
}

This middleware has one concern: ensure a valid response is returned, for a given request. It needs the request to do that, so should use your `RequestMiddlewareInterface`, but it also needs a response and has no delegate to demand one from and my return type is wrong. So it should use `ResponseMiddlewareInterface` instead? Now I have the right return type and one of my dependencies, but I don't have a request.

So I then implement both interfaces: `parseRequest()` stores the request in a property and returns it; `parseResponse()` picks up the stored request and uses it. But why are these two methods? I only have one concern.


These are fundamentally three separate responsibilities, and must be separated. There is no valid argument against this, sorry to annoy anyone there, but that's the truth. To fix this from both current implementations, we segregate the interfaces, therefore separating the concerns.

No, it's not the truth. No, we don't need to artificially divide a single concern. The concern of middleware is to return a response from a given request. It does that by optionally delegating to a later middleware to perform its concern.

You seem to object to the fact that a middleware is aware that it exists in an environment and has a delegate to call upon. I don't understand why: the middleware design pattern is predicated on there being a linear flow of control between one or more middleware instances both in (for a request) and out (for a response). If you try to build middleware that is unaware of that fact of its design, then it stops being middleware. It is instead a request or response handler.

Also, `RequestMiddlewareInterface` having a return hint of `MessageInterface` is for the express purpose of short circuiting the stack. It is giving the middleware control over the stack, just as the option to call or not `$delegate->process()` does. You can't claim to remove middleware's awareness/degree of control of the stack and keep it by stealth.

Something akin to what Josh suggested, a very simple middleware:
class ThrowableCatchingMiddleware implements MiddlewareInterface
{
   
public function process(
       
ServerRequestInterface $request,
       
DelegateInterface $delegate
   
): ResponseInterface {
       
try {
            $this
->logger->debug('Request received', ['request' => $request]);
            $response
= $delegate->process($request);
            $this
->logger->debug('Response sent', ['response' => $response]);
       
} catch (Throwable $caught) {
           
// `buildErrorResponse()` builds a valid HTTP error response for a
           
// given HTTP error code. If the given code is not a recognised HTTP
           
// error code, 500 is used.
            $response
= $this->buildErrorResponse($caught->getCode());
            $this
->logger->error(
               
'Exception encountered',
               
['response' => $response, 'exception' => $caught]
           
);
           
return $response;
       
}
   
}
}

... Which apparently can't exist under you proposal. Because the proposal makes some sweeping assumptions and dictates implementation details:


The stack is not the top level of an application, it is to be used inside an application; the app itself should be responsible for final output whether that be a true response or a handled exception.

I can imagine a light weight application where the top level is the stack and all the functionality is plugged into it as middleware. Since web frameworks are toolkits for turning a HTTP request into a response, this doesn't seem particularly outlandish. In fact, it seems a particularly likely use case for the spec. Exception catching/logging middleware may not be the best design choice outside of such an app (and, actually, I'm not even sure about that), but that is no reason to deny an end user the choice of using it. Interop is all about letting solutions from a broad set of use cases work the same way, right?

The spec should promote good practice, yes. But it should not define good practice, especially in such a narrow and opinionated way.
Reply all
Reply to author
Forward
0 new messages