-
Notifications
You must be signed in to change notification settings - Fork 57
Refactor pipe #134
Refactor pipe #134
Conversation
src/MiddlewarePipe.php
Outdated
*/ | ||
public function pipe($path, $middleware = null) : self | ||
public function pipe(string $path, MiddlewareInterface $middleware) : self |
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.
There's an even better way: reverse the arguments, and make the path optional:
public function pipe(MiddlewareInterface $middleware, string $path = '/') : void
(Note that I think this should be a void
return as well; let's remove the fluent interface usage).
This will make it more clear that what's really optional is the path.
Yes, it's a BC break. However, it's one we should have likely done a long time ago. The only reason the arguments were in this order in the first place was because I had done a literal port from Sencha Connect, without considering whether the signatures made sense within PHP.
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.
You have right. However IMO it drop readability after usage this method. Look at https://docs.zendframework.com/zend-stratigility/middleware/ . If path will be second parameter it will be hard to determine path or which middleware has defined path. With two separate method it's cleaner
src/MiddlewarePipe.php
Outdated
* | ||
* Each middleware should be executed every request cycle. | ||
*/ | ||
public function pipeMiddleware(MiddlewareInterface $middleware) : self |
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 one would no longer be necessary if we approach it the way I suggest above.
@weierophinney My comment hide after commit so I past it again: You have right. However IMO it drop readability after usage this method. Look at https://docs.zendframework.com/zend-stratigility/middleware/ . If path will be second parameter it will be hard to determine path or which middleware has defined path. With two separate method it's cleaner |
Then the appropriate solution would be: public function pipe(MiddlewareInterface $middleware) : void;
public function pipeForPath(string $path, MiddlewareInterface $middleware) : void Perhaps you could do a poll on the forums to see which solution users would prefer? I could tweet it from the zfdevteam twitter account for reach. |
Good compromise, but my code make less mess (look at changed tests) ;) |
I guess that's my chance to suggest utility functions again? 😛 use function Zend\Stratigility\path;
$pipeline->pipe($middleware);
$pipeline->pipe(path('/foo', $middleware)); |
@danizord How |
@snapshotpl yep, that function would be just a syntax sugar on top of: $pipeline->pipe(new PathMiddlewareDecorator('/foo', $middleware)); |
I like the suggestion @danizord makes here; it would allow us to remove the $app->pipe(new PathMiddlewareDecorator('/foo', $middleware, $container)); or do it with a utility function. Another option would be a closure exported from the application: public function createPathMiddlewareDecorator()
{
$container = $this->container;
return function (string $path, $middleware) {
if (! $middleware instanceof MiddlewareInterface) {
$middleware = $this->prepareMiddleware($middleware);
}
return new PathMiddlewareDecorator($path, $middleware);
}
} Which would then operate like this: $path = $app->createPathMiddlewareDecorator();
$app->pipe($path('/foo', SomeMiddlewareName::class)); Thoughts? |
@weierophinney I like the |
@danizord Then I'd suggest:
|
@weierophinney Looks perfect for me 👌 |
Looks perfect. I this case we remove "dispatching" functionality from Stratigility (Next class) and leave Stratigility pure PSR-15 implementation (as middleware dispatcher). |
Exactly!
…On Dec 14, 2017 5:25 PM, "Witold Wasiczko" ***@***.***> wrote:
Looks perfect. I this case we remove "dispatching" functionality from
Stratigility (Next class) and leave Stratigility pure PSR-15 implementation
(as middleware dispatcher).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#134 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABlV-mBh7VCDyytbWte0pz4X9z7rOc0ks5tAa6CgaJpZM4RCuxO>
.
|
@weierophinney @danizord I made prototyped changes we have discussed. For me it looks awesome. |
src/MiddlewarePipe.php
Outdated
@@ -29,16 +28,14 @@ | |||
* | |||
* @see https://github.com/sencha/connect | |||
*/ | |||
class MiddlewarePipe implements MiddlewareInterface | |||
final class MiddlewarePipe implements MiddlewareInterface |
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.
Don't make this final, please. We extend it in Expressive.
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.
I knew you would ask it. Why not use composition in Expressive instead of inheritance?
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.
Agree, I think we can't extend it anymore in Expressive, because then pipe
will accept only MiddlewareInterface
. Currently we can pipe middleware, callback or string (from container) and we wrap it inside the application. If we would extend MiddlewarePipe then we can't change signature of pipe
and folks would need to wrap everything on piping.
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.
That's a good point; I'm good with this, then.
{ | ||
/** | ||
* @var SplQueue | ||
*/ | ||
protected $pipeline; |
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 needs to be protected, for the same reason as above.
test/MiddlewarePipeTest.php
Outdated
@@ -50,7 +50,7 @@ private function createFinalHandler() : RequestHandlerInterface | |||
|
|||
public function testHandleInvokesUntilFirstHandlerThatDoesNotCallNext() | |||
{ | |||
$this->pipeline->pipe(new class () implements MiddlewareInterface | |||
$this->pipeline->pipeMiddleware(new class () implements MiddlewareInterface |
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.
You'll need to update this, but I think you're aware of that. :-)
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.
Yes :-)
@snapshotpl @weierophinney now that Here's an example implementation of this approach: https://github.com/danizord/mid/blob/0.2/src/MiddlewarePipeline.php |
I like this idea; it would mean that zend-diactoros's Thoughts? |
@weierophinney Mhmm I can't see the use case for that 🤔 . If that's the desired behavior, you can call |
Let's take an example from Node. If you build an HTTP server in Node, you pass it the application, and the application is responsible for writing to the response: let server = new http.Server();
server.listen(app); Internally, the This is similar in Rack: require 'rack'
Rack::Handler::WEBrick.run app And with WSGI: from wsgiref.simple_server import make_server
httpd = make_server(hostname, hostport, application)
httpd.serve_forever() The $request = ServerRequestFactory::fromGlobals(); // or from the async server...
$response = $server->handle($request, $application);
$emitter->emit($response); However, for that to be reliable, we'd need to have some guarantee that the middleware pipeline will return a response; i.e., Alternately, we ditch the idea of $request = ServerRequestFactory::fromGlobals(); // or from the async server...
$finalHandler = new class implements RequestHandlerInterface {
public function handle(ServerRequestInterface $request) : ResponseInterface
{
return new EmptyResponse(500);
}
};
$response = $app->process($request, $finalHandler);
$emitter->emit($response); This is quite similar to what the Thoughts? |
I've pulled this branch from @snapshotpl locally, and started playing with it. The problem is: what code throws the exception? Right now, as implemented, If The linked suggestion you made also doesn't work: what happens when the queue is exhausted? We're now back to the question of where is the exception raised? For that, I suggest one of two possibilities:
And, of course, the third possibility is that we do not implement |
Did you check https://github.com/danizord/mid/blob/0.2/src/MiddlewarePipeline.php#L37-L39? If you call If you call
Why? I think it's out of scope for the Or I'm missing something? |
Regarding:
I somehow missed that you were checking the current queue when you called |
You meant Diactoros? If so, I think it should wrap the pipeline call in a try-catch block.
IMHO yes. That would make it flexible for Edit: to clarify, I think |
@snapshotpl On a local branch based on your own, I've done the following:
Would you like me to push them to your branch? |
@weierophinney Sure 👍 |
b6db186
to
20e4180
Compare
Modifies tests to cover _only_ the behaviors they now implement.
This replaces the path segregation features of the original stratigility MiddlewarePipe with a middleware decorator. The decorator receives the path prefix to segregate by, and the middleware to dispatch when matched. During `process()`, it checks to see if the prefix is matched by the current URI path at an appropriate boundary. If not, it acts as a pass-through to the handler. If it *does* match, it does the following: - Creates a new request that contains a URI that truncates the prefix from the path. - Prepares a request handler that decorates the original in order to process it with the original incoming request. - Dispatches the composed middleware using the new request and the decorated request handler. I have pulled relevant tests from the original test suite related to path segregation and adapted them to test against the new PathMiddlewareDecorator; I have also added an integration test demonstrating that nesting works as expected within a pipeline.
This function, when imported, simplifies creation of path-segregated middleware. It allows you to refactor this: ```php use Zend\Stratigility\Middleware\PathMiddlewareDecorator; $pipeline->pipe(new PathMiddlewareDecorator('/foo', $middleware)); ``` to: ```php use Zend\Stratigility\path $pipeline->pipe(path('/foo', $middleware)); ```
- InvalidMiddlewareException is no longer thrown - Route is no longer used internally
Too long of lines
20e4180
to
b7e963b
Compare
Moves sections around to move the signature changes section first in the list of public interface changes. Also moves the documentation of PSR-15-related signature changers to the PSR-15 section. Adds signature changes for MiddlewarePipe and Next, documents the new PathMiddlewareDecorator and its associated `path()` utility function, and the removed Route and InvalidMiddlewareException classes.
Documents the `PathMiddlewareDecorator` and associated `path()` utility function, and ensures all documentation is up to date with changes in this PR and on the release-3.0.0 branch.
@snapshotpl I think we can merge this; I have a list of things to address in the current develop branch, and will start opening issues and/or PRs to do so. The new utility functions can be part of a new PR. |
@weierophinney please give me one day to review it, ok? |
@snapshotpl Sure; will push the CHANGELOG in a few minutes, but will wait until tomorrow to merge. |
@snapshotpl In backporting the The fix is easy: I just need to grab the path from the original request URI instance and inject it into the request URI instance provided to the handler. I'll provide tests and a patch shortly for this branch. Let me know when you've had a chance to review. |
This patch backports the following classes from the release-3.0.0 development series: - `Zend\Stratigility\Middleware\CallableMiddlewareDecorator`, for decorating middleware that follows the PSR-15 signature. - `Zend\Stratigility\Middleware\DoublePassMiddlewareDecorator`, for decorating callable middleware that follows the double pass middleware signature. - `Zend\Stratigility\Middleware\PathMiddlewareDecorator`, for providing path-segregated middleware. This required also extracting the class `Zend\Stratigility\Middleware\PathRequestHandlerDecorator`, which is used internally in order to decorate the request handler passed to middleware it decorates. All classes and their were updated to adapt to the http-middleware-compatibility shims.
Discovered while backporting to the 2.X series. Essentially, a path segregated middleware should still be able to pass a new request on to the next layer. However, in doing so, the _path_ should be reset. This patch updates the `PathMiddlewareDecoratorIntegrationTest` to reflect this behavior, and updates the anonymous request handler created by the `PathMiddlewareDecorator` to invoke the composed handler with the runtime request after resetting its URI instance with the original path.
ee21d90
to
127502b
Compare
This patch backports the following classes from the release-3.0.0 development series: - `Zend\Stratigility\Middleware\CallableMiddlewareDecorator`, for decorating middleware that follows the PSR-15 signature. - `Zend\Stratigility\Middleware\DoublePassMiddlewareDecorator`, for decorating callable middleware that follows the double pass middleware signature. - `Zend\Stratigility\Middleware\PathMiddlewareDecorator`, for providing path-segregated middleware. This required also extracting the class `Zend\Stratigility\Middleware\PathRequestHandlerDecorator`, which is used internally in order to decorate the request handler passed to middleware it decorates. All classes and their were updated to adapt to the http-middleware-compatibility shims.
I have time in the weekend |
@weierophinney @snapshotpl This is awesome! I am looking forward to 3.0. 😄 May I humbly suggest naming this something other than $pipeline->pipe(new OnlyForPath('/mypath', $middleware)); This might go hand-in-hand with #136. |
The point in the name is to indicate it cannot be used by itself, but instead decorates other middleware. We have other such decorators already as well:
As such, the naming is consistent. In terms of convenience and readability, this patch already includes the function $pipeline->pipe(path('/mypath', $middleware)); (The functions in #136 are inspired by the utility function included in this patch.) |
Isn't that already clear based on the constructor signature?
They could be renamed. 😉 That said, as long as the convenience method is there, I am happy. Just wanted to bring this up to discuss. |
Thanks, @snapshotpl! |
My proposition to refactor pipe method. Split to separate methods but with strict and simple usage - improve readability and predictability.