-
Notifications
You must be signed in to change notification settings - Fork 11.2k
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
[9.x] Controller middleware without resolving controller #44516
Conversation
@taylorotwell I like this PR better than the one I submitted for these reasons:
I proposed PR #44192 as a middle-ground solution, but that could imply behavior changes on a part of the framework that is very sensible to changes, as we saw back in 5.3 when auth/session data was not available anymore on the constructor and closure middleware were introduced to patch that behavior change. On the other hand -- sometimes we only think about a solutions after we see a different approach -- I think we could have a better opportunity here. Currently the What if, instead of testing for the new Doing this we:
We could move these methods to a trait, and make the base I would actually only move https://github.com/laravel/laravel/ also provides a base Controller class, that in addition of extending Not extending Hope this makes sense. Let me know if something is not clear enough. |
Maybe it is better explained with code: public function controllerMiddleware()
{
if (! $this->isControllerAction()) {
return [];
}
[$controllerClass, $controllerMethod] = [
$this->getControllerClass(),
$this->getControllerMethod(),
];
// first we check if the controller extends base controller if so we
// then keep things as they current are, without breaking changes
if (is_a($controllerClass, Illuminate\Routing\Controller::class, true)) {
return $this->controllerDispatcher()->getMiddleware(
$this->getController(),
$controllerMethod,
);
}
// OPTIONAL: then we check if the controller implements the new interface,
// if so we gather this controller's middleware from its static method
if (is_a($controllerClass, HasMiddleware::class, true)) {
return $this->staticallyProvidedControllerMiddleware(
$controllerClass,
$controllerMethod,
);
}
// else, we don't need to bother as no middleware could be provided
return [];
} |
I agree with @rodrigopedra with one small adjustment to his code snippet: // first we check if the controller implements the new interface,
// if so we gather this controller's middleware from its static method
if (is_a($controllerClass, HasMiddleware::class, true)) {
return $this->staticallyProvidedControllerMiddleware(
$controllerClass,
$controllerMethod,
);
}
// then we check if the controller extends base controller
// if so we keep things as they currently are, without breaking changes
if (is_a($controllerClass, Illuminate\Routing\Controller::class, true)) {
return $this->controllerDispatcher()->getMiddleware(
$this->getController(),
$controllerMethod,
);
}
// else, we don't need to bother as no middleware could be provided
return []; This way even controllers that extend the base controller can get marked with that new interface which enables them to use the new syntax as it'd have priority over the non static The benefit of doing these two checks (compared to the current state of this PR) is that I wouldn't have to change every single controller in my projects, having them implement |
Some other names for
|
I just can't figure out how this change allow us to have Edit: reading through @rodrigopedra comment, I can totally see how it could indeed be better to tie Controller middleware with |
@deleugpn can you explain your comment - this still works fine for me? |
@X-Coder264 I have made the change you suggested. 👍 |
Maybe I'm misunderstanding something. But my problem was never about the controller middleware. I don't have controller middleware. I attached middleware to my router endpoints and it was causing a problem because the controller required construction before the router middleware triggered. So I'm not sure whether this solves my problem. |
@christhomas The reason why your controller was constructed before the middleware ran was because So yes, this does solve your problem. If your controller does not extend the |
Yeah, I've just checked out the branch and done a simple test of using a middleware to change a config value and then inject into the constructor the dependency. So I can see that it does work. But if I implement constructor middleware, like was posted as a potential problem, doesn't that mean everybody using constructor middleware now has to update their code to support this new way of working? Or does it just add another possible way to set middleware which doesn't upset the existing controller middleware method that people used in the past? |
That's already explained in the PR description:
|
Sorry, it's late, I missed that. Yeah ok, so this is going to be the way people should do it in the future, but the old way continues to work but it's just undocumented now. I guess to be deprecated in the far future. I think it's actually a smart solution. It's smaller and more compact. Thanks a lot guys! |
Am I being dumb here? I can't see how it's executing my middleware before constructing the controller, it gathers the router using middleware(), then the controllerMiddleware(), but I don't see how it's triggering my middleware before constructing the controller, but it does actually do that. What am I missing here? |
https://github.com/laravel/framework/blob/v9.34.0/src/Illuminate/Routing/Router.php#L714-L727 It gathers the middleware here and it runs them through the pipeline which executes them and when that's done it runs |
I'm curious if we should even introduce the Another option is to introduce the |
EDIT after some thought I think the second option is better. It keeps an option for those who -- like me -- prefer to declare middleware on their controllers, to move past declaring them on the controller's constructor.
|
sorry @taylorotwell for some reason I don't get email notification about metions anymore and I haven't been able to fix that yet. My comment was exactly like yours here:
I think not adding the interface and using the base class as a way to identify whether to offer DI on the constructor of the controller would have been better. As for your screenshot with |
I know this issue is a couple of years old now, but I wanted to share my experience as another data point around this conversation. Right now I'm working on migrating a fairly large Lumen app to Laravel 5.7, and then on up the versions. I was first surprised by this behavior before beginning the migration, when I realized that in Lumen 5.7, global and route middleware are run before the controller is instantiated, while any middleware defined at construction time via Anyway, it's just funny that there's a strong argument around not changing this since it's been this way for over a decade... except in Lumen. 🤪 My solution at the moment is to backport this change and #44590 by extending a few core routing classes and rebinding them, until we can make our way from 5.7 to 9. |
This allows controller middleware to be resolved statically without instantiating the controller. Closes #44177
Alternative to #44192
AFAIK, this does not affect the order middleware are run and middleware priority is preserved. Therefore, there are NO breaking changes and I have submitted this to 9.x. This new behavior would be documented as the only way to define middleware on controllers; however, the prior way to define middleware would not be removed.
A new interface
HasMiddleware
and value objectMiddleware
have been introduced:However, I REALLY do not like method name
resolveMiddleware
. I can't make itmiddleware
because the base controller class already has that method defined non-statically. Does anyone have any ideas for better naming here?@rodrigopedra @X-Coder264 @christhomas any thoughts on this?