-
Notifications
You must be signed in to change notification settings - Fork 8
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
Allow arbitrary injection into middleware and param converters #16
Conversation
* | ||
* @author Felix Becker <f.becker@outlook.com> | ||
*/ | ||
class MiddlewareListener extends \Silex\EventListener\MiddlewareListener |
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'm not sure I understand when this class is used by Silex? Since App::before()
and App::after()
are overridden, why do we need to override this too?
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 are two types of middleware, Application level middleware via $app->before()
and route / controller collection level middleware like $app->get('/', ...)->before(...)
. Route level middleware is handled throught MiddlewareListener
.
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.
Ah right thanks!
I will squash once you give your OK to merge |
use Symfony\Component\HttpFoundation\Response; | ||
|
||
/** | ||
* Class that takes the responsibility of resolving and calling a before middleware with injection |
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.
"calling a before an after middleware"
As I don't have much time right now I just took a quick look. |
@jdreesen I addressed your comments and squashed.
I know, I don't like it either, but every case (after middleware, before middleware, param converter, controller resolver) is just a bit different so they cannot share the exact same implementation. If you break it down it's really nothing but
I'm actually quite happy that there is no duplication for route / application level middleware and that finish and after middleware can both use |
Sorry for the delay, I've taken some time to think about it. In order to preserve BC we want to inject the request, response and app by:
The application is not a problem because it's a service, but the request & response are not so using the Invoker, even with a custom What we could do is create a new type of $parameterResolver = new ResolverChain([
new AssociativeArrayResolver, // inject request/response by name
new TypeHintResolver, // inject request/response by type-hint
new TypeHintContainerResolver, // dependency injection
]);
...
$invoker->call($middleware, [
'request' => $request, // by name, used by AssociativeArrayResolver
'response' => $response, // by name, used by AssociativeArrayResolver
'Symfony\...\Request' => $request, // by type-hint, used by TypeHintResolver
'Symfony\...\Response' => $request, // by type-hint, used by TypeHintResolver
]); The What do you think? |
that doesn't make much sense to me - with default Silex there is not injection by parameter name, only by parameter position. A middleware is always called with
this is a very good solution, but I want to point out here that I strictly followed the existing implementation in What I don't get though:
Where do you pass the paramResolver here? |
The parameter resolvers are passed when constructing the Invoker: $parameterResolver = new ResolverChain([
...
]);
$invoker = new Invoker\Invoker($parameterResolver); And you are right about injection of request/response by name, that's not necessary here so let's keep it as simple as we can (and it wouldn't be consistent with injection in controllers anyway). Then you would only need something like this: $parameterResolver = new ResolverChain([
new TypeHintResolver, // inject request/response by type-hint
new TypeHintContainerResolver, // dependency injection
new NumericArrayResolver, // fallback to injecting "request, response, application"
]);
$invoker = new Invoker\Invoker($parameterResolver);
$invoker->call($middleware, [
// parameters indexed by position for the NumericArrayResolver
$request, $response, $application,
// parameters indexed by the type-hint for the TypeHintResolver
'Symfony\...\Request' => $request,
'Symfony\...\Response' => $request,
]); Does that make sense?
I agree and take your point. But with this PR this implementation gets duplicated multiple times and the code gets much more complex. By merging this I'll also be committing to supporting this code, I'd rather go with something as simple as possible ;) |
@mnapoli Your proposed solution makes a lot of sense. Do you want me to do a PR to Invoker? |
@felixfbecker if you have time for that that would be awesome! |
I changed the implementation to use the
When the change is published on Invoker I will bump the dependency version and squash. Just wanted to share the changes now so you can review. |
Looks good? |
new TypeHintContainerResolver($container), | ||
new NumericArrayResolver, | ||
])); | ||
} |
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'm not convinced by the usefulness of this class. I think you referred to that when you said:
It extends Invoker for type safety so MiddlewareListener etc. can depend on this specific Invoker with this specific ResolverChain.
But I don't see why a MiddlewareListener should explicitly require this implementation. To me it would be better than middleware listeners or parameter converters are implementations that rely on the interface of "Invoker", and then the application passes an instance of Invoker
configured as we want.
I.e. I don't see the point to couple things more than necessary.
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 then the application could theoretically pass an Invoker
instance that uses a completely different ResolverChain
, which would break the MiddlewareListener
. Only this way can MiddlewareListener
declare that it needs an Invoker
that is suited for calling Silex callbacks, through the parameter names, type hints, the container and through parameter position.
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 not attached to it though. If you feel strongly about this, let me know and I remove the class :)
Looking very good! Much better now! It's really too bad we have to copy so much code from Silex :/ but that's not your fault of course. |
Do you want me to squash so you can merge? Or do you have any other idea for the dispatcher? |
Thanks for your answers. I'm good for merging, 👍 for squashing |
👍 |
It should be good to go, any ETA? |
A few minutes :) |
Allow arbitrary injection into middleware and param converters
1.5.0 is tagged and released! The documentation was also updated in PHP-DI's docs: http://php-di.org/doc/frameworks/silex.html Thank you for your patience and implementing all that ;) |
This is an implementation for #15
I would love to get this merged as quickly as possible to depend on it in my projects.
Feature
It is now possible to inject whatever is registered in the container into route param converters and middleware via type hints. Also, as long as the arguments are type-hinted, argument position of request / response / app doesn't matter anymore. If there are no type-hints, it falls back to Silex' default argument order.
Implementation
To accomplish this,
ConverterListener
andMiddlewareListener
are overwritten and inApplication
the default dispatcher is overwritten to use the custom classes. For application middleware,before
/after
/finish
are overwritten aswell.For DRY reasons the injection logic is done in separate classes (
BeforeMiddlewareCaller
,AfterMiddlewareCaller
,ConverterCaller
). This allows the application middleware and route middleware to share the same logic and also for all event listeners to share the same instance of the caller andParameterResolver
objects.Tests
ConverterTest
andMiddlewareTest
test that both arbitrary injection is possible and the old behaviour still works.Docs
I altered the README to highlight that injection works not only for controllers but also for converters and middleware and added two examples.
BC Breaks
None
Any thoughts / objections?