Чем больше становится проект и чем сложнее его логика, тем чаще возникает потребность в построении запросов с условиями, группировками и сортировками. В коде проекта все чаще начнет возникать подобный код:

<?php

$posts = \App\Post::where('state', 'active')
    ->whereIn('user_id', $users)
    ->where('votes_count', '>', 100)
    ->orderBy('id', 'desc');

Иногда такие запросы могут встречаться прямо в обработчиках запросов. Но если начать ими злоупотреблять, то код постепенно начнет превращаться в кашу.

Проблем здесь несколько. Не все условия могут быть очевидны. Например что такое 100 в примере выше? Почему именно 100? Магическое число. Кроме того, если это число имеет какое-то особое значение, то вероятно оно будет встречаться и в других обработчиках точно в таком же запросе. Это значит что произойдет дублирование.

Некоторые части запроса могут иметь значение только тогда, когда они встречаются вместе и их нужно указывать всегда. Например выше мы выбираем только активные посты у которых больше 100 просмотров. Можно предположить что эти два условия связаны между собой, возможно в этом запросе подразумевается поиск "популярных постов". Это значит что в любом месте где нам понадобятся популярные посты, нужно не забыть скопировать эти два условия.

Для решения этих проблем нужно повышать уровень абстракции – создавать функции, которые прячут в себе эти детали. Их можно сделать самостоятельно, но Eloquent уже имеет встроенный механизм называемый скоупами (Scope).

Его принцип работы крайне прост. Каждое условие или набор условий, которые мы бы хотели как то обозначить функцией, можно задать в виде скоупа. Скоуп в терминах ORM и есть функция, которая определяется прямо в модели и используется фреймворком при построении запросов:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopeActive($query)
    {
        return $query->where('state', 'active');
    }

    public function scopePopular($query)
    {
        return $query->where('votes_count', '>', 100);
    }
}

Каждый скоуп это обычный метод, который начинается с префикса scope. Именно по префиксу Eloquent понимает что это скоуп. Внутрь скоупа передается запрос, на который можно навешивать дополнительные условия. Результатом работы любого скоупа должен быть скоуп.

Далее они становятся доступны как и любые другие методы языка запросов:

<?php

// И как статический метод и как обычный метод
$users = \App\User::popular()->active()->orderBy('created_at')->get();

Скоупы можно вызывать из скоупов. Это помогает снизить уровень дублирования при построении более сложных запросов. Например если мы считаем что популярность оценивается только среди активных постов. То это легко поправить изменив соответствующий скоуп:

<?php

public function scopePopular($query)
{
    return $query->active()->where('votes_count', '>', 100);
}

Динамические скоупы

Некоторые скоупы зависят от параметров, передающихся в процессе составления запроса. Для этого, достаточно описать эти параметры внутри скоупа после параметра $query:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    public function scopeOfType($query, $type)
    {
        return $query->where('type', $type);
    }
}

Использование:

<?php

$users = App\User::ofType('admin')->get();

Дополнительные материалы

  1. Локальные скоупы
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →