Avoid repeating yourself with Laravel Model Scopes

Once you start actually building something with Laravel, it’s easy to find yourself repeatedly typing the same code when you fetch relationships. This article shows you a centralised approach using scopes.

Setup

Imagine you have models for Blog and Post and a relationship between the two — Blog hasMany Post.

A Post has a datetime field called published_at — but your posts aren’t necessarily in order of date published, as some of them take longer to proof than others.

Problem

Across your site, you need to show the 3 most recently-published posts for each blog. Eloquent makes this easy:

$blog->posts->sortByDesc('published_at')->take(3)

But this quickly gets repetitive — and most examples are more complicated. Moreover, your logic for how recent is defined (is it on published date or created date?) is now all over the place.

Maybe we could define this once…

Writing a model scope

On your Post model, add the following function:

public function scopeRecent($query)
{
return $query->orderByDesc('published_at')->limit(3);
}

This is called a local scope, as it’s defined on the model. You can also define a global scope, which can be applied to any model.

It’s also an example of a magic method — any function you create with a name in the format scopeSomething becomes a scope you can apply.

You can now replace your code above with the scope:

$blog->posts()->recent()->get()

You can also amend your scope to take parameters — Laravel calls these dynamic scopes:

public function scopeRecent($query, $count)
{
return $query->orderByDesc('published_at')->limit($count);
}

And this can be used by passing a value into your scope, such as recent(5)

Some extra notes

Why is it sometimes orderBy and sometimes sortBy?

Inside the scope, you are querying the data as it gets fetched from the database — so you need to use Eloquent functions.

Outside the scope, you already have a collection of model instances so you need to use Collection functions.

If you can use Eloquent functions, they’ll be quicker because they are applied before the data is fetched from the database. When using collections, the full set of data is fetched before being filtered.

Telling your editor/IDE about magic methods

Because your scope is a magic method, your editor won’t be aware of the property it enables so you won’t get autocomplete or any of the other benefits your IDE should bring.

You can fix this by adding a special comment above the class declaration. For example, if your scope function is scopeRecent:

/*
* @method mixed recent() Fetches recent items
*/

mixed refers to the return type of the method, which depends on what your scope does — it’s likely to be a Collection, but could be a single item.

You can also add these hints for properties, such as when you use accessors/mutators. PhpStorm has more documentation for property and method.