Nebezpečné používanie konštánt v PHP
Pri používaní konštánt v PHP je potrebné mať na pamäti dve záludnosti.
Dynamické a statické konštanty
Konštantu možno v PHP definovať buď staticky priamo v triede (najlepšie riešenie), napríklad takto:
class Region
{
public const PREFIX = 420;
}
A použitie je celkom jasné. V čase kompilácie triedy je hodnota konštanty určená a môžeme k nej pristupovať volaním názvu triedy a samotnej konštanty. Najčastejšie zápisom Region::PREFIX
.
Druhý (oveľa horší spôsob) je definovať konštantu dynamicky za behu (najčastejšie niekde v konfiguračnom skripte), kde je potom niečo ako:
define('BASE_DIR', __DIR__ . '/../');
Hlavnou nevýhodou definovania konštanty pomocou funkcie define
je, že skript, ktorý definuje konštantu, nemusí byť zavolaný, takže konštanta nebude existovať, keď sa ju pokúsite prečítať.
V kombinácii s použitím dynamickej konštanty v rámci definície statickej konštanty v triede to môže dokonca viesť k fatálnej chybe reflexie:
class InvoiceGenerator
{
// To je úplne nesprávne!
public const DATA_DIR = BASE_DIR . '/data/invoice';
}
Vysvetlenie:
Použitie dynamickej konštanty v rámci statickej konštanty má tú hlavnú nevýhodu, že hodnotu dynamickej konštanty nemožno prečítať v čase kompilácie. Tento skript sa preto musí pri každej požiadavke spracovať znova (t. j. nemôže sa uložiť do vyrovnávacej pamäte OPCache kvôli optimalizácii rýchlosti), a ak konštanta vôbec neexistovala, vyhodí sa fatálna chyba kompilácie a aplikácia sa vôbec nemôže spustiť.
Ak používate program PhpStan, môže vás na tento problém automaticky upozorniť:
Reflection error: Could not locate constant "BASE_DIR" while evaluating expression in InvoiceGenerator at line 6
Učenie:
Hodnota všetkých konštánt by mala byť vždy konštantná.
Dedičnosť konštánt pri použití statických
V niektorých prípadoch má zmysel použiť dedičnosť na prepísanie hodnoty konštanty. V takom prípade však predok nemôže (alebo by nemal) čítať hodnotu z potomka.
Príkladom je definovanie krajín a regiónov:
abstract class Region
{
public function getPrefix(): int
{
// Osudová chyba!
return static::REGION;
}
}
final class CzechRepublic extends Region
{
public const REGION = 420;
}
Paradoxom je, že uvedený kód nemusí nutne vyhodiť chybu, ale môže byť vyhodený nevhodným použitím dedičnosti.
Ak zavoláme metódu getPrefix()
na potomkovi CzechRepublic
, všetko bude správne, pretože hodnota konštanty bude načítaná správne. Ak by však potomok nenastavil hodnotu konštanty, vyhodila by sa fatálna chyba neexistujúcej konštanty. Najhoršie na celej veci je, že ide o skrytú závislosť, ktorá sa vytvára v implementácii metódy, a vývojár, ktorý triedu zdedí, o tejto závislosti nemusí ani vedieť.
Najlepším riešením v tomto prípade je buď definovať konštantu priamo v predkovi s predvolenou hodnotou (aby logika vždy prešla), alebo aspoň vyhodiť výnimku v getteri.
abstract class Region
{
public const REGION = null;
public function getPrefix(): int
{
if (static::REGION === null) {
throw new \LogicException("Región nebol definovaný.);
}
return static::REGION;
}
}
final class CzechRepublic extends Region
{
public const REGION = 420;
}
PhpStan reaguje na túto chybu takto:
Access to undefined constant static(Region):REGION.
Jan Barášek Viac o autorovi
Autor pracuje ako senior vývojár a softvérový architekt v Prahe. Navrhuje a spravuje veľké webové aplikácie, ktoré poznáte a používate. Od roku 2009 získal bohaté skúsenosti, ktoré odovzdáva prostredníctvom tejto webovej stránky.
Rád vám pomôžem:
Kontakt