URL::defaults() + route group prefix causes positional parameter misalignment in route() calls
Laravel Version
12.22.1
PHP Version
8.2.3
Database Driver & Version
No response
Description
When a route has a parameter in the group prefix (e.g. {workspace:uuid?}) and its value is set via URL::defaults() in middleware, calling route() with positional parameters for other route parameters results in Laravel incorrectly assigning values.
The prefix parameter gets overwritten by the first positional parameter, and the intended parameter is left missing — often causing errors or producing URLs with an empty ?workspace= query string.
Note: this works without any issues in v12.3.0
Steps To Reproduce
- Define routes:
Route::group(['prefix' => 'app/{workspace:uuid?}', 'as' => 'app.', 'middleware' => [EnsureCurrentWorkspace::class]], function () {
Route::get('', function (Workspace $workspace) {
return view('dashboard');
})->name('dashboard');
Route::group(['prefix' => 'customers', 'as' => 'customers.', 'controller' => CustomerController::class], function () {
Route::get('', 'index')->name('index');
Route::get('create', 'create')->name('create');
Route::post('create', 'store')->name('store');
Route::get('modify/{customer}', 'modify')->name('modify');
Route::post('modify/{customer}', 'update')->name('update');
Route::delete('delete/{customer}', 'destroy')->name('delete');
});
});
- In Middleware:
class EnsureCurrentWorkspace
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && $request->route()->parameter('workspace') != null) {
auth()->user()->workspaces->where('id', $request->route()->parameter('workspace')->id)->firstOrFail();
URL::defaults(['workspace' => $request->route()->parameter('workspace')->uuid]);
} else if ($workspace = Workspace::where('uuid', $request->route()->parameter('workspace'))->first()) {
URL::defaults(['workspace' => $workspace->uuid]);
} else
if ($request->path() != 'workspaces')
return Redirect::to('workspaces');
return $next($request);
}
}
- Route Call:
route('app.customers.modify',$customer->id)
Expected Behavior Should generate: /app/7b1c3cbe-675e-43cc-aed6-55bc36e7f79a/customers/123
Actual Behavior
Illuminate\Routing\Exceptions\UrlGenerationException
Missing required parameter for [Route: app.customers.modify] [URI: app/{workspace?}/customers/modify/{customer}] [Missing parameter: customer].
View:
<a href="{{ route('app.customers.modify',$customer->id) }}" class="btn btn-sm btn-primary">Edit</a>
When writing the route like:
<a href="{{ route('app.customers.modify',['customer'=>$customer->id]) }}" class="btn btn-sm btn-primary">Edit</a>
It works without any issues but have to rewrite everywhere and its too hectic for bigger projects
@ibilalkhilji You may need to set the priority in bootstrap file
updated
->withMiddleware(function (Middleware $middleware) {
$middleware->prependToPriorityList(
before: \Illuminate\Routing\Middleware\SubstituteBindings::class,
prepend: \App\Http\Middleware\SetDefaultLocaleForUrls::class,
);
})
So, your implicit binding will be called first.
reference: https://laravel.com/docs/12.x/urls#url-defaults-middleware-priority
Thanks @ghabriel25 for the update I tried both method but still having the same problem there is no effect of using prependToPriorityList where as appendToPriorityList does not bind the model in the URL and returns this http://billmaker.test/app//customers
@ibilalkhilji My apologize, I have update my answer. Is it still not resolve your issue?
Thanks @ghabriel25 for you help I have fixed the issue, the problem is in specifying route model binding key instead of doing this (specifying key here)
Route::group(['prefix' => 'app/{workspace:uuid?}', 'as' => 'app.', 'middleware' => [EnsureCurrentWorkspace::class]], function () {
replace it with
Route::group(['prefix' => 'app/{workspace?}', 'as' => 'app.', 'middleware' => [EnsureCurrentWorkspace::class]], function () {
and inside model
class Workspace extends Model
{
use SoftDeletes;
public function getRouteKeyName(): string
{
return 'uuid';
}
}
then it work as expected with no workspace parameter
http://billmaker.test/app/7b1c3cbe-675e-43cc-aed6-55bc36e7f79a/customers
Hey there, thanks for reporting this issue.
We'll need more info and/or code to debug this further. Can you please create a repository with the command below, commit the code that reproduces the issue as one separate commit on the main/master branch and share the repository here?
Please make sure that you have the latest version of the Laravel installer in order to run this command. Please also make sure you have both Git & the GitHub CLI tool properly set up.
laravel new bug-report --github="--public"
Do not amend and create a separate commit with your custom changes. After you've posted the repository, we'll try to reproduce the issue.
Thanks!
Thanks @crynobone I have posted the repository here https://github.com/ibilalkhilji/bug-report.git with explanation
Hi @crynobone I've posted the public repo of my code is it resolved?
@crynobone After upgrading from 11 to 12, I had exactly the same problems as @ibilalkhilji and was able to solve them in the same way.
Current problem:
Route::prefix(“/workspace/{workspace:slug}”)->name(“workspace.”)->group(function () {...});
Does not work properly with URL::defaults([“workspace” => $workspace->slug]); and you currently have to solve it using getRouteKeyName().
@taylorotwell @crynobone Sorry for mentioning here again.
Just to clarify, the original example caused some misunderstanding. The first parameter in my initial example wasn’t meant to be optional. The issue actually occurs whether the first parameter is required or optional.
I’ve now updated the example in the description to reflect the real scenario with a required parameter. The fix is still valid and can be merged. Mentioning you here so you’re aware and can reconsider with the updated example.
PR: https://github.com/laravel/framework/pull/57382
@alaminfirdows create the PR again with proper description, example and test case, if you think the fix is valid