-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Ensure cached and uncached routes share same precedence when resolving actions and names #56920
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
Ensure cached and uncached routes share same precedence when resolving actions and names #56920
Conversation
Thanks for submitting a PR! Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface. Pull requests that are abandoned in draft may be closed due to inactivity. |
9d8d480
to
c7b6235
Compare
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Routing\Fixtures; | ||
|
||
use Illuminate\Foundation\Application; | ||
|
||
if (! class_exists(AppCache::class)) { | ||
class AppCache | ||
{ | ||
public static $app; | ||
} | ||
} | ||
|
||
if (isset($refresh)) { | ||
return AppCache::$app = Application::configure(basePath: __DIR__)->create(); | ||
} else { | ||
return AppCache::$app ??= Application::configure(basePath: __DIR__)->create(); | ||
} |
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 is a little weird; I know.
When routes are cached, a new application instance is resolved.
This file allows us to capture the app instance created to add routes to it.
…g actions and names (laravel#56920) * Ensure cached and uncached routes share same precedence * formatting * Formatting * formatting * Fix tests
Due to a change in the Laravel Framework with regards to route names and precedence (presumably laravel/framework#56920), route precedence for the `users.edit` route changed and one of our redirects and tests that relies on the old implicit route order fails. ``` return redirect()->route('users.edit', ...) ``` used to redirect to `users.edit` of our `UserManagementController` but now redirects to our `Api\UserController`, i.e. the `Route::resource` definition from `api.php` seems to be first now. ``` php artisan route:list -v | grep -A2 users.edit GET|HEAD admin/users/{user}/edit .................................. users.edit › UserManagementController@edit ⇂ web ⇂ App\Http\Middleware\Authenticate:sanctum -- GET|HEAD api/users/{user}/edit .......................................... users.edit › Api\UserController@edit ⇂ api ⇂ App\Http\Middleware\Authenticate:sanctum ``` As a fix, redirect to the controller action instead and don't rely on the route name here: https://laravel.com/docs/12.x/redirects#redirecting-controller-actions I wonder if this is the most idiomatic approach though and what others with non-API and API routes with identical names do.
Due to a change in the Laravel Framework with regards to route names and precedence (presumably laravel/framework#56920), route precedence for the `users.edit` route changed and one of our redirects and tests that relies on the old implicit route order fails. ``` return redirect()->route('users.edit', ...) ``` used to redirect to `users.edit` of our `UserManagementController` but now redirects to our `Api\UserController`, i.e. the `Route::resource` definition from `api.php` seems to be first now. ``` php artisan route:list -v | grep -A2 users.edit GET|HEAD admin/users/{user}/edit .................................. users.edit › UserManagementController@edit ⇂ web ⇂ App\Http\Middleware\Authenticate:sanctum -- GET|HEAD api/users/{user}/edit .......................................... users.edit › Api\UserController@edit ⇂ api ⇂ App\Http\Middleware\Authenticate:sanctum ``` As a fix, redirect to the controller action instead and don't rely on the route name here: https://laravel.com/docs/12.x/redirects#redirecting-controller-actions I wonder if this is the most idiomatic approach though and what others with identical names for non-API and API routes do.
Due to a change in the Laravel Framework with regards to route names and precedence (presumably laravel/framework#56920), route precedence for the `users.edit` route changed and one of our redirects and tests that relies on the old implicit route order fails. ``` return redirect()->route('users.edit', ...) ``` used to redirect to `users.edit` of our `UserManagementController` but now redirects to our `Api\UserController`, i.e. the `Route::resource` definition from `api.php` seems to be first now. ``` $ php artisan route:list -v | grep -A2 users.edit GET|HEAD admin/users/{user}/edit .................................. users.edit › UserManagementController@edit ⇂ web ⇂ App\Http\Middleware\Authenticate:sanctum -- GET|HEAD api/users/{user}/edit .......................................... users.edit › Api\UserController@edit ⇂ api ⇂ App\Http\Middleware\Authenticate:sanctum ``` As a fix, redirect to the controller action instead and don't rely on the route name here: https://laravel.com/docs/12.x/redirects#redirecting-controller-actions As far as I can tell from the output below, the three fixed locations are the only places where we have conflicting names in redirects. ``` $ php artisan route:list --json --sort name | jq -r .[].name | uniq -c | sort [...] $ git grep redirect app/Http/Controllers/ [...] ``` I wonder if this is the most idiomatic approach though and what others with identical names for non-API and API routes do.
This PR addresses a bug where routes precedence is different than expected when matching actions and names compared to matching URLs.
Also, there is an inconsistency between action matching behaviour with cached and un-cached routes. This is fun because we didn't know about the issue until it was deployed (not in production) where the routes were cached.
Given the following two routes:
Although both of the routes can respond to the following URL, the first route defined takes precedence:
Precedence is basically: first route to match handles the request.
When it comes to generating URLs for routes, we have some inconsistencies with this behaviour that is inconsistent between cached and uncached routes.
Notice when the routes were not cached, the second route was used and a query string is present. When the routes are cached, the first route is used and the second parameter becomes a path segment.
When the routes are cached, I feel the expected behaviour occurs. The
CompiledRouteCollection
will match against the first route with the same action.framework/src/Illuminate/Routing/CompiledRouteCollection.php
Lines 210 to 216 in 5d773d2
I feel that the un-compiled
RouteCollection
class exhibits the wrong behaviour, and will match on the last route with the same action. The cause of this is the way theRouteCollection
maintains a key -> value pair of action => route.framework/src/Illuminate/Routing/RouteCollection.php
Lines 103 to 106 in 5d773d2
This is called whenever we add a new route:
We also override everything again when the action lookup is refreshed:
framework/src/Illuminate/Routing/RouteCollection.php
Lines 133 to 142 in 5d773d2
The same precedence issue exists for named routes, where the last one is always matched. This doesn't have the difference between un-cached and cached routes, because you cannot serialize a routes if you have a route name conflict.
That being said, I still feel the precedence for un-cached routes should be addressed.