Laravel Policies — это механизм объектных разрешений, который определяет, может ли конкретный пользователь выполнять действие над конкретной моделью. В отличие от Gate, которые работают на уровне приложения и маршрутов, Policies привязаны к модели (например, Post, Order, User) и позволяют реализовать логику вроде «пользователь может редактировать только свои посты» или «менеджер видит заказы своего отдела». Это стандартный способ реализации объектного доступа в экосистеме Laravel.
Что такое Laravel Policies и зачем они нужны для объектных разрешений
Policies — это классы, которые содержат методы проверки прав доступа. Каждый метод соответствует действию над моделью (например, create, update, delete). Когда вы вызываете метод типа $post->authorizeUpdate($user), Laravel автоматически ищет Policy для модели Post и выполняет проверку.
Главное отличие от Gate: Gate отвечает на вопрос «Может ли пользователь делать это вообще?» (глобальный уровень), а Policy — «Может ли этот пользователь делать это с этой конкретной записью?» (локальный уровень).
Использование Policies необходимо для реализации принципа наименьших привилегий. Вы не даете пользователю роль 'admin' сразу, а проверяете права в зависимости от контекста данных.
Пример задачи: у вас есть модель Post. Пользователь должен видеть все посты, но редактировать только свои. Gate здесь не поможет эффективно без усложнения логики. Policy позволяет написать метод update, который возвращает true только если $user->id == $post->user_id.
Как настроить базовую политику: создание, методы authorize и правила доступа
Создайте Policy через Artisan или вручную. Команда генерирует класс с методами по умолчанию.
Структура класса Policy для модели Post выглядит так:
class PostPolicy extends BasePolicy { public function update(User $user, Post $post): bool { return $this->authorize($user); } }
Метод authorize — это ядро проверки. Он принимает объект пользователя и (опционально) объект модели. Возвращаем true или false.
Внутри метода вы пишете бизнес-логику: проверка роли, сравнение ID пользователей, проверка статуса записи.
Если метод возвращает true, действие разрешено. Если false — Laravel выбрасывает исключение UnauthorizedHttpException (если не перехвачено middleware).
Вы можете использовать helper $this->authorize() внутри методов Policy для делегирования проверки другим правилам или ролям.
Различия между классических Gate и Policies: когда использовать каждый инструмент
Gate работает на уровне приложения. Вы вызываете Gate::check('edit-post') в контроллере или middleware. Он не знает о конкретной модели Post, только о роли пользователя.
Policy привязан к классу модели. Laravel сам связывает модель с Policy через имя класса (Post -> PostPolicy).
Используйте Gate, когда нужно проверить доступ к ресурсам, которые не являются моделями Eloquent (например, статические данные, файлы в хранилище, внешние API).
Используйте Policy, когда действие зависит от содержимого записи: «редактировать свой профиль», «удалить свою запись», «видеть заказы своего магазина».
Gate проще для глобальных настроек (например, только админы могут удалить пользователя). Policy сложнее в настройке, но гибче для объектных проверок.
Пример реализации политики для модели Post с проверкой авторства и прав администратора
Предположим, у вас есть модель Post с полями user_id и role (опционально). Создаем класс PostPolicy.
Метод update должен разрешать редактирование только владельцу или администратору:
public function update(User $user, Post $post): bool { if ($user->role === 'admin') { return true; } return $user->id === $post->user_id; }
Метод destroy должен разрешать удаление только владельцу или администратору:
public function destroy(User $user, Post $post): bool { if ($user->role === 'admin') { return true; } return $user->id === $post->user_id; }
Метод view должен разрешать чтение всем пользователям:
public function view(User $user, Post $post): bool { return true; }
Эти методы будут вызываться автоматически при использовании методов типа $post->authorizeUpdate($user).
Настройка ролей и уровней доступа внутри одной политики (admin vs user)
В одном классе Policy можно реализовать несколько уровней прав. Это делается через отдельные методы или проверку внутри одного метода.
Пример: метод edit может разрешать редактирование всем пользователям, но с ограничениями по количеству постов для обычных юзеров:
public function edit(User $user, Post $post): bool { if ($user->role === 'admin') { return true; } return $user->posts_count < 5; }
Такой подход позволяет гибко управлять доступом без создания множества Policy классов.
Если логика слишком сложная, рассмотрите разделение на несколько Policy или использование middleware для фильтрации запросов до попадания в контроллер.
Как передавать дополнительные данные в метод authorize через аргумент $user
В методе authorize первым аргументом всегда идет объект User. Вы можете расширить этот объект, добавив кастомные поля или отношения.
Пример: если у пользователя есть поле 'department', вы можете проверить принадлежность к отделу:
public function update(User $user, Post $post): bool { return $user->department === $post->department; }
Если нужно передать дополнительные данные из модели в метод authorize, используйте аргумент $model (второй параметр).
Пример: public function update(User $user, Post $post): bool { return $user->id === $post->user_id || $user->isAdmin(); }
Не передавайте лишние данные. Laravel сам передает модель и пользователя в методы Policy.
Ошибки при работе с Policies: типичные проблемы и способы их устранения
Ошибка 1: Политика не применяется. Причина: имя класса Policy не совпадает с именем модели (Post -> PostPolicy). Решение: проверьте название файла и класса.
Ошибка 2: Метод authorize возвращает null. Laravel ожидает bool. Если метод не возвращает true/false, доступ будет запрещен по умолчанию.
Ошибка 3: Использование Gate вместо Policy для объектных проверок. Это приводит к необходимости передавать модель вручную в Gate::check(). Решение: используйте Policy для моделей.
Ошибка 4: Игнорирование исключений UnauthorizedHttpException. Если метод authorize вернул false, Laravel выбрасывает исключение. Нужно перехватить его в контроллере или middleware и вернуть ответ 403.
Ошибка 5: Отсутствие проверки прав на удаление. Метод destroy часто забывают реализовать. По умолчанию доступ запрещен.
Интеграция Policies с middleware для защиты маршрутов без явного вызова
Policies можно использовать в middleware для защиты маршрутов. Создайте middleware, который вызывает $post->authorizeUpdate($user) перед обработкой запроса.
Пример middleware:
class AuthorizeMiddleware { public function handle(Request $request, Closure $next, Post $post) { if (!$post->authorizeUpdate($request->user())) { abort(403); } return $next($request); } }
Затем добавьте этот middleware в группу маршрутов или конкретный маршрут.
Такой подход позволяет защитить маршруты без явного вызова authorize() в контроллере.
Однако, чаще всего Policies вызываются напрямую в контроллере через $post->authorizeUpdate($user), что проще и понятнее.
Вопросы и ответы
Можно ли использовать Policy для проверки прав на статический файл?
Нет, Policy привязан к модели Eloquent. Для файлов используйте Gate или middleware с проверкой прав.
Как отключить проверку Policy для конкретного запроса?
Используйте метод $post->skipAuthorization() перед вызовом authorizeUpdate(). Это полезно для админ-панелей, где права проверяются вручную.
Можно ли наследовать Policy от другого Policy?
Нет, Policy не поддерживает наследование. Каждый Policy должен быть отдельным классом.
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\Gate;
class PostPolicy extends BasePolicy
{
/**
* Разрешает ли пользователь редактировать пост?
*
* @param User $user
* @param Post $post
* @return bool
*/
public function update(User $user, Post $post): bool
{
if ($user->role === 'admin') {
return true;
}
return $user->id === $post->user_id;
}
/**
* Разрешает ли пользователь удалять пост?
*
* @param User $user
* @param Post $post
* @return bool
*/
public function destroy(User $user, Post $post): bool
{
if ($user->role === 'admin') {
return true;
}
return $user->id === $post->user_id;
}
/**
* Разрешает ли пользователь просматривать пост?
*
* @param User $user
* @param Post $post
* @return bool
*/
public function view(User $user, Post $post): bool
{
return true;
}
}