Когда хватает Gate:
Gate, по сути, именованное замыкание. Определяется в boot() у AppServiceProvider:
Код: Выделить всё
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('access-admin-panel', function (User $user) {
return $user->role === 'admin';
});
}
Код: Выделить всё
if (Gate::allows('access-admin-panel')) {
// пускаем
}
Gate::authorize('access-admin-panel'); // бросит 403, если нельзя
Policy для модели:
Код: Выделить всё
php artisan make:policy PostPolicy --model=Post
Код: Выделить всё
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PostPolicy
{
public function before(User $user, string $ability): ?bool
{
return $user->isAdmin() ? true : null;
}
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post): Response
{
return $user->id === $post->user_id
? Response::allow()
: Response::deny('Удалять можно только свои посты.');
}
}
Где ставить проверки:
Начиная с Laravel 11 базовый контроллер пустой, метода $this->authorize() в нем из коробки нет (трейт AuthorizesRequests подключается вручную, если хочется). Проще использовать фасад Gate:
Код: Выделить всё
use App\Models\Post;
use Illuminate\Support\Facades\Gate;
public function update(Request $request, Post $post)
{
Gate::authorize('update', $post);
// валидация и сохранение
}
public function store(Request $request)
{
Gate::authorize('create', Post::class); // объекта еще нет, передаем класс
}
Код: Выделить всё
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">Редактировать</a>
@endcan
Код: Выделить всё
Route::delete('/posts/{post}', [PostController::class, 'destroy'])
->middleware('auth')
->can('delete', 'post');
Типичные грабли:
Первое и главное: @can в шаблоне прячет кнопку, но не защищает действие. Запрос можно отправить через curl мимо вашей верстки, поэтому проверка обязана стоять в контроллере или на маршруте, а Blade только косметика.
before() с return false вместо return null. false мгновенно запрещает все и всем, и вы будете долго искать, почему владелец не может править собственный пост.
Гости. Для неавторизованного пользователя policy даже не вызывается, Laravel сразу возвращает отказ. Если страница должна быть видна гостям, объявите параметр nullable: public function view(?User $user, Post $post).
Путаница с create. Записи еще нет, поэтому передается имя класса, Post::class. Передадите класс там, где ожидается инстанс, получите ложный отказ и неочевидный дебаг.
И не дублируйте логику. Если правило "владелец или админ" уже в policy, не повторяйте его в Blade условием $post->user_id === auth()->id(). Правило поменяется, а один из трех экземпляров вы забудете.
Итог:
Gates для разовых действий, Policies для CRUD по моделям, before() для суперадмина, Response для внятных отказов. Проверки живут на сервере, а не в шаблоне. В следующей главе займемся файлами: загрузка, Storage, диски local и S3. Авторизация там тоже пригодится, скачивать чужие файлы нельзя точно так же, как редактировать чужие посты.