• Время чтения ~4 мин
  • 11.08.2023

Как вы настраиваете маршруты для конкретного ресурса в своих проектах на Laravel? Вы делаете это следующим образом?

Route::get('/articles/{id}', [ArticlesController::class, 'show']);

Затем реализуете ли вы это следующим образом в своем ArticlesController?

public function show($id)
{
	$article = Article::findOrFail($id);

	return view('articles.show', [
		'article' => $article,
	]);
}

Если это ваш обычный процесс, Laravel предлагает более эффективную альтернативу под названием Route Model Binding . Эта функция автоматически извлекает соответствующую модель и передает ее в метод. Все, что вам нужно сделать, это использовать имя модели в качестве заполнителя параметра:

Route::get('/articles/{article}', [ArticlesController::class, 'show']);

В ArticlesController вы можете настроить его следующим образом:

public function show(Article $article) {
    return view('articles.show', [
        'article' => $article
    ]);
}

При этом всякий раз, когда вы обращаетесь к URL-адресу, вы будете получать ожидаемые сведения:

my-blog.local/articles/1

Настройка ключа

Могут быть случаи, когда 'id' не является ключом, который вы ищете. В нашем случае мы хотим найти статью с помощью ее слага. К этому можно подойти двумя способами:

  1. Specify the key in the placeholder
Route::get('/articles/{article:slug}', [ArticlesController::class, 'show']);

Теперь модель 'Articles' будет запрашиваться по слагу, а не по id:

Article::where('slug', $value)->firstOrFail();
  1. Define a routeKeyName method in the Model:
class Article extends Model  {
    public function getRouteKeyName()  {
        return 'slug';
    }
}

Преимущество использования getRouteKeyName заключается в простоте использования. Вам не придется вводить текст {article:slug} каждый раз, когда вы определяете маршрут, для которого требуется экземпляр 'Article'.

Условия

запроса Привязка модели маршрута также позволяет изменять способ выполнения запросов модели. Например, в нашей модели «Статья» мы можем захотеть запрашивать только те статьи, которые были опубликованы. В настоящее время даже черновики статей могут быть восстановлены, если кто-то правильно угадал слаг будущей статьи.

Мы можем переопределить resolveRouteBinding метод в нашей модели, чтобы сделать это:

public function resolveRouteBinding($value, $field = null)
{
    return $this->query()
	    ->where($field ?? $this->getRouteKeyName(), $value)
	    ->where('publish_date' , '<=', now())
	    ->first();
}

Хотя мы могли бы обойти $field и жестко закодировать 'slug' в качестве ключа, лучше вернуться к getRouteKeyName. Это поддерживает различные сценарии в нашем приложении и не ограничивает нас использованием только slug.

Область действия

В настоящее время мы отображаем все статьи, так как нам нужен только слаг для получения необходимой информации. Но что делать, если мы хотим отобразить статью под ее автором? Этого можно достичь, просто определив новый маршрут, который сочетает в себе имя автора и слаг статьи:

1Route::get('/author/{user:username}/articles/{article:slug}', [AuthorArticlesController::class, 'show']);

В AuthorArticlesController вы должны сделать следующее:

1public function show(User $user, Article $article)
2{
3    return view('articles.show', [
4        'article' => $article,
5        'author' => $user
6    ]);
7}

Laravel автоматически гарантирует, что статья связана с правильным пользователем. Для этого он ищет отношение articles в модели User и извлекает его следующим образом:

1$user->articles()->where('slug', $value)->firstOrFail();

This process happens automatically, but&mldr;## Without Область действия

Но что делать, если вы не хотите ограничивать статьи их авторами? Возьмем, к примеру, список избранного читателя. Как вы можете отобразить это под их именем?

1Route::get(
2	'/read/{user:username}/articles/{article:slug}',
3	[ArticlesController::class, 'show']
4)->withoutScopedBindings();

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

Пользовательские классы

Также могут быть случаи, когда вы хотите привязать объекты, которые не являются моделями, например, объект UUID.

Предположим, что мы генерируем UUID для наших счетов:Мы можем создать привязку UUID,

1Route::get('/invoices/{uuid}', [InvoiceController::class, 'show']);

чтобы передать его как объект:

1public function show(LazyUuidFromString $uuid) {
2	// do something
3}

Чтобы преобразовать строку UUID обратно в объект, мы можем определить привязку в AppServiceProvider:

1class AppServiceProvider extends ServiceProvider
2{
3    public function register(): void
4    {
5        Route::bind('uuid', function ($value) {
6            return Uuid::fromString($value);
7        });
8    }
9}

Route Binding — это мощный инструмент, который можно использовать во многих случаях для упрощения ваших контроллеров, что упрощает управление и обслуживание.

Как это работает Давайте

рассмотрим небольшой пример, чтобы понять, как мы могли бы реализовать это самостоятельно.

Мы можем создать новое промежуточное ПО и установить привязку для любого параметра маршрута с именем что-то Route::any('/load/{something}', ...)

 1class AutoBinding
 2{
 3    public function handle(Request $request, Closure $next): Response
 4    {
 5        /** @var Route $route */
 6        $route = $request->route();
 7        if($route->hasParameter('something')) {
 8            $route->setParameter('something', SomethingModel::findByUuid($route->parameter('something')));
 9        }
10        return $next($request);
11    }
12}

Итак, это базовая структура, которая нам нужна для настройки привязки. Теперь давайте посмотрим, как это делает Laravel.

Глубокое погружение в привязку

маршрутов Если вы изучите массив $middlewareGroups, определенный в app\Http\Kernel.php, вы заметите\Illuminate\Routing\Middleware\SubstituteBindings::class, что промежуточное ПО используется как для веба, так и для API. Давайте углубимся в это:

 1public function handle($request, Closure $next)
 2{
 3    try {
 4        $this->router->substituteBindings($route = $request->route());
 5
 6        $this->router->substituteImplicitBindings($route);
 7    } catch (ModelNotFoundException $exception) {
 8        if ($route->getMissing()) {
 9            return $route->getMissing()($request, $exception);
10        }
11
12        throw $exception;
13    }
14
15    return $next($request);
16}

Метод handle выполняет две функции: substituteBindings и substituteImplicitBindings.

SubstituteBindings

Первая функция, , пытается найти любую определенную привязку (очень похоже на то, как мы использовали Route::bind() метод в поставщике услуг):

 1// Illuminate\Routing\Router
 2public function substituteBindings($route)
 3{
 4    foreach ($route->parameters() as $key => $value) {
 5        if (isset($this->binders[$key])) {
 6            $route->setParameter($key, $this->performBinding($key, $value, $route));
 7        }
 8    }
 9
10    return $route;
11}

Функция вызывает обратный вызов, определенный для ваших связывателей:

1protected function performBinding($key, $value, $route)
2{
3    return call_user_func($this->binders[$key], $value, $route);
4}

Этот performBinding процесс очень похож на пользовательскую версию, substituteBindingsкоторую мы разработали вместе в промежуточном ПО AutoBinding выше.

SubstituteImplicitBindings

Второй метод, , немного сложнее, substituteImplicitBindingsтак как он пытается собрать все необходимое для правильной замены параметра:

1public function substituteImplicitBindings($route)
2{
3    ImplicitRouteBinding::resolveForRoute($this->container, $route);
4}

Давайте углубимся в resolveForRoute метод:

 1public static function resolveForRoute($container, $route)
 2{
 3    $parameters = $route->parameters();
 4
 5    $route = static::resolveBackedEnumsForRoute($route, $parameters);
 6
 7    foreach ($route->signatureParameters(['subClass' => UrlRoutable::class]) as $parameter) {
 8        if (! $parameterName = static::getParameterName($parameter->getName(), $parameters)) {
 9            continue;
10        }
11
12        $parameterValue = $parameters[$parameterName];
13
14        if ($parameterValue instanceof UrlRoutable) {
15            continue;
16        }
17
18        $instance = $container->make(Reflector::getParameterClassName($parameter));
19
20        $parent = $route->parentOfParameter($parameterName);
21
22        $routeBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
23                    ? 'resolveSoftDeletableRouteBinding'
24                    : 'resolveRouteBinding';
25
26        if ($parent instanceof UrlRoutable &&
27            ! $route->preventsScopedBindings() &&
28            ($route->enforcesScopedBindings() || array_key_exists($parameterName, $route->bindingFields()))) {
29            $childRouteBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
30                        ? 'resolveSoftDeletableChildRouteBinding'
31                        : 'resolveChildRouteBinding';
32
33            if (! $model = $parent->{$childRouteBindingMethod}(
34                $parameterName, $parameterValue, $route->bindingFieldFor($parameterName)
35            )) {
36                throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
37            }
38        } elseif (! $model = $instance->{$routeBindingMethod}($parameterValue, $route->bindingFieldFor($parameterName))) {
39            throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
40        }
41
42        $route->setParameter($parameterName, $model);
43    }
44}

В этом методе много всего происходит, поэтому давайте разберем его по пунктам, чтобы понять каждую строку:

Первая строка извлекает массив параметров из маршрута, Затем выполняются следующие шаги:

Разрешение перечислений

Привязки не только связаны с моделями, но и могут автоматически разрешать значения перечислений:Метод resolveBackedEnumsForRoute извлекает имя параметра,

 1protected static function resolveBackedEnumsForRoute($route, $parameters)
 2{
 3    foreach ($route->signatureParameters(['backedEnum' => true]) as $parameter) {
 4        if (! $parameterName = static::getParameterName($parameter->getName(), $parameters)) {
 5            continue;
 6        }
 7
 8        $parameterValue = $parameters[$parameterName];
 9
10        $backedEnumClass = $parameter->getType()?->getName();
11
12        $backedEnum = $backedEnumClass::tryFrom((string) $parameterValue);
13
14        if (is_null($backedEnum)) {
15            throw new BackedEnumCaseNotFoundException($backedEnumClass, $parameterValue);
16        }
17
18        $route->setParameter($parameterName, $backedEnum);
19    }
20
21    return $route;
22}

а затем пытается получить значение с помощью tryFrom(). Если он не может разрешить значение, он выдает BackedEnumCaseNotFoundException.

Разрешение привязок

моделей После того, как перечисления разрешены, Laravel выполняет еще один цикл по всем параметрам в действии маршрута, чтобы получить все UrlRoutable объекты:

1foreach ($route->signatureParameters(['subClass' => UrlRoutable::class]) as $parameter) {

Все модели в Laravel реализуют UrlRoutable интерфейс.

Но как только он получает параметры UrlRoutable в цикле, он проверяет, был ли уже разрешен параметр маршрута:

1$parameterValue = $parameters[$parameterName];
2
3if ($parameterValue instanceof UrlRoutable) {
4    continue;
5}

Следующим шагом является разрешение этого нового параметра и замена его соответствующим экземпляром Model. Итак, Laravel пытается вывести имя модели, а затем создает новый объект:

1$instance = $container->make(Reflector::getParameterClassName($parameter));

Следующая строка извлекает родительский элемент модели, если это необходимо:

1$parent = $route->parentOfParameter($parameterName);

Примечание: «родитель» здесь связан с концепцией «области действия», которую мы обсуждали ранее.

Затем Laravel определяет, какой метод использовать, в зависимости от того, хотим ли мы восстановить удаленные записи или нет:

1$routeBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
2            ? 'resolveSoftDeletableRouteBinding'
3            : 'resolveRouteBinding';

На этом этапе у Laravel есть вся необходимая информация. Последние две части посвящены только решению о том, следует ли разрешать маршрут из экземпляра или из его родителя.

Получение экземпляра из родительского объекта

Первое условие проверяет, существует ли родительский элемент, реализует ли он UrlRoutable и разрешена ли область действия. Если все эти условия выполнены, он вызовет либо resolveChildRouteBinding resolveSoftDeletableChildRouteBindingили :

 1if ($parent instanceof UrlRoutable &&
 2    ! $route->preventsScopedBindings() &&
 3    ($route->enforcesScopedBindings() || array_key_exists($parameterName, $route->bindingFields()))) {
 4    $childRouteBindingMethod = $route->allowsTrashedBindings() && in_array(SoftDeletes::class, class_uses_recursive($instance))
 5                ? 'resolveSoftDeletableChildRouteBinding'
 6                : 'resolveChildRouteBinding';
 7
 8    if (! $model = $parent->{$childRouteBindingMethod}(
 9        $parameterName, $parameterValue, $route->bindingFieldFor($parameterName)
10    )) {
11        throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
12    }

Затем он выполнит вызов родителя:

1$model = $parent->{$childRouteBindingMethod}($parameterName, $parameterValue, $route->bindingFieldFor($parameterName))

который разрешится в обычный вызов отношения, например:

1$user->posts->find($value)

Разрешение из экземпляра

Таким образом, если родителя нет или если withoutScope принудительно, то Laravel выполнит вызов модели напрямую:

1} elseif (! $model = $instance->{$routeBindingMethod}($parameterValue, $route->bindingFieldFor($parameterName))) {
2    throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
3}

Если экземпляр не найден, Он выбросит ModelNotFoundException.

Последнее, что нужно в конце, это то, что он заменяет параметр:И это все,

1$route->setParameter($parameterName, $model);

что есть в привязке маршрута в Laravel.

Удачного программирования!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297