Preskočiť na obsah
PHP Architektúra 28. júl 2026 · 12 min čítania

PHP 8.4 v praxi: property hooks a asymetrická viditeľnosť

Property hooks sú najväčšia zmena v objektovom modeli PHP za posledných desať rokov. Rušia potrebu písať triviálne gettery a settery a menia spôsob, akým sa navrhujú entity a value objekty. Tento článok ukazuje, kde property hooks a asymetrická viditeľnosť reálne šetria prácu — a kde sú naopak pascou.

DC

Dušan Chlpek

PHP vývojár, GEAR s.r.o. · 25+ rokov praxe

Problém, ktorý property hooks riešia

Typická PHP trieda obsahuje množstvo kódu, ktorý nerobí nič zaujímavé. Vlastnosť je súkromná, k nej patrí getter a setter, setter niekedy niečo overí alebo normalizuje. Pri desiatich vlastnostiach je to dvadsať metód, ktoré nikto nečíta:

<?php
// pred PHP 8.4
final class Zakaznik
{
    private string $email;

    public function getEmail(): string
    {
        return $this->email;
    }

    public function setEmail(string $email): void
    {
        $email = strtolower(trim($email));
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Neplatný e-mail.');
        }
        $this->email = $email;
    }
}

Volajúci kód musí používať $zakaznik->getEmail(). Keď sa rozhodnete vlastnosť sprístupniť priamo, musíte prepísať celú aplikáciu. Presne naopak: keď máte verejnú vlastnosť a potrebujete k nej pridať validáciu, tiež musíte prepísať celú aplikáciu. Property hooks túto väzbu rušia.

Property hooks: getter a setter priamo pri vlastnosti

Property hook je blok kódu, ktorý sa vykoná pri čítaní alebo zápise vlastnosti. Syntax je priamo pri deklarácii:

<?php
final class Zakaznik
{
    public string $email {
        set (string $hodnota) {
            $hodnota = strtolower(trim($hodnota));
            if (!filter_var($hodnota, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException('Neplatný e-mail.');
            }
            $this->email = $hodnota;
        }
    }
}

$zakaznik = new Zakaznik();
$zakaznik->email = '  Jan.Novak@Priklad.SK ';
echo $zakaznik->email;   // jan.novak@priklad.sk

Volajúci kód pracuje s obyčajnou verejnou vlastnosťou. Validácia sa vykoná, no navonok to vyzerá ako prosté priradenie. Ak zajtra validáciu zmeníte, žiadny iný súbor sa meniť nemusí.

Vypočítané vlastnosti cez get

Hook get je ešte užitočnejší. Umožňuje vlastnosť, ktorá nemá vlastné úložisko a počíta sa z iných hodnôt:

<?php
final class Faktura
{
    public function __construct(
        public int $zakladCentov,
        public float $sadzbaDph = 0.23,
    ) {}

    public int $dphCentov {
        get => (int) round($this->zakladCentov * $this->sadzbaDph);
    }

    public int $spoluCentov {
        get => $this->zakladCentov + $this->dphCentov;
    }

    public string $spoluText {
        get => number_format($this->spoluCentov / 100, 2, ',', ' ') . ' EUR';
    }
}

$faktura = new Faktura(zakladCentov: 125000);
echo $faktura->spoluText;   // 1 537,50 EUR

Skrátený zápis so šípkou (get => výraz) je vhodný pre jednoriadkové výpočty. Pre zložitejšiu logiku použijete plný blok s return. Takáto vlastnosť sa nedá zapísať — pokus o priradenie skončí chybou, čo je presne to, čo chcete.

Praktický dopad na šablóny. V Blade šablóne píšete {{ $faktura->spoluText }} namiesto {{ $faktura->getSpoluText() }}. Pri formátovaní cien a dátumov to z šablón odstráni značné množstvo šumu.

Hodnota pred zmenou cez $field

Vnútri hooku máte prístup k podkladovému úložisku vlastnosti. Pri použití plného bloku sa naň odkážete cez názov vlastnosti, čo umožňuje aj lenivé prepočítanie s cache:

<?php
final class Text
{
    private ?int $pocetSlovCache = null;

    public function __construct(public string $obsah) {}

    public int $pocetSlov {
        get {
            return $this->pocetSlovCache
                ??= str_word_count(strip_tags($this->obsah));
        }
    }
}

Asymetrická viditeľnosť: čítanie verejne, zápis súkromne

Druhá veľká novinka sa dá zhrnúť jednou vetou: vlastnosť môže mať inú viditeľnosť pre čítanie a inú pre zápis. Dovtedy ste na to potrebovali súkromnú vlastnosť a verejný getter:

<?php
final class Objednavka
{
    public function __construct(
        public private(set) string $stav = 'nova',
        public protected(set) int $sumaCentov = 0,
    ) {}

    public function zaplatit(): void
    {
        if ($this->stav !== 'nova') {
            throw new DomainException('Objednávku už nie je možné zaplatiť.');
        }
        $this->stav = 'zaplatena';   // vnútri triedy povolené
    }
}

$objednavka = new Objednavka();
echo $objednavka->stav;          // v poriadku — čítanie je verejné
$objednavka->stav = 'zaplatena'; // Error — zápis je súkromný

Toto je presne to, čo väčšina entít potrebuje: stav si môže prečítať ktokoľvek, meniť ho smie len samotný objekt cez metódy s biznis pravidlami. Bez asymetrickej viditeľnosti ste museli písať getter pre každú takúto vlastnosť.

Kombinácia s readonly

readonly a asymetrická viditeľnosť riešia podobný problém, ale nie ten istý. readonly dovolí zápis práve raz. private(set) dovolí ľubovoľný počet zápisov, ale len zvnútra triedy. Pre entitu so stavom, ktorý sa mení, potrebujete private(set). Pre hodnotový objekt, ktorý sa po vytvorení nemení, zostáva správnou voľbou readonly.

Property hooks a readonly sa nekombinujú. Vlastnosť s hookom nemôže byť readonly. Ak potrebujete nemennosť aj validáciu, validujte v konštruktore a vlastnosť nechajte readonly bez hooku.

Hooks v rozhraniach

Rozhranie môže odteraz vyžadovať vlastnosť, nie len metódu. To je významná zmena pre návrh kontraktov:

<?php
interface MaIdentifikator
{
    public string $id { get; }
}

interface MaStav
{
    public string $stav { get; set; }
}

final class Zariadenie implements MaIdentifikator
{
    public function __construct(public readonly string $id) {}
}

Implementácia môže kontrakt naplniť obyčajnou vlastnosťou aj hookom — volajúceho to nezaujíma. Vďaka tomu sa dajú písať rozhrania, ktoré nenútia každú implementáciu do tvaru getId().

Lazy objekty: odložená inicializácia bez knižnice

PHP 8.4 pridalo do reflexie podporu lenivých objektov. Objekt existuje, dá sa odovzdať ďalej, ale jeho skutočná inicializácia prebehne až pri prvom prístupe k vlastnosti alebo metóde. Dovtedy to vedeli len knižnice ako Doctrine cez generované proxy triedy:

<?php
$reflektor = new ReflectionClass(TazkyReport::class);

$report = $reflektor->newLazyGhost(function (TazkyReport $objekt): void {
    // spustí sa až pri prvom reálnom použití objektu
    $objekt->__construct(nacitajMilionRiadkov());
});

// sem sa dostaneme okamžite, dáta sa ešte nenačítali
$sluzba->zaregistruj($report);

V bežnej aplikácii toto priamo nepoužijete. Význam to má pre autorov frameworkov a ORM — a znamená, že závislosti registrované v kontajneri sa dajú vytvárať skutočne lenivo bez réžie generovaných proxy tried.

Drobnosti, ktoré ušetria najviac písania

Volanie metódy na novom objekte bez zátvoriek

<?php
// pred 8.4
$hodnota = (new Formatovac())->format($cislo);

// od 8.4
$hodnota = new Formatovac()->format($cislo);

Nové funkcie pre polia

Doteraz ste hľadanie prvku v poli riešili cyklom alebo kombináciou array_filterreset. PHP 8.4 pridalo štyri funkcie, ktoré to robia priamo:

<?php
$objednavky = [...];

// prvá zhoda alebo null
$nezaplatena = array_find(
    $objednavky,
    fn (Objednavka $o) => $o->stav === 'nova'
);

// existuje aspoň jedna zhoda?
$maNezaplatene = array_any(
    $objednavky,
    fn (Objednavka $o) => $o->stav === 'nova'
);

// spĺňajú podmienku všetky?
$vsetkyZaplatene = array_all(
    $objednavky,
    fn (Objednavka $o) => $o->stav === 'zaplatena'
);

Na kolekciách v Laraveli tieto funkcie nepotrebujete — first(), contains()every() robia to isté. Užitočné sú v čistom PHP a v knižniciach bez závislosti na frameworku.

Atribút #[\Deprecated]

Vlastné funkcie a metódy sa dajú označiť ako zastarané tak, aby PHP samo vygenerovalo upozornenie:

<?php
final class PlatobnaSluzba
{
    #[\Deprecated(
        message: 'Použite metódu vytvorPlatbu().',
        since: '3.2.0',
    )]
    public function createPayment(array $data): Platba
    {
        return $this->vytvorPlatbu($data);
    }
}

Pri údržbe väčšieho projektu alebo internej knižnice je to lacný spôsob, ako upozorniť kolegov bez čítania dokumentácie.

Čo z toho použiť hneď a čo nie

Property hooks nie sú dôvod prepisovať existujúci kód. Fungujúca trieda s gettermi a settermi nemá žiadny problém, ktorý by hooky riešili — prepis len vytvorí príležitosť na chybu. Odporúčam:

Hooky nie sú zadarmo. Prístup k vlastnosti s hookom je volanie funkcie, nie čítanie pamäte. V cykle cez státisíce prvkov je rozdiel merateľný. Vo vnútornej slučke, ktorá rieši výkon, si hodnotu načítajte raz do lokálnej premennej.

Zhrnutie

Ak ešte len plánujete prechod na PHP 8.4, prečítajte si aj článok o rozdieloch medzi PHP 8.3 a 8.4 a o bezpečnostnom checkliste pre produkčné PHP aplikácie.

Beží váš projekt ešte na starom PHP?

Prechod na PHP 8.4 robím vrátane analýzy nekompatibilít, automatizovaného prepisu cez Rector a otestovania na stagingu. Vopred poviem, koľko práce to je — bez záväzku.

Ďalšie články

Zavolať E-mail Dopyt

Ochrana súkromia

Táto stránka využíva cookies pre nevyhnutné fungovanie. Rešpektujeme vaše súkromie a legislatívu GDPR.