Продвинутый кастинг route параметров в Laravel
Всем привет.
Многие из вас хотя бы раз использовали laravel Explicit Binding (явная привзяка значений), где можно явно указать, что параметр с определенным ключом должен возвращать подготовленные данные:
// RouteServiceProvider.phppublic function boot()
{
Route::model('user', User::class); // или Route::bind('user', function ($value) {
return User::where('name', $value)->firstOrFail();
});
}// web.phpRoute::get('/users/{user}', function (User $user) {
//
});
А 99% из вас ежедневно используют Route Model Binding, а именно Implicit Binding (неявная привязка значений) в laravel, это когда роутер автоматически загружает объект который указан в качестве типа параметра.
// web.phpRoute::get('/users/{user}', function (User $user) {
// ...
});// илиRoute::get('/users/{user}', 'UserController@show');// UserController.phppublic function show(User $user)
{
// ...
}
В случае Explicit Binding все более менее очевидно, т.к. мы явно указываем что с чем связывать. В случае с Implicit Binding все менее очевидно и его необходимо рассмотреть более подробно.
Implicit Binding
Как видно из названия, неявная привзяка подразумевает, что разработчик не указывает явно что параметр с определенным именем должен быть свзяан с указанной моделью, а фреймворк сам пытается это определить.
Как это происходит:
Отправной точкой в этом процессе является \Illuminate\Routing\Middleware\SubstituteBindings
, который выполняет две задачи:
- Регистрация кастомных биндингов
Route:bind
илиRoute::model
- Поиск среди параметров контроллера тех, у которых в качестве типа указан класс реализующий интерфейс
\Illuminate\Contracts\Routing\UrlRoutable
и загружать данные. Если посмотреть в код Eloquent модели, то можно увидеть, что класс модели реализует этот интерфейс.
Основная задача этого middleware — найти среди параметров все те, которые реализуют этот интерфейс и заменить их на реализацию, выглядит это приблизительно так:
Полный код можно изучить здесь: https://github.com/laravel/framework/blob/8.x/src/Illuminate/Routing/ImplicitRouteBinding.php#L21
Ну а теперь зная все это, давайте напишем что нибудь своё.
Свой вариант биндинга
Представим, что в проекте используюся uuid в качестве идентификаторов и мы хотим на этапе передачи значений в контроллер производить их валидацию (что это действительно uuid) и конвертировать в объект реализующий интерфейс Ramsey\Uuid\UuidInterface
// UserController.phppublic function show(\Ramsey\Uuid\UuidInterface $user)
{
// ...}
Теоретически это можно сделать через стандартный биндинг
Это способ имеет большой недостаток: Что если мы хотим передать в метод контроллера два uuid или хотим давать им разные названия $user
, $userUuid
, … В этом случае нам придется все названия параметров регистрировать и их всегда нужно будет помнить.
Идеальный вариант - производить кастинг по типу передаваемого значения. Т.е. мы хотим чтобы все параметры, которые содержат тип Ramsey\Uuid\UuidInterface
были преобразованы в объект реализующий этот интерфейс.
Мне нравится идея вынести кастинг в подключаемый middleware (по аналогии с \Illuminate\Routing\Middleware\SubstituteBinding
), т.к. это позволит подключать его по необходимости.
Далее регистрируем созданный middleware в Kernel.php
protected $middlewareGroups = [
'web' => [
...
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Interfaces\Http\Middleware\SubstituteUuids::class, ],
'api' => [
...
],
];
И фреймворк сам позаботится о том, чтобы все значения пераметров реализующих этот интерфейс были преобразованы в нужный объект.
Если вам понравилась статья, то буду рад увидеть вас на patreon