mboynes's avatar
Level 10

Deriving multiple route params from one argument

I have a multi-tenant application where I'm including the organization in the URL, e.g. /{organization}/post/{post}. In the case of every URL, the organization can be derived from the "child" object (post, in this case), so I have a lot of repetition in my code, e.g. route('post.show', [$post->organization_id, $post]). Ideally, I would love to be able to call route('post.show', $post) and have some code which universally replaces the {organization} param, but I've tried and failed to make that work.

I have other ideas to work around this, but before I go down one of those roads, I'm curious if anyone else knows a way to get that to work? I'm set on that idea because I'd like to extract this multi-tenancy functionality into a public package, and it would be great if others didn't have to change their routes or route generators after installing the package.

TL;DR: is it possible to get route('post.show', $post) to work for the url /{organization}/post/{post}, where {organization} can be derived from $post?

Thanks!

0 likes
1 reply
LaryAI's avatar
Level 58

To achieve the desired functionality where you can call route('post.show', $post) and have the {organization} parameter automatically derived from the $post object, you can use Laravel's route model binding and custom URL generation logic. Here's a step-by-step solution:

  1. Custom Route Key Binding:

    First, ensure that your Post model has a relationship with the Organization model. Then, you can define a custom route key binding for the Post model to automatically include the organization in the URL.

    In your RouteServiceProvider, you can override the boot method to define a custom binding:

    use Illuminate\Support\Facades\Route;
    use App\Models\Post;
    
    public function boot()
    {
        parent::boot();
    
        Route::bind('post', function ($value) {
            return Post::where('id', $value)->with('organization')->firstOrFail();
        });
    }
    
  2. Custom URL Generation Logic:

    You can extend the UrlGenerator to customize how URLs are generated for your routes. This involves creating a custom service provider to override the default URL generator.

    First, create a custom URL generator:

    namespace App\Routing;
    
    use Illuminate\Routing\UrlGenerator as BaseUrlGenerator;
    
    class UrlGenerator extends BaseUrlGenerator
    {
        public function route($name, $parameters = [], $absolute = true)
        {
            if (isset($parameters[0]) && $parameters[0] instanceof \App\Models\Post) {
                $post = $parameters[0];
                $parameters = [$post->organization_id, $post->id];
            }
    
            return parent::route($name, $parameters, $absolute);
        }
    }
    
  3. Register the Custom URL Generator:

    In your AppServiceProvider or a custom service provider, bind your custom URL generator:

    namespace App\Providers;
    
    use Illuminate\Support\ServiceProvider;
    use App\Routing\UrlGenerator;
    
    class AppServiceProvider extends ServiceProvider
    {
        public function register()
        {
            $this->app->singleton('url', function ($app) {
                return new UrlGenerator($app['router']->getRoutes(), $app->rebinding(
                    'request', $this->requestRebinder()
                ));
            });
        }
    
        protected function requestRebinder()
        {
            return function ($app, $request) {
                $app['url']->setRequest($request);
            };
        }
    }
    
  4. Update Your Routes:

    Ensure your routes are defined to accept both {organization} and {post} parameters:

    Route::get('/{organization}/post/{post}', [PostController::class, 'show'])->name('post.show');
    

With these changes, you should be able to call route('post.show', $post) and have the {organization} parameter automatically derived from the $post object. This approach keeps your route definitions clean and allows for easy integration into a package.

1 like

Please or to participate in this conversation.