Home Dynamic Query Scopes

Enter a search term to find articles.
2018-10-10
104

Dynamic Query Scopes

Long controller methods and complicated queries often times go hand in hand. One of the common reasons I see is that developers don't utilize dynamic scopes for their models enough (or at all).

As a rule of thumb, whenever I find 'where' query calls that needs a callback, I usually do throw that into a scope. I find this most common when I need to do some sort of constraint on the relationship.

Throwing your query calls into a scope abstracts the actual implementation, and you can just re-use the scope whenever you want.

Example:

We are developing a medical service app and we are tasked to get all the doctors having cases assigned to the current authenticated doctor and include a count of cases assigned by each doctor.

Our original implementation goes like this..

use App\Models\Doctor;
use Illuminate\Support\Facades\Auth;

...

public function index()
{
    return Doctor::whereHas('cases', function ($query) {
        $query->where('assignee_id', Auth::id());
    })
    ->withCount(['cases' => function ($query) {
        $query->where('assignee_id', Auth::id());
    }])
    ->get();
}

This implementation isn't bad at all. However, reading this takes a while to process what this code is trying to accomplish. Also, if we are going to use the scope on other places aside from the index() method, it makes more sense to just extract it to a scope.

Let's see how we can refactor this..

use App\Models\Doctor;
use Illuminate\Support\Facades\Auth;

...

public function index()
{
    return Doctor::onlyWithCasesAssignedTo(Auth::user())
        ->withCountOfCasesAssignedTo(Auth::user())
        ->get();
}

// Then in our Doctor model, we add couple of query scopes..

use Illuminate\Database\Eloquent\Builder;

...

public function scopeOnlyWithCasesAssignedTo(Builder $query, Doctor $doctor)
{
    return $query->whereHas('cases', function ($query) {
        $query->where('assignee_id', $doctor->id);
    });
}

public function scopeWithCountOfCasesAssignedTo(Builder $query, Doctor $doctor)
{
    return $query->withCount(['cases' => function ($query) {
        $query->where('assignee_id', Auth::id());
    }]);
}

And there you go! We've successfully extracted the complicated queries from our controller to their dedicated query scope methods.

Cheers!

Marvin Quezon

Full Stack Web Developer
Marvin Quezon · Copyright © 2024 · Privacy · Sitemap