Eloquent ORM: Modely, metoda casts(), relace HasMany a BelongsTo
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?
- Nativní podpora enumů: Můžeme přímo uvést FQCN enumu
ModuleDifficulty::class. - 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(), [...])). - 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
difficultyuložen obyčejný řetězec'beginner'. - Jakmile v kódu přistoupíte k
$module->difficulty, Eloquent hodnotu automaticky převede na instanci enumuModuleDifficulty::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 relaceHasMany. 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í instanciIlluminate\Database\Eloquent\Collectionobsahující načtené modely.
// 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.
Jaká je hlavní přednost definice casts pomocí metody protected function casts(): array v Laravelu 12?
Co vrátí volání $module->lessons() (s kulatými závorkami) oproti volání $module->lessons (jako vlastnost)?
K čemu slouží metoda getRouteKeyName(): string v Eloquent modelu?
| 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 | |