Laravel Multitenancy: Route Model Binding
Tags: laravel, multitenancy, routing, php
Published: 2018-10-28 22:15:55

---
title: Laravel Multitenancy: Route Model Binding
published: true
tags: laravel, multitenancy, routing, php
canonical_url: https://davidllop.com/laravel-multitenancy-route-model-binding
---

Why write code like this

```
public function edit($articleId)
{
    $article = Article::findOrFail($articleId);
 
    return view('articles.edit', compact('article')); 
}

```

When you can simply do

```
public function edit(Article $article)
{
    return view('articles.edit', compact('article'));
}

```

I'm a huge fan of [this Laravel feature](https://laravel.com/docs/5.7/routing#route-model-binding) since I discovered it. You just need to typehint the model in the functiondeclaration and the framework will handle the `findOrFail` for you. That's it.

But it has a problem though, sometimes you need to check for other fields.

Using our previous example:

```
public function edit($articleId)
{
    $article = Article::where('tenant_id', auth()->user()->tenant_id)->findOrFail($articleId);
    
    return view('articles.edit', compact('article'));
}

```

Would look cleaner than

```
public function edit(Article $article)
{
    abort_unless($article->tenant_id === auth()->user()->tenant_id);
    
    return view('articles.edit', compact('article'));
}

```

This is a common scenario. You have an app to edit articles, and you have to prevent your users to edit otherusers's articles. Basic security rule. But you don't like that kind of code you need to read twice to understand what it does at a first glance.

Well, you could use [Global Scopes](https://laravel.com/docs/5.7/eloquent#global-scopes) for sure, but they have a problem: a global scope will be applied to all the queries onthat model, no matter where in the app they occur. So when you need to query all the articles for some statistics or maintenance,you will need to add `withoutGlobalScope` to that query every single time.

The solution my team and I found was to override the default behaviour of Route Model Binding. Of course Laravel offers youa simple solution, you just have to override a method in your model to have it up and running.

This is the default behaviour:

```
/**
 * Retrieve the model for a bound value.
 *
 * @param mixed $value
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value)
{
    return $this->where($this->getRouteKeyName(), $value)->first();
}

```

And this is what we will need to declare in our `Article` model:

```
/**
 * Retrieve the model for a bound value.
 *
 * @param mixed $value
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value)
{
    return $this->where($this->getRouteKeyName(), $value)
        ->where('tenant_id', auth()->user()->tenant_id)
        ->first();
}

```

I know what you're about to say

> But David, it's easy to understand what the code does if we check this in the controller

Yeah sure, I'll not do this for every kind of filtering like reedeming a `PromoCode` with an expiration date. But when youhave a multitenancy app this kind of delegation is appreciated.

I'm with the philosophy of:

> A Controller should receive only Entities which it can work without checking conditionals.

I apply this rule also for `Request` validation using [Form Requests](https://laravel.com/docs/5.7/validation#form-request-validation).You'll find times in which this rule cannot be applied due to some sort of complicated and arcane checks, of course, butthat's what programming is about 🙂.