-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create initial proposal for middleware interface #755
Conversation
I would rename it to HTTP Middleware as it is closely PSR-7 related, but middlewares themselves are not. |
I would think @mindplay-dk has right of first refusal as "editor" here, since he brought it up. |
* [Zend Stratigility](https://github.com/zendframework/zend-stratigility/blob/1.0.0/src/MiddlewarePipe.php#L69-L79) | ||
* [Relay](https://github.com/relayphp/Relay.Relay/blob/1.0.0/src/MiddlewareInterface.php#L24) | ||
* [Slim](https://github.com/slimphp/Slim/blob/3.4.0/Slim/MiddlewareAwareTrait.php#L66-L75) | ||
* [Middleman](https://github.com/mindplay-dk/middleman/blob/1.0.0/src/MiddlewareInterface.php#L24) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about keeping these projects sorted alphabetically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if it's worth mentioning, but there are actually quite a bunch of projects out there using the exact same interface, while not being frameworks, they provide middleware implementing that interface.
For example, see this list right here, taken from https://github.com/refinery29/piston/pull/192:
Interface
A bunch of projects provide and/or use exactly the same interface, for example
bitexpert/adroit
madewithlove/glue
radar/adr
relay/relay
zendframework/zend-expressive
zendframework/zend-stratigility
- ...
Existing Middleware using this interface
akrabat/rka-ip-address-middleware
akrabat/rka-scheme-and-host-detection-middleware
bear/middleware
hannesvdvreken/psr7-middlewares
los/api-problem
los/los-rate-limit
monii/monii-action-handler-psr7-middleware
monii/monii-nikic-fast-route-psr7-middleware
monii/monii-response-assertion-psr7-middleware
mtymek/blast-base-url
ocramius/psr7-session
oscarotero/psr7-middlewares
php-middleware/block-robots
php-middleware/http-authentication
php-middleware/log-http-messages
php-middleware/maintenance
php-middleware/phpdebugbar
php-middleware/request-id
relay/middleware
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the additional list!
@pmjones I'm perfectly happy to let @mindplay-dk take ownership. My goal was simply to get the ball rolling. |
@sagikazarmark I'm not opposed to a rename. |
@shadowhand Good form, sir. :-) |
Just dropping a link to the draft I wrote here: https://gist.github.com/mindplay-dk/1d60ccfa083acfca32698d3c6d9a0945 I'm not going to do much more now - I don't have time. Will check in here and on the mailing list when I can. @shadowhand feel free to use whatever you want from what I drafted. (That awesome list by @localheinz should be added to it, I think - I started, but only have a few items on mine.) |
Very good. @mindplay-dk thanks for bringing up the idea; @shadowhand it looks like you're editor and lead if you want to be. I'll be happy to fill the role of coordinator if you want me to. |
I just pushed a bunch of changes to this, including a complete interface, as well as a definition for the exception and trait for server request checking. Please review and let me know if other changes need to be made before an entrance vote. @pmjones should I list you as the sponsor of this proposal? @mindplay-dk I have integrated your draft with my proposal. Some of it was directly copied in and some of it was edited. Please let me know if you don't agree with my changes. @localheinz the list of middleware has been updated from your list. Thank you for sharing it! |
@shadowhand As a sponsor or coordinator, your call. I think this is a very good start and ready for presentation to the group once a second sponsor is found. |
@MWOP would you be willing to be the second sponsor, since Zend Framework has a clear stake in this proposal? |
I think you intended to mention @weierophinney, right?! |
@localheinz oops yes I did. |
I think RequiresServerRequestTrait::assertServerRequest() should return ServerRequestInterface for code completion. Maybe another method name like ensureServerRequest is better. |
@n1215 I don't agree. Method chaining increases cognitive load for the sake of convenience and could be abused with immutable objects EDIT: see https://ocramius.github.io/blog/fluent-interfaces-are-evil/ |
@shadowhand |
Returning an object is a fluent interface. There is no need for it. |
* | ||
* @throws ServerRequestRequiredException | ||
*/ | ||
private function assertServerRequest(RequestInterface $request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably be protected
to allow class hierarchies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would that be necessary? A trait can be used in any scenario and so using protected
offers no advantage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this trait be here at all? I mean, for cache there is a util repo. Any non-interface stuff should be placed in such thing and should not be part of the standard IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between shipping with an optional trait vs having a util repo? Personally I think having a trait is more efficient and easier to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it makes it part of the standard, while it isn't. The less content the better it is. And it's an already existing pattern within the FIG.
IMO the exception and trait are too much - it raises more questions than answers. The exception itself is a runtime-exception anyhow; runtime-exceptions are unchecked, so it doesn't actually matter if these are consistent or not. I still think that requiring everyone to do a run-time check to see if the incoming request is a server-request, is really problematic - the large majority of middleware will have to implement this check; it seems wrong to favor the most rare case and make every middleware check with I would strongly prefer having two interfaces:
Since the second interface is by far the most rarely used, having to do |
I agree with @mindplay-dk. Are there any cases when the same middleware can be used for both kinds of messages at all? Because that seems to be the reason behind having one common interface, but I don't think it's a common case, if possible at all. |
The problem with having two different interfaces is in type hinting. At the very minimum, we would end up with three interfaces (one empty): interface MiddlewareInterface {} // totally empty
ServerMiddlewareInterface extends MiddlewareInterface
{
public function __invoke(...);
}
ClientMiddlewareInterface extends MiddlewareInterface
{
public function __invoke(...);
} The only difference between server and client middleware is the usage of methods that are associated with |
Agree with @mindplay-dk as well. In addition, Middleware that can be used for both RequestInterface and ServerRequestInterface can be typehinted against just RequestInterface, since (if memory serves) ServerRequestInterface extends it. |
@pmjones sort of, but it doesn't provide protection for ensuring that necessary methods exist, hence the addition of the trait to this proposal. ServerMiddlewareInterface cannot extend from MiddlewareInterface, because the signatures are different. See here: https://3v4l.org/fVPAV |
I guess he meant the case when the middleware does not implement the interface, but is a callable. |
@shadowhand My point (perhaps mistaken) is that a dispatch mechanism might take any combination of ServerRequest middlewares and (Client)Request middlewares; a middleware used in that dispatch cycle typehinted to (Client)Request can still receive a ServerRequest object and fullfill the typehint. (Perhaps I am forgetting something.) |
@pmjones that's correct, but how the middleware check that the request is of the correct type? There needs to be some mechanism for that and I feel this proposal is incomplete without (at minimum) a standard exception class that can be caught. |
(/me ponders) I think for a ServerMiddleware typehinted to ServerRequestInterface, obviously no need to check for type, since it's the extended type. And I think for a ClientMiddleware typehinted to RequestInterface, there's no expectation of the ServerRequest methods, so there's no need to check for type there either. So for those who want to write a middleware that works for both Request and ServerRequest, depending on only the RequestInterface seems like the right thing to do. I feel like I'm missing an important part of your question somehow. |
p.s. I'm coming at this from the direction of wanting two middleware interfaces, one for client requests and one for server requests. Perhaps that's the disconnect. |
I don't have the ability to deploy at the moment. @reinink could you do a site update at your convenience? |
@svpernova09 Will do! |
@shadowhand two issues. Regarding Also, I wonder, with How you add or remove middleware doesn't affect my ability to dispatch a request - that's really about manipulating an internal array/stack, which is not how I implement immutability in my dispatcher, which is fully-immutable once instantiated; if one wants to manipulate the array/stack of middleware components, one would need to do that prior to instantiation, which is simpler. Also, my dispatcher creates/initializes middleware on the fly, so that interface wouldn't even make sense or work for me, and I'm planning some other changes that would clash with this even more. This makes me think that this part of the interface is first of all not necessary, but secondly, doesn't help interoperability - instead, it feels opinionated, and I feel it's actually standing in the way of interop. I realize it's listed as optional, but an optional interface doesn't guarantee interop, so it doesn't even really work for that. In addition, the ability to process a request probably shouldn't be optional? It seems like this an interface any dispatcher could implement, and it probably should be a |
@mindplay-dk I would refer to https://groups.google.com/forum/#!topic/php-fig/GTm9ho6rFts which is covering the exact same things. |
👍 and https://groups.google.com/forum/#!topic/php-fig/V12AAcT_SxE regarding |
@shadowhand @mnapoli I read both of threads, and I don't feel like either of them addresses either of the issues I'm highlighting.
|
Agreed.
Agreed.
That's already an improvement, even though still vague but that's just my opinion. While I agree with your remarks about |
Note that I'm not making that assertion - I'm not opposing the interface, as long as it's optional, and separate from the interface that makes it dispatchable. The existence of a middleware delegate interface does seem to have some merit in terms of offline inspections and IDE support, do you not agree? I'm currently implementing a different type of middleware stack, not for HTTP requests, and I'm using the approach with type-safe delegate - I will say that, in any other regard besides inspections/IDE support, those delegates are themselves merely dumb delegates to internally-generated closures, e.g. something like: class MiddlewareDelegate implements MiddlewareDelegateInterface
{
/**
* @var callable
*/
private $delegate;
/**
* @param callable $delegate
*/
public function __construct(callable $delegate)
{
$this->delegate = $delegate;
}
/**
* @param Request $request
*
* @return Response
*/
public function resolve(Request $request)
{
return call_user_func($this->delegate, $request);
}
} In other words, this doesn't appear to have any value beyond external type-safety - internally, it's still I am somewhat concerned about the performance implications - in micro benchmarks, these dispatchers are going to measurably slower than simpler dispatchers using callables with no object-wrapping. In practice, probably only measurable in micro benchmarks though; in practice, the cost is negligible for sure. |
Do you have an example? I really don't see it. Here is something concrete: my implementation of |
@mnapoli By changing this into e.g. Even for those who don't use static analysis tools or an IDE, you'd get the benefit of a bad call failing immediately at the call site, as opposed to failing later, or (worse) executing without error and producing a bad result - that sort of thing is much harder to debug. |
Oh OK you are talking about In the thread I linked there was this proposal: https://groups.google.com/d/msg/php-fig/V12AAcT_SxE/ZrNIz46zCAAJ What do you think about it? It's a middleground between simplicity ( interface Delegate
{
public function __invoke(ServerRequestInterface $request) : ResponseInterface;
} Can be then used like this: function (ServerRequestInterface $request, Delegate $next) {
return $next($request);
} WDYT? |
@mnapoli the problem with Also, some people have voiced concern that reserving |
On a side note, an interface like this serves more than one purpose: interface DispatchableInterface
{
public function dispatch(RequestInterface $request): ResponseInterface;
} That is, a middleware stack could implement it - or a simple controller could implement it; which means you could use, in some contexts, a simple controller or an entire middleware stack interchangibly, which could create interesting opportunities, as this could work for a bunch of different types of components. I would not name it |
It's simpler and it's very similar to current middlewares:
But… that's what PSR-7 middlewares are doing today: when has it become bad/complex? Everybody uses it and it works so well exactly because it's simple. We don't need to have names for everything. We struggle finding a name for that thing because it's nothing really, it's just an action: "call the next middleware", there's nothing more to it. We have a solution today, it works, it's simple, let's try to keep its simplicity. Trying to force it into a concept of a frame or a delegate or something is just adding complexity for no gain. If we reaaaally want type safety (I see no real added value because any good middleware framework will type-hint correctly the $next callable anyway)
That's fine. Will there really be an implementation of the "delegate/frame" that needs to be invokable for other reasons? Do we have actual examples of that? (not just theoretical scenarios) And if so, can the project write an adapter?
I think you are confusing |
I've heard this argument quite a few times (or in a more general form: everyone is doing that, how can it be wrong?). I don't think it's a valid argument. Someone started it, some other people continued the "standard" and that's why it became so popular. It doesn't mean it's good (or bad), but again: it doesn't make it a valid argument.
The concept of delegates exist in other languages and I am very happy when I can use a language where it exists. The fact that it doesn't exist in PHP shouldn't keep us from "emulating" the idea. If PHP receives delegates in a future version, it will be easier to adapt.
No, it's probably bad design, but I still agree with @mindplay-dk. For me using |
👍 I don't have much to add so I'll avoid going circles. Just about this:
A standard is not a place for innovation (I personally learnt this with ContainerInterface which was massively rejected years ago). What if we standardize something that sucks (whatever the reason)? On the other hand past experience is very useful:
I've never seen |
Although there is a chance for that (see PSR-6), I don't think that could happen because there is no better solution, rather because the development process sucks. The fact that there are no problems with |
in my opinion, using an idiomatic interface/method does have an advantage: you can type-hint it for static analysis and IDE support. And it will fail early, which makes for easier debugging. Both of those things are practical, not theoretical. The only advantage of Sure, a
What could be less innovative that a plain old interface? A standard should be more than a formalization of the familiar - if we can do something better/cleaner and fix mistakes of the past, now is the only opportunity to do that. We've already (most of us) crossed the bridge from double-pass to lambda-style, which was an important step, and a massive break from the de-facto standard, which will benefit the community in the long run. |
@sagikazarmark I'm not against improvements, but the only way to be sure something is stable/good enough is to try it out in real scenarios. If we innovate, we must then take some time (months/years?) to try that out, else (yes) we risk PSR-6 again. Even PSR-7 has issues that we only discovered when actually using it widely.
I agree with that.
I don't agree with this: all stack/pipe implementations I've seen type-hint properly the arguments so it will fail early (https://github.com/zendframework/zend-stratigility/blob/master/src/Next.php#L78, https://github.com/slimphp/Slim/blob/3.x/Slim/MiddlewareAwareTrait.php#L66) if you pass something wrong.
I could agree with that if
By that logic it's impossible to do something new unless we add new operators/concepts to the language. |
@mnapoli I've commented on this extensively on the list, but I'll summarize here:
|
@weierophinney Just to be sure: are you talking about |
@mnapoli Yes, the current |
This documentation is a transcript of the discussions in https://groups.google.com/d/topic/php-fig/V12AAcT_SxE/discussion and #755
@weierophinney your response in the mailing list thread was great, I wasn't expecting existing middlewares to work with PSR-15 stacks (and the other way around), so I had no idea you wanted to make the 2 systems work together at that level. I've opened a PR to document all that: #807 Reviews are welcome. |
I'd been wanting invokables as well, but when I started considering what it would take to make the transition within Expressive or Slim, I realized that doing so made migration substantially harder. Having just come off of Zend Framework v2 -> v3 transitions, I recognized the same challenges we had there, as well as the same opportunities to make the transition easier for both library authors and consumers. |
Based on discussions here: https://groups.google.com/d/msg/php-fig/vTtGxdIuBX8/NXKieN9vDQAJ