-
Notifications
You must be signed in to change notification settings - Fork 1
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
Map path prefixes to middleware stacks #7
Conversation
composer.json
Outdated
@@ -20,7 +20,9 @@ | |||
}, | |||
"require-dev": { | |||
"eloquent/phony-phpunit": "^3.0", | |||
"phpunit/phpunit": "^6.0" | |||
"league/container": "^2.0", | |||
"phpunit/phpunit": "^6.0", |
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.
No need to require phpunit
, phony-phpunit
always requires it.
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.
It's been there before. Do you want me to remove it?
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.
Doh, of course it was. No need to change anything, composer.json
changes should just be reverted.
src/Broker.php
Outdated
return $this; | ||
} | ||
|
||
public function resolve($middleware): 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.
I don't really like this being a public method. What are we gaining by having an interface for resolution, as opposed to a shared trait?
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. I was thinking Interface Segregation Principle and "freeing" the RequestHandler
(and now the PathDispatcher
as well) from having to know about containers...
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 don't think that actually helps here. The ContainerInterface
is a more widely used standard and offers ISP without having to define a local interface that (effectively) does the exact same thing.
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.
Fine. So I'll just duplicate the three lines of resolve()
both in the RequestHandler
(where they originally come from) and the PathDispatcher
class?
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.
Define a trait that is used by both classes. DRY.
|
||
class PathBrokerTest extends TestCase | ||
{ | ||
use CanMock; |
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 see no reference to the implementation of this trait.
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 am calling $this->process()
.
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.
Oh you're reusing an existing trait. Been a while since I worked on this. 😉
// Execute | ||
$this->broker->paths([ | ||
'/foo' => new stubs\ReturnsGivenString('something'), | ||
'/bar' => stubs\ReturnsGivenString::class, |
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.
Please use phony for mocking instead of creating stubs.
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.
But I am not mocking anything. (I don't care whether and how the things are called, I just check for the expected results.)
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.
It's not necessary to use Phony call checks, I just don't want stubs created when a mock implementation works equally well without adding more files.
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.
Okay, it's your call. Will change this soon-ish.
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.
Hmm. I find it super painful having to mock a middleware that returns a mocked response with a mocked body. Here is what I came up with... Am I missing something?
/**
* @return \Eloquent\Phony\Mock\Handle\InstanceHandle
*/
private function mockMiddlewareReturning(string $responseBody)
{
$stream = Phony::mock(StreamInterface::class);
$stream->__toString->returns($responseBody);
$response = Phony::mock(ResponseInterface::class);
$response->getBody->returns($stream);
$middleware = Phony::mock(MiddlewareInterface::class);
$middleware->process->returns($response);
return $middleware;
}
This is why I like stubs: they are real implementations, and usually super straight-forward.
If the only downside is having one more file in the test code, is that really a downside? (The stub is small enough to be put into the same file, if you prefer 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.
Always an option to use an anonymous class if the mocking is too much work. Which I see is already being done in some places.
I would also argue that for many of these tests using Phony mocks and call checks would provide better overall testing because the expectations inside the middleware should remain consistent. For instance in PathPrefixingHandler
the expectation is that UriInterface
is going to be manipulated. The test should reflect this, rather than testing the output the 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.
But I don't want to test how the PathDispatcher
does its work, I just wan't to test that it does it. That means two things:
1.) Ensure it dispatches to the correct middleware. I do this by comparing the expected responses, depending on which request I pipe through the broker.
2.) Ensure that it provides a manipulated request object to the middleware. I do this by returning the path (which is the part that should be manipulated) in the middleware response so that I can verify on the "outside" (a.k.a. from the result of the operation).
Mocking and call checks would couple me to a specific implementation, as far as I can tell, and that's what I wanted to avoid. Can you provide an example of how you would mock this without these downsides?
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 don't you want to test how it works? The internals are more important than the output here.
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.
Because (in the tests) I don't care about how it does its job. There are a thousand ways to implement that. What exactly would you like to see mocked?
composer.json
Outdated
@@ -20,7 +20,9 @@ | |||
}, | |||
"require-dev": { | |||
"eloquent/phony-phpunit": "^3.0", | |||
"phpunit/phpunit": "^6.0" | |||
"league/container": "^2.0", |
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?
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 like testing with real implementations (to show real example usage). I can change to use a mock / stub if you want.
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.
So... okay to keep? Not 100% sure how to interpret your 👍
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.
No, remove it please.
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.
Okay.
composer.json
Outdated
@@ -20,7 +20,9 @@ | |||
}, | |||
"require-dev": { | |||
"eloquent/phony-phpunit": "^3.0", | |||
"phpunit/phpunit": "^6.0" | |||
"league/container": "^2.0", |
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.
No, remove it please.
|
||
class PathBrokerTest extends TestCase | ||
{ | ||
use CanMock; |
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.
Oh you're reusing an existing trait. Been a while since I worked on this. 😉
src/Broker.php
Outdated
return $this; | ||
} | ||
|
||
public function resolve($middleware): 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.
I don't think that actually helps here. The ContainerInterface
is a more widely used standard and offers ISP without having to define a local interface that (effectively) does the exact same thing.
src/PathDispatcher.php
Outdated
return $handler->handle($request); | ||
} | ||
|
||
private function unprefixedRequest(Request $request, $prefix): 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.
string $prefix
?
src/PathDispatcher.php
Outdated
); | ||
} | ||
|
||
private function prefixedHandler(Handler $handler, $prefix): Handler |
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.
string $prefix
?
src/PathPrefixingHandler.php
Outdated
/** @var string */ | ||
private $prefix; | ||
|
||
public function __construct(RequestHandlerInterface $wrapped, $prefix) |
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.
string $prefix
?
// Execute | ||
$this->broker->paths([ | ||
'/foo' => new stubs\ReturnsGivenString('something'), | ||
'/bar' => stubs\ReturnsGivenString::class, |
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.
Always an option to use an anonymous class if the mocking is too much work. Which I see is already being done in some places.
I would also argue that for many of these tests using Phony mocks and call checks would provide better overall testing because the expectations inside the middleware should remain consistent. For instance in PathPrefixingHandler
the expectation is that UriInterface
is going to be manipulated. The test should reflect this, rather than testing the output the middleware.
$uri->withPath( | ||
substr($uri->getPath(), strlen($prefix)) | ||
) | ||
); |
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.
@shadowhand How would you test this method works correctly, without passing in a real Request
instance?
@shadowhand I have taken care of the requested changes in the source code. Regarding the tests: I have spent more than an hour trying to test this in a useful way using stubs only, and did not succeed. Right now, we have green (and, IMO, useful) tests. Can I leave this part to you, if it is important to you? |
Hmm, you know what... if you are unsure about this, I can just add the No hard feelings either way. 😉 |
Don't worry, I'll get to this very soon. Just had to finish out my work week. Expect a release with it by Monday. |
No pressure. |
Another idea: Hey @oscarotero, would you be interested in accepting such a middleware (see proposed functionality in issue #6) into your middlewares organization? |
@franzliedke That's a feature that many dispatchers provide (stratigility, middleland and now broker). But if you feel that it can be more useful in a middleware format, I do not feel bad. |
@oscarotero As far as I can tell, Middleland does not strip the path from the request object. Broker does not (yet) have the implementation. That leaves us with Stratigility, which I did not want to use, because I don't (yet) want to drop PHP 7.0 support. Hence, I suggested to put this into a generic, reusable package instead. Should I create my own repo first and then submit it for inclusion into your organization? |
@franzliedke Yes, you're right. Middleland does not strip the path prefix, but it can be done with middlewares/base-path. That brings more flexibility because you can choose to strip the prefix to some paths but not in others. Anyway, If you want to create the package, you can use this template and then move it to the middlewares organization. |
@oscarotero Please have a look at the repository and let me know whether you consider this for inclusion. (And also if there is some other, more official path - or route, eh? - where I should send my, uhm, request.) |
@franzliedke would the proposed middleware replace this PR? |
Yep. (Although it currently lacks one thing in comparison to this PR, which is lazy evaluation of prefixed middlewares, e.g. from the container.) |
@franzliedke Great. I left you some suggestions here: middlewares/base-path-router#1 |
@franzliedke I really appreciate the work you did here. In the interest of having the most widely usable solution, I am going to close this out in favor of the middleware, which is a universal solution. |
As proposed in #6.
Please have a look at the tests - they describe the intended feature set quite well.