Fáze požadavku: 4. Routing & Matchování tras
Modul 3 · Lekce 1

Routování v routes/web.php, pojmenované routy a Route Model Binding

💡 Shrnutí lekce

Jak Laravel 12 registruje a vyhodnocuje HTTP routy, proč používat pojmenované trasy a jak funguje automatický Implicitní a Scoped Route Model Binding.

Úvod do routovací vrstvy Laravelu 12

Routovací vrstva (Routing) představuje vstupní bránu do aplikační logiky frameworku. Jakmile HTTP požadavek projde přes webserver (FrankenPHP) a bootstrap fázi (bootstrap/app.php), jádro frameworku předá objekt Illuminate\Http\Request routovací tabulce definované v routes/web.php.

Hlavním úkolem routeru je porovnat příchozí HTTP metodu (GET, POST, PUT, DELETE atd.) a URI cestu proti registrovaným vzorům a určit cílový handler (akci v Controlleru, Livewire komponentu nebo anonymní funkci Closure).

Příchozí HTTP požadavek (GET /moduly/routing-middleware-a-controllery)
        │
        ▼
   routes/web.php ──► Vyhodnocení trasy & HTTP metody
        │
        ▼
   Implicitní / Scoped Route Model Binding (vyhledání entity v PostgreSQL)
        │
        ▼
   Předání čisté instance CourseModule do CourseController::module()

1. Pojmenované routy (Named Routes) a generování URL

V profesionálním vývoji se nikdy neodkazujeme na cílové stránky „natvrdo vepsaným“ textem URL (např. <a href="/moduly/...">). Místo toho přiřazujeme trasám unikátní název pomocí metody ->name():

Route::get('/moduly/{module:slug}', [CourseController::class, 'module'])
    ->name('course.module');

V Blade šablonách nebo přesměrováních pak URL sestavujeme pomocí helper funkce route():

<a href="{{ route('course.module', $module) }}">
    {{ $module->title }}
</a>

Proč je tento přístup klíčový:

  1. Refaktoring beze strachu: Změní-li se kdykoliv struktura URL (např. z /moduly/... na /kurz/kapitoly/...), stačí změnit jeden řádek v routes/web.php. Veškeré odkazy v šablonách i přesměrování v aplikaci zůstanou okamžitě plně funkční.
  2. Automatická serializace parametrů: Předáte-li do helperu route('course.module', $module) přímo Eloquent model, Laravel z něj automaticky vytáhne klíč pro URL podle metody getRouteKeyName().

2. Route Model Binding: Od URL parametru k Eloquent entitě

Jednou z nejelegantnějších vlastností Laravelu je Route Model Binding. Místo ručního dotazování do databáze v controlleru necháme framework vyhledat model automaticky:

A) Implicitní vazba podle sloupce (Scoped / Custom Key)

Ve výchozím stavu Laravel hledá záznamy podle primárního klíče id. V moderních webových aplikacích však vyžadujeme SEO přívětivé URL se slugem:

// routes/web.php
Route::get('/moduly/{module:slug}', [CourseController::class, 'module'])
    ->name('course.module');

Syntaxe {module:slug} říká frameworku: „Vezmi hodnotu z URL, najdi v tabulce course_modules řádek, kde slug = :hodnota, a předej nalezenou instanci CourseModule do parametru metody controlleru.“ Pokud záznam neexistuje, router okamžitě vyhodí výjimku ModelNotFoundException, která vyústí v čistou HTTP chybu 404 Not Found.

B) Vnořený Scoped Route Model Binding

V naší aplikaci máme trasu pro zobrazení konkrétní lekce v modulu:

Route::get('/moduly/{module:slug}/lekce/{lesson:slug}', [CourseController::class, 'lesson'])
    ->name('course.lesson');

Zde nastupuje Scoped Binding: Laravel nejenže vyhledá $module a $lesson, ale díky Eloquent relaci mezi CourseModule a CourseLesson automaticky ověří, že daná lekce skutečně patří tomuto konkrétnímu modulu! Pokud by se útočník pokusil podvrhnout cizí slug lekce k jinému modulu, framework automaticky vrátí 404 Not Found.


3. Rozbor reálného kódu v naší aplikaci

Podívej se do našeho souboru routes/web.php. Zde jsou definovány veškeré veřejné trasy platformy:

  • Route::get('/', [CourseController::class, 'index'])->name('course.index'); — Hlavní rozcestník.
  • Route::get('/moduly/{module:slug}', [CourseController::class, 'module'])->name('course.module'); — Přehled modulu.
  • Route::get('/moduly/{module:slug}/lekce/{lesson:slug}', [CourseController::class, 'lesson'])->name('course.lesson'); — Detail lekce s živou anatomií.
  • Route::get('/api/status', ...)->name('api.status'); — JSON API endpoint pro ověření stavu aplikace.
💻 Klíčová konstrukce z praxe
<?php

declare(strict_types=1);

use App\Http\Controllers\AnatomyController;
use App\Http\Controllers\CourseController;
use App\Models\CourseLesson;
use App\Models\CourseModule;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Route;

// 1. Hlavní rozcestník osnovy a živá anatomie
Route::get('/', [CourseController::class, 'index'])->name('course.index');
Route::get('/anatomie', [AnatomyController::class, 'index'])->name('anatomy.index');

// 2. Přepnutí profilu studenta pomocí přenosového kódu (POST požadavek)
Route::post('/profil/prenosovy-kod', [CourseController::class, 'switchProfile'])->name('course.switch-profile');

// 3. Modul a Scoped Route Model Binding pro lekce ({module:slug} a {lesson:slug})
Route::get('/moduly/{module:slug}', [CourseController::class, 'module'])->name('course.module');
Route::get('/moduly/{module:slug}/lekce/{lesson:slug}', [CourseController::class, 'lesson'])->name('course.lesson');

// 4. Diagnostický JSON endpoint vracející stav systému a počty modulů
Route::get('/api/status', function (): JsonResponse {
    return response()->json([
        'app' => config('app.name'),
        'php_version' => PHP_VERSION,
        'laravel_version' => app()->version(),
        'modules_count' => CourseModule::count(),
        'lessons_count' => CourseLesson::count(),
        'status' => 'ok',
    ]);
})->name('api.status');

🎯 Ověření znalostí z lekce

Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.

Otázka 1 z 3

Co přesně znamená zápis parametru {module:slug} v definici Laravel trasy?

Otázka 2 z 3

Jak se v Laravelu chová Scoped Route Model Binding při zápisu /moduly/{module:slug}/lekce/{lesson:slug}?

Otázka 3 z 3

Jaká je hlavní architektonická výhoda používání pojmenovaných tras přes route('course.lesson', [$m, $l])?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
3 soubory
Na co se v kódu zaměřit: V souboru routes/web.php si všimni řádku 18: Route::get('/moduly/{module:slug}/lekce/{lesson:slug}', [CourseController::class, 'lesson']). Syntaxe {module:slug} instruuje framework, aby záznam vyhledal podle sloupce slug a automaticky ověřil vztah mezi modelem CourseModule a CourseLesson.
cesta: routes/web.php
29 řádků · 1.1 KB
Definice webových tras, Route Model Binding pro moduly a lekce, mapování na Controllery a API status endpoint.
1 <?php
2
3 declare(strict_types=1);
4
5 use App\Http\Controllers\AnatomyController;
6 use App\Http\Controllers\CourseController;
7 use App\Models\CourseLesson;
8 use App\Models\CourseModule;
9 use Illuminate\Http\JsonResponse;
10 use Illuminate\Support\Facades\Route;
11
12 Route::get('/', [CourseController::class, 'index'])->name('course.index');
13 Route::get('/anatomie', [AnatomyController::class, 'index'])->name('anatomy.index');
14
15 Route::post('/profil/prenosovy-kod', [CourseController::class, 'switchProfile'])->name('course.switch-profile');
16
17 Route::get('/moduly/{module:slug}', [CourseController::class, 'module'])->name('course.module');
18 Route::get('/moduly/{module:slug}/lekce/{lesson:slug}', [CourseController::class, 'lesson'])->name('course.lesson');
19
20 Route::get('/api/status', function (): JsonResponse {
21 return response()->json([
22 'app' => config('app.name'),
23 'php_version' => PHP_VERSION,
24 'laravel_version' => app()->version(),
25 'modules_count' => CourseModule::count(),
26 'lessons_count' => CourseLesson::count(),
27 'status' => 'ok',
28 ]);
29 })->name('api.status');
30
cesta: app/Models/CourseModule.php
52 řádků · 1.0 KB
Eloquent model tématického modulu kurzu s vazbou hasMany na lekce a přetypováním na enum ModuleDifficulty.
1 <?php
2
3 declare(strict_types=1);
4
5 namespace App\Models;
6
7 use App\Enums\ModuleDifficulty;
8 use Illuminate\Database\Eloquent\Factories\HasFactory;
9 use Illuminate\Database\Eloquent\Model;
10 use Illuminate\Database\Eloquent\Relations\HasMany;
11
12 class CourseModule extends Model
13 {
14 use HasFactory;
15
16 /**
17 * @var list<string>
18 */
19 protected $fillable = [
20 'number',
21 'slug',
22 'title',
23 'difficulty',
24 'estimated_minutes',
25 'summary',
26 ];
27
28 /**
29 * @return array<string, string|class-string>
30 */
31 protected function casts(): array
32 {
33 return [
34 'difficulty' => ModuleDifficulty::class,
35 'number' => 'integer',
36 'estimated_minutes' => 'integer',
37 ];
38 }
39
40 /**
41 * @return HasMany<CourseLesson, $this>
42 */
43 public function lessons(): HasMany
44 {
45 return $this->hasMany(CourseLesson::class)->orderBy('number');
46 }
47
48 public function getRouteKeyName(): string
49 {
50 return 'slug';
51 }
52 }
53
cesta: app/Models/CourseLesson.php
65 řádků · 1.4 KB
Eloquent model lekce kurzu s obsahem v Markdownu, přetypováním JSON polí a enumem LifecyclePhase.
1 <?php
2
3 declare(strict_types=1);
4
5 namespace App\Models;
6
7 use App\Enums\LifecyclePhase;
8 use Illuminate\Database\Eloquent\Factories\HasFactory;
9 use Illuminate\Database\Eloquent\Model;
10 use Illuminate\Database\Eloquent\Relations\BelongsTo;
11 use Illuminate\Database\Eloquent\Relations\HasMany;
12
13 class CourseLesson extends Model
14 {
15 use HasFactory;
16
17 /**
18 * @var list<string>
19 */
20 protected $fillable = [
21 'course_module_id',
22 'number',
23 'slug',
24 'title',
25 'summary',
26 'content_markdown',
27 'code_snippet',
28 'anatomy_files',
29 'anatomy_note',
30 'lifecycle_phase',
31 ];
32
33 /**
34 * @return array<string, string|class-string>
35 */
36 protected function casts(): array
37 {
38 return [
39 'anatomy_files' => 'array',
40 'lifecycle_phase' => LifecyclePhase::class,
41 'number' => 'integer',
42 ];
43 }
44
45 /**
46 * @return BelongsTo<CourseModule, $this>
47 */
48 public function module(): BelongsTo
49 {
50 return $this->belongsTo(CourseModule::class, 'course_module_id');
51 }
52
53 /**
54 * @return HasMany<QuizQuestion, $this>
55 */
56 public function quizQuestions(): HasMany
57 {
58 return $this->hasMany(QuizQuestion::class)->orderBy('position');
59 }
60
61 public function getRouteKeyName(): string
62 {
63 return 'slug';
64 }
65 }
66
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →