LaravelMiddleTechnical
Что такое scopes в Laravel (local vs global) в Eloquent?
Local scope — метод scopeXxx() на модели, вызывается явно в цепочке запроса; global scope — применяется к каждому запросу автоматически (через addGlobalScope) и отключается через withoutGlobalScope().
Scopes в Eloquent: local и global
Scopes позволяют инкапсулировать повторяющиеся условия запросов внутри модели, делая код в контроллерах и сервисах чище и читаемее.
Local Scopes — переиспользуемые фильтры по требованию
Метод называется scope + ИмяScope (camelCase), принимает Builder и дополнительные параметры. Вызывается без префикса scope в цепочке запроса.
class Post extends Model
{
// Простой scope
public function scopePublished(Builder $query): Builder
{
return $query->where('status', 'published');
}
// Scope с параметром
public function scopeOfType(Builder $query, string $type): Builder
{
return $query->where('type', $type);
}
// Scope с несколькими условиями
public function scopeRecent(Builder $query, int $days = 7): Builder
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}
// Использование
$posts = Post::published()->get();
$posts = Post::published()->ofType('article')->recent(14)->get();
$posts = Post::ofType('video')->orderBy('views', 'desc')->get();
Global Scopes — автоматические фильтры для всех запросов
Применяются к каждому запросу модели автоматически. Реализуются через интерфейс Scope или анонимную функцию. Soft Deletes — встроенный пример глобального scope.
// Отдельный класс scope
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('active', true);
}
}
// Регистрация в модели
class User extends Model
{
protected static function booted(): void
{
static::addGlobalScope(new ActiveScope());
// Или анонимно с именем
static::addGlobalScope('verified', function (Builder $builder) {
$builder->whereNotNull('email_verified_at');
});
}
}
// Теперь User::all() автоматически добавляет WHERE active = 1
$users = User::all(); // SELECT * FROM users WHERE active = 1 AND email_verified_at IS NOT NULL
// Отключение глобального scope
$allUsers = User::withoutGlobalScope(ActiveScope::class)->get();
$allUsers = User::withoutGlobalScope('verified')->get();
$allUsers = User::withoutGlobalScopes()->get(); // отключить все
Soft Deletes как встроенный global scope
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes; // добавляет GlobalScope WHERE deleted_at IS NULL
}
Post::all(); // только не удалённые
Post::withTrashed()->get(); // все, включая удалённые
Post::onlyTrashed()->get(); // только удалённые
$post->restore(); // восстановить
$post->forceDelete(); // удалить физически
Подводные камни
- Global scope ломает JOIN-запросы. Если глобальный scope добавляет WHERE на столбец без алиаса таблицы, при JOIN с другой таблицей, имеющей такой же столбец, возникнет ambiguous column error. Всегда квалифицируйте:
$builder->where('users.active', true). - withoutGlobalScope нужно вызывать явно. Если где-то нужны все записи — не забудьте отключить scope, иначе получите неожиданно отфильтрованные данные.
- Local scope не возвращает Builder — тихая ошибка. Если забыть
return $query->..., метод вернёт null, цепочка сломается с неинформативным сообщением. - Имя local scope не может совпадать с методом Eloquent. Например,
scopeWithвызовет конфликт с методомwith()eager loading. - Global scope и migrations. Если вы запускаете seed/migration и в модели зарегистрирован scope, он применится и там — это может привести к неожиданным результатам при начальном наполнении данными.
- Порядок применения global scopes. Scopes применяются в порядке регистрации. Если один scope изменяет JOIN, а другой добавляет условие на ту же таблицу, порядок может иметь значение.
- Анонимные scopes требуют строкового ключа для отключения.
withoutGlobalScope('verified')работает только если при регистрации передано то же имя 'verified'. Без имени отключить анонимный scope нельзя точечно.
Common mistakes
- Сводить eloquent scopes к названию метода без lifecycle и failure path.
- Игнорировать модель runtime: Laravel 13 поверх PHP: request проходит через HTTP kernel, middleware, routing, controller/action и response pipeline.
- Не отделять validation, authorization, transaction boundary и business logic.
What the interviewer is testing
- Объясняет eloquent scopes через конкретную точку lifecycle в Laravel.
- Приводит корректный минимальный пример без вымышленных методов или callbacks.
- Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.