Fáze požadavku: 8. Databáze & Eloquent ORM
Modul 6 · Lekce 2

Eloquent ORM: Modely, metoda casts(), relace HasMany a BelongsTo

💡 Shrnutí lekce

Jak Eloquent mapuje databázové záznamy na objekty. Využití nové metody casts() v Laravelu 12, přetypování na nativní PHP enumy a obousměrné relace HasMany a BelongsTo.

Eloquent ORM: Síla objektově-relačního mapování v Laravelu 12

Eloquent ORM je srdcem databázové vrstvy Laravelu. Implementuje návrhový vzor Active Record, kde každá PHP třída modelu reprezentuje jednu tabulku a instance modelu odpovídá konkrétnímu řádku v této tabulce.

V moderním Laravelu 12 a PHP 8.4 píšeme modely se striktním typováním a maximálním využitím nativního typového systému.

1. Nová metoda casts(): array v Laravelu 12

Historicky se v Eloquent modelech přetypování sloupců definovalo přes chráněnou vlastnost protected $casts = [...]. Od Laravelu 11 a 12 je standardem deklarovat metodu protected function casts(): array:

// app/Models/CourseModule.php
protected function casts(): array
{
    return [
        'difficulty' => ModuleDifficulty::class,
        'number' => 'integer',
        'estimated_minutes' => 'integer',
    ];
}

Proč je metoda lepší než vlastnost?

  1. Nativní podpora enumů: Můžeme přímo uvést FQCN enumu ModuleDifficulty::class.
  2. Dynamická logika a dědičnost: V případě potřeby můžete v metodě volat pomocné funkce nebo slučovat pravidla z rodičovské třídy (array_merge(parent::casts(), [...])).
  3. Statická analýza: Nástroje jako PHPStan / Larastan mnohem lépe analyzují návratový typ metody a kontrolují správnost typů.

2. Automatické přetypování na PHP 8.4 Backed Enumy

Všimněte si přetypování 'difficulty' => ModuleDifficulty::class:

  • V PostgreSQL je v sloupci difficulty uložen obyčejný řetězec 'beginner'.
  • Jakmile v kódu přistoupíte k $module->difficulty, Eloquent hodnotu automaticky převede na instanci enumu ModuleDifficulty::Beginner.
  • Můžete rovnou volat metody enumu: $module->difficulty->label() nebo $module->difficulty->badgeClass().
  • Při uložení do databáze ($module->save()) Eloquent automaticky extrahuje surovou hodnotu $enum->value.

V modelu CourseLesson stejným způsobem přetypováváme sloupec lifecycle_phase na LifecyclePhase::class a sloupec anatomy_files na 'array' (automatický JSON decode/encode).

3. Vztahy HasMany a BelongsTo

Eloquent umožňuje definovat čisté obousměrné relace:

V modelu CourseModule:

/**
 * @return HasMany<CourseLesson, $this>
 */
public function lessons(): HasMany
{
    return $this->hasMany(CourseLesson::class)->orderBy('number');
}

V modelu CourseLesson:

/**
 * @return BelongsTo<CourseModule, $this>
 */
public function module(): BelongsTo
{
    return $this->belongsTo(CourseModule::class, 'course_module_id');
}

/**
 * @return HasMany<QuizQuestion, $this>
 */
public function quizQuestions(): HasMany
{
    return $this->hasMany(QuizQuestion::class)->orderBy('position');
}

Všimněte si klíčového rozdílu:

  • $module->lessons() (s kulatými závorkami) vrací instanci Query Builderu relace HasMany. Můžete nad ní dále řetězit podmínky, např. $module->lessons()->where('lifecycle_phase', 'view')->get().
  • $module->lessons (bez závorek, dynamická vlastnost) dotaz rovnou provede a vrátí instanci Illuminate\Database\Eloquent\Collection obsahující načtené modely.
💻 Klíčová konstrukce z praxe
// app/Models/CourseModule.php & CourseLesson.php

class CourseModule extends Model
{
    protected $fillable = [
        'number', 'slug', 'title', 'difficulty', 'estimated_minutes', 'summary',
    ];

    // Nová metoda casts() v Laravelu 12
    protected function casts(): array
    {
        return [
            'difficulty' => ModuleDifficulty::class, // Nativní enum cast
            'number' => 'integer',
            'estimated_minutes' => 'integer',
        ];
    }

    public function lessons(): HasMany
    {
        return $this->hasMany(CourseLesson::class)->orderBy('number');
    }

    // Pro Route Model Binding: v URL hledáme podle sloupce slug místo id
    public function getRouteKeyName(): string
    {
        return 'slug';
    }
}

🎯 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

Jaká je hlavní přednost definice casts pomocí metody protected function casts(): array v Laravelu 12?

Otázka 2 z 3

Co vrátí volání $module->lessons() (s kulatými závorkami) oproti volání $module->lessons (jako vlastnost)?

Otázka 3 z 3

K čemu slouží metoda getRouteKeyName(): string v Eloquent modelu?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
3 soubory
Na co se v kódu zaměřit: V app/Models/CourseModule.php prozkoumejte metodu casts() na řádcích 31–38 a metodu lessons() na řádcích 43–46. V app/Models/CourseLesson.php se zaměřte na řádky 36–43 (array a enum casting) a getRouteKeyName() na řádcích 61–64.
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
cesta: app/Models/QuizQuestion.php
46 řádků · 0.9 KB
Eloquent model otázky kvízu s JSON možnostmi, správnou volbou a vysvětlením pro interaktivní prověření znalostí.
1 <?php
2
3 declare(strict_types=1);
4
5 namespace App\Models;
6
7 use Illuminate\Database\Eloquent\Factories\HasFactory;
8 use Illuminate\Database\Eloquent\Model;
9 use Illuminate\Database\Eloquent\Relations\BelongsTo;
10
11 class QuizQuestion extends Model
12 {
13 use HasFactory;
14
15 /**
16 * @var list<string>
17 */
18 protected $fillable = [
19 'course_lesson_id',
20 'position',
21 'question',
22 'options',
23 'correct_option',
24 'explanation',
25 ];
26
27 /**
28 * @return array<string, string>
29 */
30 protected function casts(): array
31 {
32 return [
33 'options' => 'array',
34 'correct_option' => 'integer',
35 'position' => 'integer',
36 ];
37 }
38
39 /**
40 * @return BelongsTo<CourseLesson, $this>
41 */
42 public function lesson(): BelongsTo
43 {
44 return $this->belongsTo(CourseLesson::class, 'course_lesson_id');
45 }
46 }
47
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →