Доступ к связанным с маршрутом моделям в запросах формы Laravel с #[RouteParameter]

Источник: «Access Route Model-Bound Models in Laravel Form Requests with »
В этой статье вы узнаете о PHP атрибуте #[RouteParameter], который используется в Laravel для доступа к моделям, связанным с маршрутом, в классах запросов форм. Мы подробно рассмотрим, как применять этот атрибут и какие проблемы он решает.

Введение

Недавно я начал использовать новый атрибут #[RouteParameter] в Laravel, и мне он понравился. Он решает проблему, с которой я сталкивался долгое время при работе с запросами формы, и, на мой взгляд, делает код намного чище.

В этой статье я расскажу, что такое атрибут #[RouteParameter], как его использовать и какую проблему он решает.

Что такое атрибут #[RouteParameter]

Атрибут #[Illuminate\Container\Attributes\RouteParameter] — новый PHP атрибут, внесённый в Laravel Bastien Philippe (@bastien-phi) в PR #53080. Он вышел в Laravel v11.28.0.

Атрибут позволяет разрешать параметр маршрута непосредственно в сигнатурах методов запроса формы. Но что это значит? Давайте рассмотрим несколько примеров его использования.

Как использовать атрибут #[RouteParameter]

Атрибут #[RouteParameter] можно использовать в методах authorize и rules запросов формы.

Использование атрибута #[RouteParameter] в методе authorize

Представьте, что есть класс запроса формы App\Http\Requests\UpdateArticleRequest, используемый при обновлении модели App\Models\Article. В методе authorize запроса формы необходимо проверить, является ли авторизованный пользователь владельцем статьи, которую он пытается обновить. Если да, то разрешаем доступ, в противном случае — запрещаем.

Предположим, что этот запрос формы используется методом контроллера, доступ к которому осуществляется по следующему маршруту:

// routes/web.php
use App\Http\Controllers\ArticleController;
use Illuminate\Support\Facades\Route;

Route::patch('/articles/{article}', [ArticleController::class, 'update']);

Используя атрибут #[RouteParameter], можно разрешить модель App\Models\Article из параметров маршрута непосредственно в сигнатуре метода, как это делается в методе контроллера.

Запрос формы будет выглядеть так:

// app/Http/Requests/UpdateArticleRequest.php
declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;

final class UpdateArticleRequest extends FormRequest
{
public function authorize(
#[RouteParameter('article')] Article $article
): bool {
return $article->user->is($this->user());
}

// ...
}

Как видите, мы добавили параметр $article в метод authorize и применили к нему атрибут #[RouteParameter('article')]. Строка "article", переданная в атрибут, является именем параметра маршрута, который необходимо разрешить. Мы также указали тип параметра с помощью модели App\Models\Article, чтобы убедиться, что разрешённый параметр маршрута является экземпляром той модели, которая ожидается.

Например, если мы посетили articles/123, переменная $article теперь будет экземпляром App\Models\Article для статьи с идентификатором 123.

Используя этот атрибут, можно улучшить понимание кода интегрированной средой разработки (IDE) и инструментами статического анализа, чтобы обеспечить лучшее завершение кода и проверку типов.

Довольно круто, правда?

Использование атрибута #[RouteParameter] в методе rules

Помимо использования атрибута #[RouteParameter] в методе authorize, его также можно использовать в методе rules запроса формы.

Например, можно передать экземпляр модели в правило следующим образом:

// app/Http/Requests/UpdateArticleRequest.php
declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

final class UpdateArticleRequest extends FormRequest
{
// ...

public function rules(#[RouteParameter('article')] Article $article): array
{
return [
'slug' => Rule::unique('articles')->ignoreModel($article),
];
}
}

Ошибки при использовании атрибута #[RouteParameter]

Я предпочитаю по возможности избегать сырых строк в своём коде, поскольку склонен к опечаткам. И давайте будем честными, ошибки, вызванные опечатками, всегда труднее всего обнаружить!

Но если вы уверены, что указываете тип параметра с классом модели, который ожидаете, то получите сообщение об ошибке, если допустили опечатку в строке атрибута. Например, допустим, вы случайно набрали "artiicle" вместо "article":

// app/Http/Requests/UpdateArticleRequest.php
declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;

final class UpdateArticleRequest extends FormRequest
{
public function authorize(#[RouteParameter('artiicle')] Article $article): bool
{
return $article->user->is($this->user());
}

// ...
}

Выполнение вышеописанных действий приведёт к ошибке:

App\Http\Requests\UpdateArticleRequest::authorize(): Argument #1 ($article) must be of type App\Models\Article, null given, called in /Users/ashallen/www/laravel-app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php on line 36

Проблема, решаемая атрибутом #[RouteParameter]

Теперь, когда мы рассмотрели, как использовать атрибут #[RouteParameter], давайте посмотрим на решаемую им проблему. Возможно, проблема — это слишком сильно сказано, но мне всегда казалось, что она требует обходного пути.

Раньше, когда требовалось получить доступ к параметрам маршрута в запросах формы, я использовал метод route в объекте запроса для их получения. Например, в методе authorize запроса формы App\Http\Requests\UpdateArticleRequest я делал следующее:

// app/Http/Requests/UpdateArticleRequest.php
declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;

final class UpdateArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->route('article')->user->is($this->user());
}

// ...
}

Выполнение приведённого выше кода полностью корректно, и вызов метода $this->route('article) вернёт экземпляр модели App\Models\Article, разрешённый из параметра маршрута. Однако он не предоставляет достаточно информации для моей IDE или инструментов статического анализа, чтобы понять, что возвращает метод.

Один из используемых мною способов обойти это — создание приватного метода в запросе формы, возвращающего разрешённый параметр маршрута. Например:

// app/Http/Requests/UpdateArticleRequest.php
declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;

final class UpdateArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->article()->user->is($this->user());
}

private function article(): Article
{
return $this->route('article');
}

// ...
}

Это поможет моей IDE с автозавершением в методе authorize, но на самом деле я лишь перекладываю проблему на другой метод (в данном случае на метод article()). Конечно, можно использовать docblock, чтобы предоставить IDE больше информации, но я предпочитаю по возможности избегать их, поскольку они могут устареть и легко забыть их обновить.

По этим причинам мне нравится атрибут #[RouteParameter]. Это небольшое изменение, но оно делает код чище и проще для понимания, а также радует мою IDE и инструменты статического анализа.

Заключение

В статье мы рассмотрели атрибут #[RouteParameter] в Laravel. Выяснили, как использовать его в методах authorize и rules запросов формы и как он решает проблему получения параметров маршрута в этих методах.

Комментарии


Дополнительные материалы

Предыдущая Статья

Понимание различных типов SSH ключей

Следующая Статья

Что означает ошибка "refusing to merge unrelated histories"