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:
- 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();
- 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…## 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.php
programie , 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, substituteBindings
którą opracowaliśmy razem w oprogramowaniu pośredniczącym AutoBinding powyżej.
SubstituteImplicitBindings
Druga metoda, , jest nieco bardziej skomplikowana, substituteImplicitBindings
ponieważ 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!