I've re-read everything that was discussed in my last issue #46 
<https://github.com/http-interop/http-middleware/issues/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 <https://github.com/schnittstabil> has 
also touched on.


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


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


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


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


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

-- 
You received this message because you are subscribed to the Google Groups "PHP 
Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to php-fig+unsubscr...@googlegroups.com.
To post to this group, send email to php-fig@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/php-fig/b929f88c-6794-4ec6-afca-14d23f5eec51%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to