-
Notifications
You must be signed in to change notification settings - Fork 11.4k
[12.x] fix method dependencies order #56062
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
base: 12.x
Are you sure you want to change the base?
Conversation
$this->assertEquals(['one' => 'one', 'two' => 'two'], $_SERVER['__test.controller_callAction_parameters']); | ||
$this->assertSame(['two' => 'two', 'one' => 'one'], $_SERVER['__test.controller_callAction_parameters']); |
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 replaced assertEquals
by assertSame
because assertEquals
is NOT checking the order of array items.
While assertSame
is checking that both values and order are identical.
When we call the controller action, the order of param is important so we must check it.
In that example, the order of params must be reversed if they are reversed in the controller action.
fyi: Another way to solve this would be using contextual attributes |
Creating a new ContextualAttribute is not really convenient. |
@stancl what do you think of this? |
I didn't mean a new one. There already is |
@taylorotwell From the perspective of my work on the route generation logic, I think route generation should always only be concerned with route strings, not the route actions they point to. Meaning, if you have a route like this: Route::get('companies/{company}/users/{user}/details', function (User $user, Company $company) {
dd($user, $company);
})->name('foo'); You'd generate routes as:
Maybe you could add logic that'd reorder Types aren't always sufficient information. If you have a route like It's difficult to come up with realistic edge cases on the spot, but with how the route generation logic can now sort of fill parameters "from the middle" if too few are passed (e.g. there are some with defaults at the start, some optional ones at the end, so it just fills the required ones in the middle) the behavior would also drastically change if made type-aware, I'm not sure how realistic that concern is but it seems undesirable. For instance if you had:
As I'm writing this I'm also realizing mere reordering would not work if you wanted to make route generation type-aware. Point 2) would actually require adding keys based on types because in cases of too few arguments, the logic treats parameters with different precedence: required > optional > with defaults. In short, from quickly thinking of some cases I think this might be painful to handle in route generation logic too. Maybe it would turn out to be not as bad as I'm imagining, but you'd still be left with the potential scenario where a seemingly innocuous refactor, that changes whether some variables are model instances, could change how routes are generated. From that my conclusion is that as far as route generation is concerned, it should only ever care about parameter order or names, not types. Cases like If route model binding didn't require parameters to match their model names, and you could have Considering the feature overall, this change could make sense and would perhaps feel more consistent with dependency injection, my concerns would just be:
So the main benefit I see would be making this feel more like dependency injection, but I'm worried about some inconsistencies this would introduce and potential edge cases, given that routing has always expected developers to be strict about parameter order. Personally I feel like being able to rely on developers having "parameter order discipline" simplifies things, so I'd probably favor just updating the docs. Right now routing feels like a "function call" with the dependence on parameter order, type-aware magic autowiring would feel more Laravely, I'm just not sure if it's worth the potential complications. |
Hi @stancl Please let me provide additional context: Currently, if an object with the required typehint is found in route parameters, it's skipped and we assume it's at the correct position when the name does not match the route param (same as before), resulting in an exception if not.
I thought about it too, but i did not implemented yet. I can give it a try if you want?
That case is not possible:
That's why i decided to go with reordering by param names only for now.
Not strictly, even before my change it was possible to inject a service in between 2 route parameters (eg: injecting the framework/src/Illuminate/Routing/ResolvesRouteDependencies.php Lines 42 to 66 in 46b66aa
Maybe you are referring to Laravel router? |
Not sure what you mean by this, positions seem to always be the only thing the router looks at regardless of types being present or not.
Interesting. I'd expect that to work since you do see routes that accept e.g. multiple users in some APIs (though probably not Laravel APIs in that case?). Above I mentioned:
If you could specify parameters something like
I know, but if you ignore the injected dependency parameters, the order of parameters coming from the routing logic always matches what's used in the route string as well as route generation.
Yes, I don't think most Laravel developers are exposed to Symfony internals much. Anyway - if it's impossible to use two parameters of the same (model) type, then I don't have as many concerns as expressed in the original comment, but I'm still not sure how I'd feel about the inconsistency between this logic and route generation (latter requiring the correct order). Maybe you can try making the route generation changes here to see how complex (or not!) it'd end up being 👍 |
Just to clarify, I think we have some confusion here. In your example, I am not sure if by We cannot reuse the same segment name in the route:
But yes, it's possible to have two parameters of the same (model) type if the segment names are different:
class UsersController extends Controller
{
// same order
public function show(User $user_from, User $user_to)
{
// This works
}
// different param name
public function show(User $a, User $b)
{
// This also works, not the same name, so it fallback on declared order of route params
// $a contains {user_from}
// $b contains {user_to}
}
// reversed:
public function show(User $user_to, User $user_from)
{
// Without my PR: $user_to contains {user_from}
// With my PR: $user_to contains {user_to}
}
} |
Definitely sounds interesting 🤔
@Seb33300 Isn't that working for you? |
@stancl I am not sure if you really understood the changes I did in that PR. In my PR, I modified the That method has the
On top of that, I added the ability to reorder the params based on their name to match the order of controller action just before calling it |
@shaedrich from what I understand about contextual attribute, is that they are not resolved by the router. So it is not possible to use them with scope binding. |
Hm interesting, looks like the Route::get('/test1/{foo}', function (User $foo) {
// true
dd($foo->exists);
}); So yeah you can have @Seb33300 Pretty much all my feedback has been about whether reordering is desired or not. The rest is just considering edge cases (some of which I see aren't really an issue). My main point of feedback remains that as Laravel works now, all of:
uses the same order. Without updating route generation this would add some inconsistency, unless it's specifically decided that route generation should only look at route strings and follow the parameter order there. Given how this works: Route::get('/test3/{foo}/{bar}', function (User $foo, User $bar) {
// true, true
dd($foo->exists, $bar->exists);
}); (Route model binding works by matching the route string param names to the action param names, then reading the typehint) I understand why you think the order of parameters should not matter. I'm just not sure if it'd be a good change. In the example you gave originally, you essentially reused the same route action for two different routes. Can you maybe comment on how the |
Yes
No, there is no check at all on the parameter name. This works Route::get('/test1/{a}', function (User $b) {
// true
dd($b->exists);
}); And my PR tries to add that the check on the names to find a match and reorder them if not in the correct order. I need to investigate And about wayfinder. I never used it, so I have no idea about how it works. If it's based on routes definition, this should not have any side effect on it. |
Does it? Route::get('/test1/{foo}', function (User $foo) {
// true
dd($foo->exists);
});
Route::get('/test2/{foo}', function (User $different_name) {
// false
dd($different_name->exists);
}); If I change
https://laravel.com/docs/11.x/routing#implicit-binding Regarding wayfinder, I think it's just going to be the same story as |
Right my bad. Using a different name works only for non object params (string, int...) I will try to take time tomorrow to test the |
In other words: They work fine as long as you don't need scope binding. This in turn means, your PR will primarily benefit the case, the attribute doesn't cover. Gotcha! |
I just did the following test on my local environment with my PR and it looks good: Route::get('/from/{from}/to/{to}', ...)
class UsersController extends Controller
{
public function show(User $from, User $to)
action([UsersController::class, 'show'], ['from' => 1, 'to' => 2]); # /from/1/to/2
action([UsersController::class, 'show'], ['to' => 2, 'from' => 1]); # /from/1/to/2
public function show(User $to, User $from)
action([UsersController::class, 'show'], ['from' => 1, 'to' => 2]); # /from/1/to/2
action([UsersController::class, 'show'], ['to' => 2, 'from' => 1]); # /from/1/to/2
public function show(User $to, Request $request, User $from)
action([UsersController::class, 'show'], ['from' => 1, 'to' => 2]); # /from/1/to/2
action([UsersController::class, 'show'], ['to' => 2, 'from' => 1]); # /from/1/to/2
} |
Hello,
Fixes #56047
In the documentation, we can read:
What I understand:
As long as our controller action variable names matches with the routes segment names, the order of variables should not matter.
Unfortunately, this is not how it currently works, if our controller actions does not declare variables in the same order as the route, this simple example will fail:
With the following routes :
The exception shows that the params are not passed in the correct order, even if I am using the same param names.
With my PR, that issue is fixed by trying to resolve dependencies by param name first.