Eloquent SCOPE

Скоупы

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


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

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

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

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

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


<?php namespace App\Models; 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\Models\User::popular()->active()->orderBy('created_at')->get();

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


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

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

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


<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { public function scopeOfType($query, $type) { return $query->where('type', $type);
    }
}
Eloquent SCOPE
Пролистать наверх