Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

kaarch's avatar

Route Model Binding to the Request (not Controller parameters)

I haven't been able to find a suitable pattern to clean up my controller classes, and perhaps it's because it's an anti-pattern to begin with. Consider a multi-tenancy application, where the sub-domain relates to some model (say, "Customer"). Route model binding works fine, but feels bloated and possibly inefficient for every controller/method to need to reference it:

public function index(Request $request, Customer $customer, ...)

Additionally, I may want to be able to reference the bound model outside of a controller, but if it's not been loaded via a controller parameter, request()->customer is the slug, not the model.

Is there a preferred way, perhaps a middleware or extending the Request class, to make the Customer model more accessible throughout the application, instead of being so closely tied to calling a controller method?

0 likes
6 replies
martinbean's avatar

@kaarch Why? It’s a route parameter. And with the customer model being injected, means you can scope queries to that particular customer:

Route::domain('{customer:subdomain}.example.com')->group(function () {
    // Customer routes...
    Route::get('articles', [ArticleController::class, 'index']);
    Route::get('articles/{article:slug}', [ArticleController::class, 'show']);
namespace App\Http\Controllers\Customer;

class ArticleController extends Controller
{
    public function __construct(Customer $customer)
    {
        // Can now access articles scoped to customer without awful global scopes...
        $articles = $customer->articles()->published()->paginate();

        return view('customer::articles.index')->with([
            'customer' => $customer,
            'articles' => $articles,
        ]);
    }

    public function show(Customer $customer, Article $article)
    {
        return view('customer::articles.show')->with([
            'customer' => $customer,
            'article' => $article,
        ]);
    }
}

You also get scoped route-model bindings for free, meaning if article 1 belongs to customer 1, you can’t access it via customer 2’s subdomain, i.e.

GET '/customer-1.example.com/articles/article-1' // 200 OK
GET '/customer-2.example.com/articles/article-1' // 404 Not Found

I do this in my own CMS (but use the whole domain as a parameter rather than a subdomain). In a first iteration, I tried to global scopes approach and it just introduced far more problems than it solved, so just switched to scoping queries on the customer model as above.

I also don’t see why you would be trying to access route parameters or other request data outside of a controller. That definitely smells like an anti-pattern.

kaarch's avatar

It’s not that I DONT want it in the controller, it’s more that the Customer object often applies to more than the context of that specific route. I can and have been making it work, it just feels overly bloated. In practice I have a route group with 40-50 routes (probably more in the future), and every single controller method in that group needing to declare the $request AND the $customer parameter doesn’t feel very DRY.

I need to take a look at the other suggestion of putting it in the session. I felt like attaching it to the request made more sense, but a session variable would certainly reduce the repeated code.

webrobert's avatar

@kaarch I like how simple it is... listens for the login event then adds the tenant_id to the session. Then each model that it applies to uses a trait to grab the tenant_id from the session.

martinbean's avatar

@kaarch Again, the customer is part of the request and route definition. Why is it then also being passed to controller actions—like any other route parameter—such a bad thing? This isn’t a problem to be solved by “DRY”—there’s nothing being duplicated here, there’s nothing to be abstracted away; you just happen to have lots of controllers with a common parameter.

I have literally hundreds of controller actions in my CMS for handling both front- and back-end routes. Pushing the customer resolving logic to some magic, single place is just going to make my code harder to reason about; not easier.

kaarch's avatar

@martinbean Sure, that’s why in the original question I was asking if a pattern exists or if what I’m asking is an anti-pattern. I guess conceptually I’d ask why then request()->user() is a built in auth/framework feature, but doing something similar for a multi-tenant type solution where I could similarly call request()->customer() would be an odd thing to want?

Please or to participate in this conversation.