Laravel 4.2. Кастомные методы фильтрации в Eloquent моделях (Query Scopes)

Категория: Laravel

Query Scopes предоставляют возможность содержать внутри методов модели часто используемые наборы фильтров. Мне не очень импонирует магия вокруг этих scope'ов.. и то, что для поддержки дополнения кода нужно следить за актуальностью PhpDoc комментариев класса. Но это работает! Поэтому..

Пример реализации scope-метода фильтрации записей по полю email:

/*
 * @method static \Illuminate\Database\Eloquent\Builder|\User ofEmail(string $email)
 */
class User extends Eloquent
{
  public function scopeOfEmail(Illuminate\Database\Eloquent\Builder $query, $email)
  {
    return $query->where('email', $email);
  }
}

Далее вы сможете применять этот фильтр через вызов статического метода модели:

User::ofEmail('value')->first();

Или в составных запросах:

$query = User::select(['username'])->where('is_active', true);
if ('че-то там') {
  $query->ofEmail('mail@gmail.com');
}
$user = $query->first();

Вместо префикса scopeOf вы вольны использовать scopeWhere (например, scopeWhereEmail) - тогда запрос фильтрации будет вида:

User::whereEmail('value')->first();
Примечание

Помните о том, что префикс where используется для составления динамических запросов вида whereShortTitle, который динамически формирует условие фильтрации WHERE short_title = '..'.

Global Scopes

В Laravel 4.2 также появилась возможность объявлять глобальные Scopes, которые применяются ко всем запросам на всех моделях (полезно при использовании Soft Delete).

Примечание

Регистрация глобального скоупа основана на правиле, что если метод трейта именуется как bootSomeNameTrait, то такой метод будет вызван при загрузке (booted) модели, что и позволит зарегистрировать глобальный scope.

Приведу пример объявления глобального скоупа:

trait SoftDeletingTrait
{
  public static function bootSoftDeletingTrait()
  {
    static::addGlobalScope(new SoftDeletingScope);
  }
}

Класс глобального скоупа должен реализовать интерфейс Illuminate\Database\Eloquent\ScopeInterface, который определяет 2 метода apply - отвечает за добавления дополнительных where-условий в запрос, и метод remove - который удаляет where-условия из запроса:

use Illuminate\Database\Eloquent\ScopeInterface;
use \Illuminate\Database\Eloquent\Builder;

class SoftDeletingScope implements ScopeInterface
{
  // Применить scope
  public function apply(Builder $builder)
  {
    $model = $builder->getModel();

    $builder->whereNull($model->getQualifiedDeletedAtColumn());
  }

  // Удалить фильтр глобального скоупа (для методов $model->newQueryWithoutScope*)
  public function remove(Builder $builder)
  {
    $column = $builder->getModel()->getQualifiedDeletedAtColumn();

    $query = $builder->getQuery();

    foreach ((array)$query->wheres as $key => $where)
    {
      // Если в запросе есть `where` условие для `soft delete` (поле deleted_at), то
      // мы удаляем его из запроса и сбрасываем ключи оставшихся `wheres`.
      // Это позволяет включать удаленные модели в результатах выборки,
      // при запросе связанных записей (relationship), ленивой загрузки (lazy loaded) или 
      // при явном "отключении" фильтров скоупа через `newQueryWithoutScope($scope)`
      if ($this->isSoftDeleteConstraint($where, $column))
      {
        unset($query->wheres[$key]);
        $query->wheres = array_values($query->wheres);
      }
    }
  }
}

Также модель предоставляет следующие методы для работы с Global Scopes:

$model->newQueryWithoutScope(new SoftDeletingScope); // получить новый Query объект без применения scope
$model->removeGlobalScopes($builder);                // удалить все глобальные scope

Еще один пример: http://softonsofa.com/laravel-how-to-define-and-use-eloquent-global-scopes/

#eloquent, #query scopes, #models

категория: Laravel