Политики в Laravel

Политики в Laravel

Как управлять авторизацией пользователя с помощью Политик (Policies). В прошлой статье мы обсудили авторизацию через Гейты. Если еще не прочли — рекомендую.

Способ управления основанный на классе — Политики

В Гейтах мы использовали способ основанный на замыканиях. Политики же больше похожи на Контроллеры. Вы можете создать Политику, используя следующую команду.

php artisan make:policy PostPolicy

Она создаст файл PostPolicy.php в каталоге app/Policies. Вам нужно будет добавить свои методы в этот класс. Но, если вы укажите в команде параметр --model=Post, то она сгенерирует методы за вас. После этого нужно будет обновить в классе AuthServiceProvider свойство $policies, как указано ниже:

protected $policies = [
    Post::class => PostPolicy::class,
];

Убедитесь, что импортируете классы правильно. Теперь давайте добавим несколько методов в наш класс Политики. Вы можете использовать следующий код.

namespace App\Policies;

use App\User;
use App\Post;

class PostPolicy
{
    use HandlesAuthorization;

    public function create (User $user) {
        return true;
    }

    public function edit (User $user, Post $post) {
        return $user->id == $post->user_id;
    }

    public function update (User $user, Post $post) {
        return $user->id == $post->user_id;
    }

    public function delete (User $user, Post $post) {
        return $user->id == $post->user_id;
    }
}

Как и в Гейтах, методы получают текущего пользователя. Вам не нужно самостоятельно передавать его. Для проверки разрешено ли пользователю то или иное действие, мы можем использовать методы Gate::*, описанные в предыдущей статье.

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

Gate::allows('edit', Post::find(20));

В данном случае edit ссылается на метод класса PostPolicy::edit. И второй параметр приведенного выше фрагмента это второй параметр $post метода PostPolicy::edit. Мы можем использовать эти методы для проверки авторизации.

Всегда ли нужно регистрировать свою Политику?

Краткий ответ: нет. Laravel следует определенным соглашениям, и если вы будете также следовать им, то фреймворк сам найдет подходящий вам класс Политики.
Соглашения следующие:

  • Модель (в нашем случае Post::class) должна находиться внутри каталога app.
  • Политика должна находиться в каталоге app/Policies.
  • Класс политики должен быть формата {Model}Policy, Post — наша модель, соответственно, PostPolicy — название класса Политики.

Если всё так, то Model::class => ModelPolicy::class не требует регистрации.

Предостережения, Соглашения и Иерархия

При использовании метода edit в Gate::allow('edit', Post::find(20)); он будет называться умением (ability) и будет ссылаться на метод PostPolicy::edit. Если название вашего метода в формате CamelCase, то название умения пишется в формате spinal-case. Это соглашение для методов Политики. Вы не можете это изменить.

Если вы не следуете соглашению Laravel для Моделей и Политик, то вам придется зарегистрировать кастомный колбэк с помощью guessPolicyNamesUsingCallback, чтобы получить соответствующий класс Политики для класса Модели.

Больше не значит лучше. Чем больше кода выполняется, тем больше времени требуется для получения ответа. Если вы не зарегистрировали свою Политику в Модели, то Laravel придется её искать самостоятельно, а если зарегистрируете, то фреймворк сразу будет её использовать. Таким образом дольше выполняется код с незарегистрированными Политиками.

Иерархия гейтов в Laravel:

  1. Найти в Политике или Угадать
  2. Найти в Гейтах, определенных в формате ClassName@methodName.
  3. Найти в Гейтах, определенных как замыкание.

Чем раньше найдет, тем раньше обслужит.

Если умение не найдено в Политиках или колбэках, то она всегда будет неавторизована.

Различные способы проверки прав

Ранее мы проверяли только с использованием методов Gate :: *. Теперь мы разберемся с остальными.

Если вы используете класс Политики и ваш метод Политики принимает только объект $user, то вам придется использовать его следующим образом: передайте класс Модели как строку. Передача объекта также сработает, но зачем создавать ресурс?

Gate::allows('create', Post::class);
// Post::class означает, что Гейт должен указывать на PostPolicy::class

Если вы хотите проверить разрешение из маршрута, то используйте мидлвар.

Route::get('/edit/{post}', function (Post $post) {
  // делай что хочешь
})->middleware('can:update,post');
// update — это умение
// post — это модель, из параметра маршрута.
// модель в мидлваре can, параметр маршрута и параметр метода должны совпадать

// ---------------------------

Route::get('create', function () {
  // делай что хочешь
})->middleware('can:create,\App\Post');
// так же как и в пердыдущем фрагменте.
// PostPolicy::create — не принимает никакую модель
// \App\Post - определяет, что метод create находится в классе PostPolicy
// Используйте для класса FQCN (Полное имя класса)
// В обоих случаях если будет выброшено исключение,
// то оно будет перехвачено app/Exceptions/Handler.php
// и будет использован шаблон 403.blade.php.

В своих шаблонах вы можете использовать следующее

@can('update', $post)
<!-- Пользователь может обновить сообщение. -->
<!-- $post это модель, которая используется в PostPolicy::class -->
@endcan

@can('create', \App\Post::class)
<!-- Пользователь может создать сообщение. -->
<!-- \App\Post::class используется в PostPolicy::class -->
@endcan

@cannot('create', \App\Post::class)
<!-- Пользователь не может создать сообщение. -->
<!-- \App\Post::class используется в PostPolicy::class -->
@endcannot

Вы можете напрямую применить следующие методы к модели User

User::find($id)->can('update', Post::find(20));
User::find($id)->cannot('update', Post::find(20));
User::find($id)->cant('edit', Post::find(20));
auth()->user()->can('delete', Post::find(20));

Вы можете вызвать умения из методов контроллера

$this->authorize('update', $post);
// следует иерархии. В Политике или ClassName@method или Замыкании
// $post это модель, либо DI из параметра маршрута
// Или вы уже взяли её из базы

$this->authorize('create', Post::class);
// Post::class используется в PostPolicy.
// если не сработало, значит отсутствует умение create
// и будет выброшено исключение

Если вы используете ресурсный контроллер, то в методе __construct вы можете использовать следующее:

namespace App\Http\Controller;

class PostController extends Controller 
{
    public function __construct() {
        $this->authorizeResource(Post::class, 'post');
        // Post::class это модель для поиска Политики
        // post - имя параметра
    }
}

// если вы вспомните флаг --model=Post в команде make:policy
// то он в класс PostPolicy добавляет следующие методы
// viewAny, view, create, update, delete, restore, forceDelete
// иначе, вам придется создать их самим, чтобы вышеуказанная команда
// $это->авторизацияResource() смогла правильно отработать
// или перепишите метод resourceAbilityMap, чтобы он совпадал с вашими методами.

// Метод PostController::index использует PostPolicy::viewAny
// Метод PostController::show использует PostPolicy::view
// Метод PostController::create использует PostPolicy::create
// Метод PostController::store использует PostPolicy::create
// Метод PostController::edit использует PostPolicy::update
// Метод PostController::update использует PostPolicy::update
// Метод PostController::destroy использует PostPolicy::delete

// Чтобы узнать больше посмотрите файл
// Illuminate/Foundation/Auth/Access/AuthorizesRequests.php:84

Это всё, что вам нужно знать о Политиках Laravel.

Автор: Syed Sirajul Islam Anik
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.