Продвинутый кастинг route параметров в Laravel

Pavel Buchnev
2 min readApr 20, 2021

--

Всем привет.

Многие из вас хотя бы раз использовали 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 , который выполняет две задачи:

  1. Регистрация кастомных биндингов Route:bind или Route::model
  2. Поиск среди параметров контроллера тех, у которых в качестве типа указан класс реализующий интерфейс \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

--

--

Pavel Buchnev

Senior PHP Developer | Contributor to Spiral Framework 🚀 | Enthusiast of RoadRunner & long-running applications | Creator of Buggregator