framework icon indicating copy to clipboard operation
framework copied to clipboard

URL::defaults() + route group prefix causes positional parameter misalignment in route() calls

Open ibilalkhilji opened this issue 8 months ago • 10 comments

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

  1. 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');
        });
});
  1. 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);
    }
}
  1. 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 avatar Aug 10 '25 10:08 ibilalkhilji

@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

ghabriel25 avatar Aug 10 '25 12:08 ghabriel25

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 avatar Aug 10 '25 12:08 ibilalkhilji

@ibilalkhilji My apologize, I have update my answer. Is it still not resolve your issue?

ghabriel25 avatar Aug 10 '25 12:08 ghabriel25

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

ibilalkhilji avatar Aug 10 '25 12:08 ibilalkhilji

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!

crynobone avatar Aug 11 '25 02:08 crynobone

Thanks @crynobone I have posted the repository here https://github.com/ibilalkhilji/bug-report.git with explanation

ibilalkhilji avatar Aug 11 '25 14:08 ibilalkhilji

Hi @crynobone I've posted the public repo of my code is it resolved?

ibilalkhilji avatar Aug 15 '25 07:08 ibilalkhilji

@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().

BubeDameKoenig avatar Oct 09 '25 03:10 BubeDameKoenig

@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 avatar Oct 16 '25 05:10 alaminfirdows

@alaminfirdows create the PR again with proper description, example and test case, if you think the fix is valid

Sajid-al-islam avatar Oct 16 '25 12:10 Sajid-al-islam