• Czas czytania ~9 min
  • 11.08.2023

W jaki sposób w projektach Laravela konfigurujesz trasy dla konkretnego zasobu? Czy robisz to w następujący sposób?

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

Następnie, czy implementujesz to w ten sposób w swoim ArticlesController?

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

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

Jeśli jest to Twój zwykły proces, Laravel oferuje bardziej wydajną alternatywę o nazwie Wiązanie modelu trasy . Ta funkcja automatycznie pobiera odpowiedni model i przekazuje go do metody. Wszystko, co musisz zrobić, to użyć nazwy modelu jako symbolu zastępczego parametru:

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

W ArticlesController możesz go skonfigurować w następujący sposób:

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

Dzięki temu za każdym razem, gdy uzyskasz dostęp do adresu URL, otrzymasz oczekiwane szczegóły:

my-blog.local/articles/1

Dostosowywanie klucza

Mogą wystąpić przypadki, w których "id" nie jest kluczem, którego szukasz. W naszym przypadku chcielibyśmy zlokalizować artykuł za pomocą jego ślimaka. Można do tego podejść na dwa sposoby:

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

Spowoduje to teraz zapytanie do modelu "Artykuły" według sluga, a nie według id:

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

Zaletą zastosowania getRouteKeyName jest łatwość użycia. Nie trzeba będzie wpisywać {article:slug} za każdym razem, gdy zdefiniujesz trasę, która wymaga wystąpienia "Article".

Warunki

zapytania Powiązanie modelu trasy umożliwia również modyfikowanie sposobu wykonywania zapytań modelu. Na przykład w naszym modelu "Artykuł" możemy chcieć odpytywać tylko artykuły, które zostały opublikowane. W obecnej sytuacji nawet szkice artykułów mogą zostać odnalezione, jeśli ktoś poprawnie odgadnie ślimak przyszłego artykułu.

Możemy nadpisać metodę w naszym modeluresolveRouteBinding, aby to zrobić:

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

Chociaż możemy ominąć $field i zakodować na stałe "slug" jako nasz klucz, lepiej jest wrócić do getRouteKeyName. Obsługuje to różne scenariusze w naszej aplikacji i nie ogranicza nas do używania tylko "slug".

Określanie

zakresu Obecnie wyświetlamy wszystkie artykuły, ponieważ potrzebujemy tylko ślimaka, aby pobrać niezbędne informacje. Ale co, jeśli chcemy wyświetlić artykuł pod odpowiednim autorem? Można to osiągnąć po prostu poprzez zdefiniowanie nowej trasy, która łączy nazwisko autora i slug artykułu:

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

W AuthorArticlesController, należy wykonać następujące czynności:

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

Laravel automatycznie upewni się, że artykuł jest powiązany z właściwym użytkownikiem. Robi to, szukając relacji articles w modelu User i pobierając ją w następujący sposób:

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

This process happens automatically, but&mldr;## Without Określanie

zakresu Ale co, jeśli nie chcesz ograniczać artykułów do ich autorów? Weźmy na przykład listę ulubionych czytelników. Jak możesz to pokazać pod ich nazwą?

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

W takim przypadku można zastosować metodęwithoutScopedBindings(). Zapewni to, że artykuły nie będą ograniczone do konkretnego użytkownika, co pozwoli na szerszy dostęp.

Klasy

niestandardowe Mogą również wystąpić wystąpienia, w których chcesz powiązać obiekty, które nie są modelami, takie jak na przykład obiekt UUID.

Załóżmy, że generujemy identyfikatory UUID dla naszych faktur:

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

Możemy utworzyć powiązanie UUID, aby przekazać go jako obiekt:

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

Aby przekonwertować ciąg UUID z powrotem na obiekt, możemy zdefiniować powiązanie w 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 to potężne narzędzie, które może być używane w wielu przypadkach do uproszczenia kontrolerów, ułatwiając zarządzanie i konserwację.

Jak to działa

Przyjrzyjmy się szybkiemu przykładowi, aby zrozumieć, jak możemy to wdrożyć sami.

Możemy utworzyć nowe oprogramowanie pośredniczące i ustawić powiązanie dla dowolnego parametru trasy o nazwie something 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}

Jest to więc podstawowa struktura, której potrzebujemy, aby skonfigurować powiązanie. Zobaczmy teraz, jak robi to Laravel.

Szczegółowe informacje na temat powiązania

tras Jeśli sprawdzisz tablicę $middlewareGroups zdefiniowaną w app\Http\Kernel.phpprogramie , zauważysz, \Illuminate\Routing\Middleware\SubstituteBindings::class że oprogramowanie pośredniczące jest używane zarówno dla Internetu, jak i interfejsu API. Zanurzmy się w to:

 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}

Metoda uchwytu spełnia dwie funkcje: substituteBindings i substituteImplicitBindings.

SubstituteBindings

Pierwsza funkcja, , próbuje znaleźć dowolne zdefiniowane powiązanie (podobnie jak użyliśmy Route::bind() metody u dostawcy usług):

 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}

Funkcja performBinding wywołuje wywołanie zwrotne zdefiniowane dla twoich binderów:

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

Ten proces jest bardzo podobny do wersji niestandardowej, substituteBindingsktórą opracowaliśmy razem w oprogramowaniu pośredniczącym AutoBinding powyżej.

SubstituteImplicitBindings

Druga metoda, , jest nieco bardziej skomplikowana, substituteImplicitBindingsponieważ próbuje zebrać wszystko, co jest potrzebne do poprawnego zastąpienia parametru:

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

Zagłębmy się w resolveForRoute metodę

 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}

:W tej metodzie dużo się dzieje, więc podzielmy to punkt po punkcie, aby zrozumieć każdą linię:

Pierwsza linia pobiera tablicę parametrów z trasy, następnie wykonywane są następujące kroki:

Rozwiązywanie problemów z wyliczeniami

Powiązania są nie tylko połączone z modelami, ale mogą również automatycznie rozpoznawać wartości wyliczeniowe:

 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}

Metoda resolveBackedEnumsForRoute pobiera nazwę parametru, a następnie próbuje uzyskać wartość przy użyciu tryFrom(). Jeśli nie może rozpoznać wartości, zgłasza BackedEnumCaseNotFoundException.

Rozwiązywanie powiązań

modelu Po rozwiązaniu wyliczeń Laravel wykonuje kolejną pętlę na wszystkich parametrach w akcji trasy, aby uzyskać wszystkie UrlRoutable obiekty:

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

Wszystkie modele w Laravelu implementują UrlRoutable interfejs.

Ale gdy pobierze parametry UrlRoutable w pętli, sprawdza, czy parametr trasy został już rozwiązany:

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

Następnym krokiem jest rozwiązanie tego nowego parametru i zastąpienie go odpowiednim wystąpieniem modelu. Laravel próbuje więc wywnioskować nazwę modelu, a następnie tworzy nowy obiekt:

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

W razie potrzeby poniższy wiersz pobiera element nadrzędny modelu:

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

Uwaga: "Element nadrzędny" jest tutaj związany z koncepcją "zakresu", którą omówiliśmy wcześniej.

Następnie Laravel określa, której metody użyć, w zależności od tego, czy chcemy odzyskać wyrzucone rekordy, czy nie:

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

Na tym etapie Laravel ma wszystkie potrzebne informacje. Ostatnie dwie części dotyczą tylko podjęcia decyzji, czy rozwiązać trasę z wystąpienia, czy z jego elementu nadrzędnego.

Pobieranie wystąpienia z elementu nadrzędnego

Pierwszy warunek sprawdza, czy element nadrzędny istnieje, czy implementuje UrlRoutable i czy zakres jest dozwolony. Jeśli wszystkie te warunki są spełnione, wywoła albo resolveChildRouteBinding lub 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    }

Następnie wykonuje wywołanie do elementu nadrzędnego:

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

co zostanie rozwiązane jako zwykłe wywołanie relacji takie jak:

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

Rozpoznawanie z instancji

Więc jeśli nie ma rodzica lub jeśli withoutScope jest wymuszany, Laravel wykona wywołanie bezpośrednio do modelu:

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

Jeśli instancja nie zostanie znaleziona, wyrzuci plik ModelNotFoundException.

Ostatnią rzeczą na końcu jest to, że zastępuje parametr:

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

I to wszystko, co jest do powiązania trasy w Laravel.

Miłego kodowania!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297