Closed alamin3000 closed 8 years ago
In addition, the new signature limits one middleware from delegating to another middleware!
Wut? I can't agree at all with you, that is the whole point of the DelegateInterface
argument in the signature and it is present in both interfaces
Also having two different versions of middlewares (server variant) at the same time only one delegate interface without accepting server variant request is also inconsistent and limiting.
You're wrong here. The interface does not typehint the $middleware
argument and by doing so allows the underlying implementation to decide if it will allow the mixing of different types middleware. Execution wise, it will not have any effect on the application in either case, unless the middleware expects a ServerRequestInterface
and receive RequestInterface
since then it will blow up in your face.
All you guys ("interoperability" community) needed to do was to write out the existing signature and make it official.
Honestly nothing works that way, literally nothing. afaik there are representatives from the major frameworks/projects that work on this and it has to be a solution that brings as much benefits to the table in order to be a standard, what is the point otherwise?
I will just continue to create own interfaces for my projects and continue to use wrappers because I don't trust using these interfaces directly in my applications.
1st. don't, 2nd you will occasionally hit a point where the framework you want to use will be implementing the standard so the sooner you get used to it, the better. \ Also it really doesn't matter what a you or I think is better (i was against the immutability of PSR7, but once I used it a bit, it is definitely the way to go). Again, major projects and frameworks with people with a lot of experience are working on coming up with a solution, so collective mind is definitely better.
Also, since the concept of "middleware" is not something new in general, though it's only now gaining popularity in PHP frameworks, I will say it is great that the FIG group started working on this PSR. I also think that single-pass middleware is more logical and I am happy with the approach, it is simple, easy to understand, on double pass ("All About Middleware" by Anthony Ferrara), you have to write boilerplate to migrate headers for example, since you have no idea if a previous middleware has modified something.
Plus I wouldn't compare PHP to JS (yet again) since both languages are absolutely different by design and PHP7 takes the it a step further, so please realize that there is no 1-fits-all solution, especially no JS/Node solution is a silver bullet in every other language/framework/tool. To argument myself as you did:
@DaGhostman, For some reason, I have a feelings you didn't get my arguments, and you don't even want to get the arguments. Since apparently you have "contributed" to this, you are taking bit personally.
DelegateInterafce
and MiddlwareInterafce
both have same method process
, but with different method signature. This makes impossible to delegate to another middleware, along with other disadvantages.
What's worse, the DelegateInterface signature has typehint of RequestInterface
, so it makes quite hard to do any server request handling because I can't type hint ServerRequestInterface
in the delegate class implementing the interface.
In addition, there is no way to make changes to Response and pass it along. I can't create a new modified Response object and pass THAT into next middleware.
No one asked PSR to re-invent wheel or try to be cool with making DelegateInterface. As I mentioned, previous signature was working fine without any issues. And has been practiced and used for long time by many different platforms/frameworks. PHP 7.1 is coming, all PSR needed to do is make the $next callable, a nullable. And write out the signature.
Listen, I am not here to argue. So unless someone can point me to the discussion about this PSR so I can read through some of the reasoning behind this or address some of my concerns, no need to reply. please.
I would ignore the remark about me contributing, so let's focus on:
DelegateInterafce
andMiddlwareInterafce
both have same method process, but with different method signature. This makes impossible to delegate to another middleware, along with other disadvantages.
From what I understand, you are looking to get the DelegateInterface
to behave the same way a middleware does. As far as I understand it, the delegate is responsible for linking the middleware and the next delegate (could be wrong). So I think of a delegate as a pipe, which I think is the most appropriate association, once starting it, you flow through it.
IDK if the issue you are addressing is not related to design, since every middleware implementation I've used has always had $next
and that's it, no other way of jumping out to another middleware beside that one.
... along with other disadvantages.
Mind elaborating?
In addition, there is no way to make changes to Response and pass it along. I can't create a new modified Response object and pass THAT into next middleware.
Well, you can. The idea in single-pass, afaik is that the very last middleware creates the response (think of it as a bottom-up approach). In order to modify the response your solution (with the interfaces at present) is:
AddResponseHeadersMiddleware
{
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$response = $next->process($request);
return $response->withAddedHeader('Content-Type', 'application/json');
}
}
ControllerAction
{
public function process(ServerRequestInterface $request, DelegateInterface $delegate = null)
{
// Assuming something like zendframework/zend-expressive is used
new TextResponse(json_encode(['foo' => 'bar']); // I know there is JsonResponse, but ..
}
}
Now lets assume the logic of linking delegates is FIFO, you define your middleware as:
[
AddResponseHeadersMiddleware::class,
ControllerAction::class
]
Or you can have (which is also very popular approach) global middleware and route middleware depending on what you want to do and to what you refer as "passing to another middleware"
And there you have it, the headers will get added to the response on the way out, this is a common thing, before and after middleware, basically if you want to modify the response you invoke the middleware first and change it before return.
PHP 7.1 is coming, all PSR needed to do is make the $next callable, a nullable.
Well you can default it to null in your implementation and it will still be valid, no need for changes of the interface for that, just make the signature:
// ...
public function process(ServerRequestInterface $request, DelegateInterface $delegate = null)
{
// ....
This solves the problem entirely unless I am missing a use-case? Keep in mind that PSRs are attempting to be as BC as possible, so nullable types from 7.1 (will require everyone to jump to 7.1 and there are still 5.3> versions on production) is a no-go, well until at least.
I was not (and am not for that matter) looking to argue with you, sorry if my previous reply came out wrong - didn't mean to. I am not taking it personally nor am I offended or anything, just my 2 cents on what you wrote. I maybe got a bit triggered by the comparison to JS, sorry about it
$delegate
) into $delegate = null
. Even though PHP wouldn't complain (at least for now) doesn't mean this is good thing to do. If that what is being suggested, in that case MiddlwareInterface should make its signature explicit by making the $delegate = null
. Which in turn makes DelegateInterface and MiddlwareInterface no different, hence original __invoke method signature did just that. So why change it? $next doesn't have to be glue as you are suggesting. It can be the $next middleware.All I am saying, that making changes to Middleware interface was really bold and unnecessary move. This will break so many codes and interoperability with other existing middlewares. Making the Request and Response standard was a big deal, along with the Container. Middleware is certainly was next piece of puzzle, but it was already standardized.
If there is link to any page/message board online about the discussion about this, I would love to read them. I am having really hard time getting my head around the reason(s) for changing this. Was the change just to make it different, or we truely had some flaw in the existing signature.
All I am saying, that making changes to Middleware interface was really bold and unnecessary move
I was bold but not unnecessary - using a simpler interface ultimately simplifies writing, describing and supporting middleware components. The change is usually very minor, quick and easy to implement.
I personally needed a lot of time to realize why this is (much) better than simply carrying on with the thing most of us are used to. We were doing it wrong. Correcting that mistake now may the last and only chance, since, once this is widely accepted as a standard, it'll be much harder to correct.
Was the change just to make it different, or we truely had some flaw in the existing signature.
There was a clear flaw, and trust me, we did not make this decision lightly. It would have been so much simpler for us to just describe and assign a number to the de-facto standard, but the problem is, the de-facto standard contains a flaw, and is therefore difficult to describe without pointing out the problem.
Modifying the response on the way in is unreliable - you can't know if headers you added will be changed or even removed, the whole response object could be replaced for that matter; thus, any middleware that modifies the response on the way is actually quite unreliable.
The only exception is middleware that attempts to set default values, expecting them to be overridden - a case that can be covered by the proposed interface, for example by checking for the existence of a header before setting the "default".
Finally, the idea of having request and response in both directions is conceptually not sound: a request, by definition is incoming state, and response is outgoing state. In a sense, middleware components model a cooperative process in which middleware components process a request and generate a response together - the de-facto standard interface does not mirror the idea that the request is what you get, and the response is what you return; even if it's more familiar to you and I, to someone who is new to the concept of middleware, it would be more difficult to explain how or why the response already exists prior to any of the components actually generating a response.
There are other minor issues with the de-facto standard as well, such as being able to accidentally return a meaningless, empty response - the proposed standard forces you to organize your middleware components into a meaningful stack, e.g. with something (at the bottom of the stack) returning something, or else your application is broken and should be aborted.
There are very compelling reasons not to attempt to standardize without addressing these issues. Arriving at that conclusion was somewhat of a personal journey for me, so I'm not in the least surprised by your surprise - this change will seem initially problematic, but I think that, with time, you will come to agree with it.
The community has always been fragmented on this point, but I am convinced that in this case, ultimately, the minority had it right.
Modifying the response on the way in is unreliable - you can't know if headers you added will be changed or even removed, the whole response object could be replaced for that matter; thus, any middleware that modifies the response on the way is actually quite unreliable.
This entire thing you can said about the 'way out' of the chain. Even if you change something in response way out, you don't know if it will be changed by the previous middleware in the chain.
The concept of having both Request and Response at the same time through a chain isn't a new concept for the industry. This is well practice in software engineering. Used by .NET (Context object), Java (Context object + explicit), PHP (existing middleware def used by most frameworks), JavaScript (Express and other frameworks). I just don't like PSR trying to standardize Interface AND industry at the same time. Since BOTH is practiced, and the community accepted one already, why was it necessary to change. Just don't feel like we need to go overboard with "standardizing". Idea is "interoperability", as the project/movement suggest.
I understand that proposing any change and how it can affect people and get "initial" angry protests. I am NOT about that. I really don't care for changes as long as it makes sense and people follow a standard. I also have qualm with other interfaces standardized by PSR, but I didn't make any comment because as long as nothing major issues and community accept it. But This, in my honest opinion, was unnecessary because we had one accepted by community which didn't have serious "flaw". I am still to see the "flaw" which new standard over calms from previous comment.
Anyway. I know I can't do anything by arguing here. Just hope you guys think this through. And I also hope committee tries to solve real problem if exists, not force their ideology. You guys want people to accept this movement of standards, not make problematic for community and other frameworks but going to fast and breaking too many things at once.
Best thing that happened to PHP community since PHP 5 is Composer and standardizing PSR-7 Messaging, PSR-11 (Container) and focusing on Middleware based development. So middleware needs to be standardized no doubt, but in my mind it already was.
This entire thing you can said about the 'way out' of the chain. Even if you change something in response way out, you don't know if it will be changed by the previous middleware in the chain.
Yes, but then you're expecting it -
I am still to see the "flaw" which new standard over calms from previous comment.
You're likely not seeing it right now because you haven't looked very closely yet.
As said, it took me a long time to come around as well - I think it was more than a month of confusing debate before it clicked for me. You can probably find the whole history of my posts on the fig list, initially coming from the exact same position you're coming from now. The difference seems insubstantial at first.
Think of it like this: request and response are input and output. It would be logical to expect request being passed as arguments, which are input, and response being returned as results, which are output. Passing the response first as input and then returning it as output is a round-about way to apparently simplify certain operations, such as setting default header values, but it leads to unpredictable side-effects. Passing the request and returning the response leads to fewer side-effects, because it more naturally matches the way a function works.
What's more, passing the response isn't necessary - you don't need it. So we're making things simpler by removing something unnecessary, which in turn reduces the ways in which middleware, and especially combinations of middleware, can malfunction.
We're not trying to force ideology out of personal preference. There's no force behind this proposal. Everyone is welcome to continue running their de-facto standard middleware and stacks.
And please don't think that we were looking for reasons to change something - I was personally very strongly against deviating from the de-facto standard, and initially thought that this PSR would be a slam-dunk, until we actually sat down and tried to describe it. It didn't hold water.
There has to be room to improve things, and proposing a standard is the right time to do that. If all we were going to do was slap a label on the de-facto standard, without scrutinizing anything, what would even be the point? Everything would continue to work just the same as it does today. Your de-facto standard middleware components already work fine in de-facto standard hosts. You'd have a breaking change in most hosts and middleware when they switch to the official interface (which you wouldn't be able to type-hint against in the first place - it would serve mainly as documentation) and then everything would go back to normal.
If we had found the de-facto standard to be good enough, the process itself would have served to validate and document the concept, so it would have value in that way. But since we didn't find it to be good enough, now I believe we're doing the responsible thing and dealing with the consequences of that, rather than recommending something we believe to be flawed.
This whole exercise is about more than just agreeing on things, it's about validating and refining ideas.
This is my final comment in this thread. And here it goes.
I read through and understand everything you guys are saying and PSR is trying to do with this standard. But whatever reason you are giving so far, it is not enough and I can counter all the arguments you are giving go on and on. But I have a feeling this wont do any good in this thread. And if all you are waiting on to "eventually" to "sink in", well it already does sink in to everyone. The interface is not hard and we know what it is trying to do. I don't think anyone is arguing that new proposed will NOT work. But it doesn't make middleware process any better. It does however limit to some of existing functionalities.
All I am saying, existing system was working and PSR could have make it standard if they wanted to. Even if they did rename __invoke
to process
it would've been ok. However, the new delegate concept which ideologically trying to differentiate from Middleware itself and further confusing with tying up with RequestInterface
, not providing ServerRequestInteface
varient... man doesn't make sense. It seems like you guys are trying too hard to solve problem which already had been solved by the industry. I still strongly hope you guys consider this and think carefully what is it that PSR trying to do and what should its role be.
I would recommend reading these in order:
Now that we've come this far, let's consider the HTTP spec, which should be a guiding principle for any interface that works with HTTP stuff.
According to HTTP spec, a response must contain a status code. Since the response of a request is an unknown when it hits the server, it is not valid to create a response early on. But we can create a response with 200 OK
and modify it later on, which is what double-pass implementations do. Let's try that:
200
status.ETag
header based on the request data, because responses should be cached.400
status being set on the body.The user just received an error response for what should have been a valid request, and we told the browser to cache it! This is a terrible situation that shouldn't be allowed to exist. We could blame it on incompetent developers. Or we could just prevent the mistake in the first place.
Thus, with much debate, we have arrived at the current proposal of using lambda. The proposal was not created in a vacuum and everyone working on this has skin in the game.
I don't think anyone is arguing that new proposed will NOT work. But it doesn't make middleware process any better. It does however limit to some of existing functionalities.
Well, I don't know what else to say: you are wrong, on both points.
This does make middleware better/cleaner - I speak from personal experience of having recently ported our middleware from the de-facto standard to this, which improved and simplified various middleware components on several points. (these are all proprietary, but you can examine the old/new versions of @oscarotero's middleware collection for comparison - he has already ported them all, so we already have a very versatile suite of available middleware components following this standard.)
It also makes middleware stacks better - again, I speak from personal experience, having recently ported middleman from supporting the de-facto standard, which (for one) clarifies the role of middleware as components that share the responsibility of producing a response, where the de-facto standard was more about sharing, or, in many cases, fighting for control. (You can compare the bottom section of the README of the last 1.x release, if you'd like - there's a section describing the concept of middleware, which completely changed, substantially for the better, as the concept is much more clearly defined.)
As for limiting existing functionality, this is simply false - this was a common claim among some of the debaters, myself included, early on, but it turns out to be false; there is nothing you can do (that you should be doing) with the de-facto standard that the proposed standard doesn't permit you to do; and usually in a simpler, cleaner way.
It seems like you guys are trying too hard to solve problem which already had been solved by the industry.
Yes and no.
No, lambda-style wasn't our original idea by any means.
Yes, this has already been solved by the industry, inconsistently. There are frameworks using double-pass, and there are (yes, a great number of) frameworks using lambda-style.
These two fundamental approaches to middleware have both been used and are both proven concepts in various different areas of the industry. We selected the one we believe is better, as opposed to simply selecting the one that is more widely used - this was not an easy choice.
I still strongly hope you guys consider this and think carefully what is it that PSR trying to do and what should its role be.
I assure you, we have, and are - we have all spent a lot of time working on this.
We did not arrive at these decisions lightly.
The only real argument for standardizing on the de-facto standard, is that it is already widely used - that this would cause less disruption. That's valid. However, it doesn't carry much weight, since, as you will learn if you attempt to port middleware components from the de-facto standard to the proposed standard, porting is actually very little work, and leads to simpler, cleaner implementations.
You're over assessing the effort required to do this. It's easy. And as discussed, even if we standardized on the de-facto as a standard, that would still be a breaking change. So this is a little more work, but it will improve the quality of middleware overall, so it's worth while.
Just to give you one concrete example: any middleware component that integrates a router, e.g. something that resolves paths and maps them to controllers, under the de-facto standard creates a very weird relationship between middleware and controllers, with only two possible solutions, neither of which are at all desirable.
I'm using very simplified code in these examples, for example not handling failure to resolve to a controller - these are just simple examples to illustrate the problem.
Option one - adopt a strange controller signature in which controllers don't create responses, but rather modify an existing response:
interface Controller {
public function run($request, $response) : ResponseInterface;
}
class RouterMiddleware {
public function __invoke($request, $response, $next) {
return $this->resolveController($request)->run($request, $response);
}
// ... controller resolution etc. goes here ...
}
What sort of framework has an already-existing response going into the controller? That's extremely counter-intuitive, and often problematic, since the controller can't know if pre-existing headers etc. are appropriate for the response it's going to generate. Not a good option.
Option two - correct the controller interface, e.g.:
interface Controller {
public function run($request) : ResponseInterface;
}
class RouterMiddleware {
public function __invoke($request, $response, $next) {
return $this->resolveController($request)->run($request);
}
// ... controller resolution etc. goes here ...
}
Using this approach, we get a more traditional controller signature - but here, we simply ignore the existing response object and replace it entirely - which negates any work that may have been already done on the response object, which negates any supposed value of even having the response object available in the first place.
The bottom line is that the de-facto standard is an extremely poor fit for the controller scenario, which will occur in some form in just about any middleware stack, so ignoring this would be extremely problematic.
The proposed standard is a perfect natural fit for the traditional controller:
interface Controller {
public function run($request) : ResponseInterface;
}
class RouterMiddleware {
public function __invoke($request, $next) {
return $this->resolveController($request)->run($request);
}
// ... controller resolution etc. goes here ...
}
Besides being a better fit for the controller pattern, this is an all-around safer approach where really nothing surprising or weird can happen. If another middleware component wishes to decorate the response, it must do so after the response has been created by the controller - no option to run the risk of decorating a response that ends up getting thrown away. If another middleware wishes to affect the processing of the request, it must do so not by attempting to cause side-effects by modifying the response object, but by decorating the request object.
Fundamentally, this approach simply separates request/input from response/output, the way any function would normally work, as opposed to treating the response as input, which it is not, in HTTP terms, conceptually, or otherwise. This makes it (for one) a better fit for controllers, but also more generally, as it creates a clear, direct input/output flow.
If this is not enough to convince you, I tried my best.
I hope that you will give this PSR a chance either way, as I am pretty firmly convinced that you and everyone else will find this approach to be simpler, cleaner and overall better.
Some of the people who have been most instrumental in the introduction of middleware and stacks to the PHP world are behind this proposal, please keep that in mind. We're not a random group of nutters who decided to change shit up just 'coz we think it's fun to mess with you ;-)
We worked hard to ensure this proposal is the best it can be, and, yes, sometimes improving things long-terms means a little more work and investment up front. You will likely spend less time refactoring your middleware components than I spent posting to this thread :-D
Since
Zend\Stratigility
version 1.3 introduced the http-interop version ofMiddlewareInterface
, I got to explore this new "standard". I honestly don't see the benefit of changing existing and well established middleware method signature that is used widely not only in php community but other platforms like javascript as well. All you guys ("interoperability" community) needed to do was to write out the existing signature and make it official.In addition, the new signature limits one middleware from delegating to another middleware! Just doesn't make sense. Because of having same name
process
with two different signature this is not possible.Also having two different versions of middlewares (server variant) at the same time only one delegate interface without accepting server variant request is also inconsistent and limiting.
Honestly, it seems like my excitement of the new movements with "standards" and having faith in them is just lost. I will just continue to create own interfaces for my projects and continue to use wrappers because I don't trust using these interfaces directly in my applications.