Readonly třídy, DTO objekty a Constructor Property Promotion
Poznejte sílu neměnných datových struktur (DTO), zjednodušení kódu pomocí Constructor Property Promotion a důvody, proč je immutabilita klíčová v moderním FrankenPHP prostředí.
Revoluce v psaní tříd: Constructor Property Promotion
Před verzí PHP 8.0 vyžadovala definice jednoduchého datového objektu (DTO) značné množství duplicitního boilerplate kódu:
// Zastaralý přístup v PHP 7:
class OldUserDto {
private string $name;
private string $email;
public function __construct(string $name, string $email) {
$this->name = $name;
$this->email = $email;
}
public function getName(): string { return $this->name; }
public function getEmail(): string { return $this->email; }
}
V PHP 8.0 přišla zásadní inovace: Constructor Property Promotion. Pokud před parametr v konstruktoru přidáte modifikátor viditelnosti (public, protected nebo private), PHP engine automaticky:
- Deklaruje vlastnost třídy se stejným jménem a typem.
- Při vytvoření instance přiřadí předaný argument přímo do této vlastnosti.
Konstruktor se tak zredukuje na pouhou signaturu bez jediného řádku v těle!
Neměnnost s readonly class
V PHP 8.1 byly představeny readonly vlastnosti – vlastnost lze nastavit pouze jednou (typicky v konstruktoru) a jakýkoliv další pokus o zápis vyvolá Error.
PHP 8.2 posunulo koncept ještě dál představením readonly class:
final readonly class AnatomyFileNodeData
{
public function __construct(
public string $path,
public string $category,
public string $description,
public bool $exists,
public int $lineCount,
public int $sizeBytes,
public string $content,
) {}
}
Když označíte celou třídu klíčovým slovem readonly:
- Všechny vlastnosti třídy se automaticky stanou
readonly(nemusíte psátreadonlypřed každou proměnnou). - Třídě nelze dynamicky přidávat vlastnosti za běhu (
$node->foo = 'bar'způsobí chybu). - Zaručuje absolutní immutabilitu (neměnnost) stavu objektu po jeho inicializaci.
Proč je immutabilita kritická ve FrankenPHP a moderních runtimech?
V klasickém PHP-FPM modelu proces po dokončení každého HTTP požadavku zemře a veškerá paměť je uvolněna.
V moderních aplikačních serverech jako je FrankenPHP (nebo RoadRunner) však PHP běží v tzv. Worker módu. Aplikační proces žije nepřetržitě v paměti a postupně odbavuje tisíce HTTP požadavků za sebou:
- Pokud by vaše služby nebo DTO objekty měly modifikovatelný vnitřní stav (mutable state), mohlo by dojít k prosáknutí dat mezi dvěma různými uživateli!
- Neměnné DTO objekty (
readonly) garantují, že jakmile data jednou vytvoříte, žádná jiná část aplikace je nemůže nepozorovaně přepsat. - Kód je plně bezpečný, predikovatelný a vláknově stabilní.
V naší aplikaci agy-laravel používáme final readonly class pro všechny datové přenosy mezi službou AnatomyInspectorService, Livewire komponentami a Blade šablonami.
<?php
declare(strict_types=1);
namespace App\Data;
// Ukázka reálné neměnné třídy z naší aplikace (app/Data/AnatomyFileNodeData.php)
final readonly class AnatomyFileNodeData
{
public function __construct(
public string $path, // Relativní cesta k souboru v projektu
public string $category, // Architektonická vrstva (např. "Controller", "Service")
public string $description, // Popis role souboru v architektuře
public bool $exists, // Zda soubor fyzicky existuje na disku
public int $lineCount, // Počet řádků kódu
public int $sizeBytes, // Velikost souboru v bajtech
public string $content, // Bezpečně načtený obsah pro syntax highlighting
) {}
}
🎯 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řínos označení třídy jako readonly class v PHP 8.2+?
Jak funguje Constructor Property Promotion zavedené v PHP 8.0?
Proč je používání neměnných DTO (immutable objects) obzvlášť důležité ve FrankenPHP worker režimu?
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace App\Data; |
| 6 | |
| 7 | final readonly class AnatomyFileNodeData |
| 8 | { |
| 9 | public function __construct( |
| 10 | public string $path, |
| 11 | public string $category, |
| 12 | public string $description, |
| 13 | public bool $exists, |
| 14 | public int $lineCount, |
| 15 | public int $sizeBytes, |
| 16 | public string $content, |
| 17 | ) {} |
| 18 | } |
| 19 | |