Představte si, že by vaše PHP objekty mohly být čistší,
přehlednější a lépe použitelné. Dobrá zpráva – už nemusíte snít!
PHP 8.4 přichází s revoluční novinkou v podobě property hooks a
asymetrické viditelnosti, které kompletně mění pravidla hry
v objektově orientovaném programování. Zapomeňte na neohrabané gettery a
settery – konečně máme k dispozici moderní a intuitivní způsob, jak
kontrolovat přístup k datům objektů. Pojďme se podívat na to, jak tyto
novinky mohou změnit váš kód k nepoznání.
Property hooks představují promyšlený způsob, jak definovat chování
při čtení a zápisu vlastností objektu – a to mnohem čistěji a
výkonněji než dosavadní magické metody __get/__set. Je to jako
byste dostali k dispozici sílu magických metod, ale bez jejich typických
nevýhod.
Podívejme se na jednoduchý příklad z praxe, který vám ukáže, proč
jsou property hooks tak užitečné. Představme si běžnou třídu
Person s veřejnou property age:
class Person
{
public int $age = 0;
}
$person = new Person;
$person->age = 25; // OK
$person->age = -5; // OK, ale to je přece nesmysl!
PHP sice díky typu int zajistí, že věk bude celé číslo
(to lze od PHP 7.4), ale co s tím záporným věkem? Dříve bychom museli
sáhnout po getterech a setterech, property by musela být private, museli
bychom doplnit spoustu kódu… S hooks to vyřešíme elegantně:
class Person
{
public int $age = 0 {
set => $value >= 0 ? $value : throw new InvalidArgumentException;
}
}
$person->age = -5; // Ups! InvalidArgumentException nás upozorní na nesmysl
Krása tohoto řešení spočívá v jeho jednoduchosti – navenek se
property chová úplně stejně jako dřív, můžeme číst i zapisovat
přímo přes $person->age. Ale máme plnou kontrolu nad tím,
co se při zápisu děje. A to je teprve začátek!
Můžeme jít ještě dál a vytvořit třeba hook pro čtení. Hookům lze
přidat atributy. A samozřejmě mohou obsahovat složitější logiku než
jednoduchý výraz. Podívejte se na tento příklad práce se jménem:
class Person
{
public string $first;
public string $last;
public string $fullName {
get {
return "$this->first $this->last";
}
set(string $value) {
[$this->first, $this->last] = explode(' ', $value, 2);
}
}
}
$person = new Person;
$person->fullName = 'James Bond';
echo $person->first; // vypíše 'James'
echo $person->last; // vypíše 'Bond'
A něco důležitého: kdykoliv se přistupuje k proměnné (i uvnitř
samotné třídy Person), vždy se využijí hooks. Jediná výjimka je přímý
přístup k reálné proměnné uvnitř kódu samotného hooku.
Ohlédnutí do
minulosti: Co nás naučil SmartObject?
Pro uživatele Nette může být zajímavé ohlédnout se do minulosti.
Framework totiž podobnou funkcionalitu nabízel už před 17 lety ve
formě SmartObject,
který výrazně vylepšoval práci s objekty v době, kdy PHP v této
oblasti značně zaostávalo.
Pamatuju si, že tehdy přišla vlna bezbřehého nadšení, kdy se
properties používaly prakticky všude. Tu pak vystřídala vlna opačná –
nepoužívat je nikde. Důvod? Chybělo jasné vodítko, kdy je lepší použít
metody a kdy property. Ale dnešní nativní řešení je kvalitativně úplně
jinde.Property hooks a asymetrická viditelnost jsou plnohodnotné nástroje,
které nám dávají stejnou úroveň kontroly jako máme u metod. Proto dnes
můžeme mnohem lépe rozlišit, kdy je property skutečně tím správným
řešením.
Představte si, že byste mohli svým datům dát pevnou půdu pod
nohama – jednou je nastavíte a pak si můžete být jistí, že je nikdo
nezmění. Přesně to přineslo PHP 8.1 s readonly vlastnostmi. Je to jako
dát vašim objektům neprůstřelnou vestu – chrání jejich data před
nechtěnými změnami. Pojďme se podívat, jak vám tento mocný nástroj
může usnadnit život a na co si při jeho používání dát pozor.
Začněme jednoduchým příkladem:
class User
{
public readonly string $name;
public function setName(string $name): void
{
$this->name = $name; // První nastavení - vše OK
}
}
$user = new User;
$user->setName('John'); // Paráda, máme jméno
echo $user->name; // "John"
$user->setName('Jane'); // BOOM! Výjimka: Cannot modify readonly property
Jakmile jednou jméno nastavíte, je to jako vytesané do kamene. Žádné
náhodné přepsání, žádné nechtěné změny.
Kdy je uninitialized
opravdu uninitialized?
Často se setkávám s mýtem, že readonly vlastnosti musí být nastaveny
v konstruktoru. Ve skutečnosti je PHP mnohem flexibilnější – můžete je
inicializovat kdykoliv během života objektu, ale pouze jednou! Před prvním
přiřazením jsou ve speciálním stavu ‚uninitialized‘, což je takový
limbo stav mezi nebytím a bytím.
A tady přichází zajímavý detail – readonly vlastnosti nemohou mít
výchozí hodnotu. A proč? Kdyby měly výchozí hodnotu, staly by se de facto
konstantami – hodnota by byla nastavena při vytvoření objektu a už by
nešla změnit.
Vyžadují se typy
Readonly proměnné vyžadují explicitní definici datového typu. Je to
proto, že stav ‚uninitialized‘, který využívají, existuje pouze
u typovaných proměnných. Bez uvedení typu tedy readonly proměnnou nelze
definovat. Pokud si nejste jistí typem, můžete použít
mixed.
Vývoj softwaru často přináší dilema. Například jak
řešit situace, kdy getter nemá co vrátit. V tomto článku prozkoumáme
tři strategie pro implementaci getterů v PHP, které ovlivňují strukturu a
čitelnost kódu, a každá má své specifické výhody i nevýhody. Pojďme
se na ně podrobněji podívat.
Univerzální getter
s parametrem
Prvním a v Nette používaným řešením je vytvoření jediné getter
metody, která, pokud hodnota není dostupná, může dle potřeby vrátit buď
null nebo vyhodit výjimku. O chování rozhoduje volitelný
parametr. Zde je příklad, jak by mohla metoda vypadat:
public function getFoo(bool $need = true): ?Foo
{
if (!$this->foo && $need) {
throw new Exception("Foo not available");
}
return $this->foo;
}
Hlavní výhodou tohoto přístupu je, že eliminuje potřebu mít několik
verzí getteru pro různé scénáře použití. Někdejší nevýhodou byla
horší srozumitelnost uživatelského kódu používajícího booleovské
parametry, ale ta padla s příchodem pojmenovaných parametrů, kdy lze psát
getFoo(need: false).
Dále tento přístup může způsobit komplikace v oblasti statické
analýzy, jelikož dle signatury se zdá, že getFoo() může
vrátit null v každé situaci. Nicméně nástroje jako PHPStan
umožňují explicitní dokumentaci chování metody pomocí speciálních
anotací, které zlepšují porozumění kódu a jeho správnou analýzu:
/** @return ($need is true ? Foo : ?Foo) */
public function getFoo(bool $need = true): ?Foo
{
}
Tato anotace jasně určuje, jaké návratové typy může metoda
getFoo() generovat v závislosti na hodnotě parametru
$need. Ale například PhpStorm jí nerozumí.
Dvojice metod: hasFoo()
a getFoo()
Další možností je rozdělit zodpovědnost na dvě metody:
hasFoo() pro ověření existence hodnoty a getFoo()
pro její získání. Tento přístup zvyšuje přehlednost kódu a je
intuitivně srozumitelný.
public function hasFoo(): bool
{
return (bool) $this->foo;
}
public function getFoo(): Foo
{
return $this->foo ?? throw new Exception("Foo not available");
Hlavním problémem je redundance, zvláště v případech, kdy je kontrola
dostupnosti hodnoty sama o sobě náročným procesem. Pokud
hasFoo() provádí složité operace k ověření, zda je hodnota
dostupná, a tato hodnota je poté opět získávána pomocí
getFoo(), dojde k jejich opětovnému provedení. Hypoteticky
může být stav objektu nebo dat změněn mezi voláním hasFoo()
a getFoo(), což může vést k nesrovnalostem. Z uživatelského
pohledu může být tento přístup méně pohodlný, protože nás nutí volat
dvojici metod s opakujícím se parametrem. A nemůžeme využít například
null-coalescing operátor.
Výhodou je, že některé nástroje pro statickou analýzu umožňují
definovat pravidlo, že po úspěšném volání hasFoo() nedojde v
getFoo() k vyhození výjimky.
Metody getFoo() a
getFooOrNull()
Třetí strategií pro je rozdělení funkcionality na dvě metody:
getFoo() pro vyhození výjimky, pokud hodnota neexistuje, a
getFooOrNull() pro vrácení null. Tento přístup
minimalizuje redundanci a zjednodušuje logiku.
public function getFoo(): Foo
{
return $this->getFooOrNull() ?? throw new Exception("Foo not available");
}
public function getFooOrNull(): ?Foo
{
return $this->foo;
}
Alternativou je dvojice getFoo() a
getFooIfExists(), ale v tomto případě nemusí být zcela
intuitivní pochopit, která metoda vyhazuje výjimku a která vrací
null. O trošku výstižnější by byla dvojice
getFooOrThrow() a getFoo(). Další možností je
getFoo() a tryGetFoo().
Každý z představených přístupů k implementaci getterů v PHP má
své místo v závislosti na specifických potřebách projektu a preferencích
vývojářského týmu. Při výběru vhodné strategie je důležité zvážit,
jaký dopad bude mít na čitelnost, údržbu a výkon aplikace. Volba by
odrážet snahu o co nejsrozumitelnější a nejefektivnější kód.
Chcete se ponořit do světa objektově orientovaného
programování v PHP, ale nevíte, kde začít? Mám pro vás nového
stručného průvodce OOP, který vás seznámí se všemi těmi pojmy, jako
class, extends, private atd.
Průvodce si neklade za cíl udělat z vás mistry v psaní čistého kódu
nebo podat zcela vyčerpávající informace. Jeho cílem je vás rychle
seznámit se základními koncepty OOP v současném PHP a dát vám fakticky
správné informace. Tedy poskytnout pevný základ, na kterém můžete dále
stavět. Třeba aplikace v Nette.
Jako navazující čtení doporučuji podrobný průvodce
světem správného návrhu kódu. Ten je přínosný i pro všechny, co
PHP a objektově orientované programování ovládají.
Programování v jazyce PHP byla vždycky trošku výzva, ale naštěstí
prošlo mnohými změnami k lepšímu. Pamatujete na časy před verzí PHP 7,
kdy skoro každá chyba znamenala fatal error, což aplikaci okamžitě
ukončilo? V praxi to znamenalo, že jakákoli chyba mohla aplikaci zcela
zastavit, aniž by programátor měl možnost ji zachytit a náležitě na ni
reagovat. Nástroje jako Tracy využívaly magických triků, aby dokázaly
takové chyby vizualizovat a logovat. Naštěstí s příchodem PHP 7 se tohle
změnilo. Chyby nyní vyvolávají výjimky, jako jsou Error, TypeError a
ParseError, které lze snadno zachytávat a ošetřit.
Avšak i v moderním PHP existuje slabé místo, kdy se chová stejně jako
ve své páté verzi. Mluvím o chybách během kompilace. Ty nelze zachytit a
okamžitě vedou k ukončení aplikace. Jedná se o chyby úrovně
E_COMPILE_ERROR. PHP jich generuje na dvě stovky. Vzniká paradoxní situace,
že když v PHP načteme soubor se syntaktickou chybou, což může být třeba
chybějící středník, vyhodí zachytitelnou výjimku ParseError. Ovšem
v případě, že kód je sice syntakticky v pořádku, leč obsahuje chybu
odhalitelnou až při kompilaci (například dvě metody se stejným názvem),
vyústí to ve fatální chybu, kterou zachytit nelze.
Bohužel, kompilační chyby v PHP nemůžeme ověřit interně. Existovala
funkce php_check_syntax(), která navzdory názvu odhalovala
i kompilační chyby. Byla zavedena v PHP 5.0.0, ale záhy odstraněna ve
verzi 5.0.4 a od té doby nikdy nebyla nahrazena. Pro ověření správnosti
kódu se musíme spolehnout na linter z příkazové řádky:
php -l soubor.php
Z prostředí PHP lze ověřit kód uložený v proměnné
$code třeba takto:
Nicméně režie spouštění externího PHP procesu kvůli ověření
jednoho souboru je docela velká. Ale dobrá zpráva přichází s verzí PHP
8.3, která přinese možnost ověřovat více souborů najednou:
Jenže udělal to v době, kdy potřeba používat isset()
značně klesla. Dnes častěji počítáme s tím, že data, ke kterým
přistupujeme, existují. A pokud neexistují, tak se o tom sakra chceme
dozvědět.
Operátor ?? má ale vedlejší efekt a to schopnost detekovat
null. Což je taky nejčastější důvod k jeho užití:
$len = $this->length ?? 'default value'
Bohužel zároveň zatajuje chyby. Zatajuje překlepy:
Bylo by úžasné, kdyby PHP 9.0 mělo odvahu chování operátoru
?? upravit k trošku větší striktnosti. Udělat z „isset
operátoru“ opravdu „null coalesce operator“, jak se mimochodem oficiálně jmenuje.
Před mnoha lety jsem si uvědomil, že když v PHP ve funkci používám
proměnnou obsahující předdefinovanou tabulku dat, tak při každém volání
funkce musí být pole znovu „vytvořené“, což je překvapivě dost
pomalé. Příklad:
Zrychlení, pokud pole bylo trošku větší, se pohybovalo v několika
řádech (jako třeba klidně 500×).
Takže od té doby jsem u konstantních polí vždy používal
static. Je možné, že tento zvyk někdo následoval, a třeba ani
netušil, jaký má skutečný důvod. Ale to nevím.
Před pár týdny jsem psal třídu, která nesla v několika properties
velké tabulky předdefinovaných dat. Uvědomil jsem si, že to bude zpomalovat
vytváření instancí, tedy že operátor new bude pokaždé
„vytvářet“ pole, což jak víme je pomalé. Tudíž musím properties
změnit na statické, nebo možná ještě lépe použít konstanty.
A tehdy jsem si položil otázku: Hele a nejsi jen ve vleku cargo kultu? Opravdu pořád
platí, že bez static je to pomalé?
Těžko říct, PHP prošlo revolučním vývojem a staré pravdy nemusí
být platné. Připravil jsem proto testovací vzorek a udělal pár měření.
Samozřejmě jsem si potvrdil, že v PHP 5 použití static uvnitř funkce
nebo u properties přineslo zrychlení o několik řádů. Ale pozor, v PHP
7.0 už šlo jen o jeden řád. Výborně, projev optimalizací v novém
jádře, ale stále je rozdíl podstatný. Nicméně u dalších verzí PHP
rozdíl dál klesal a až postupně téměř vymizel.
Dokonce jsem zjistil, že použití static uvnitř funkce v PHP 7.1 a
7.2 běh zpomalovalo. Zhruba 1,5–2×, tedy z pohledu řádů, o kterých se
tu celou dobu bavíme, zcela zanedbatelně, ale byl to zajímavý paradox. Od
PHP 7.3 rozdíl zmizel zcela.
Zvyklosti jsou dobrá věc, ale je nutné jejich smysl stále validovat.
Zbytečný static v těle funkcí už používat nebudu. Nicméně u oné
třídy, která držela velké tabulky předdefinovaných dat v properties,
jsem si řekl, že je programátorsky správné konstanty použít. Za chvíli
jsem měl refaktoring hotový, ale už jak vznikal jsem naříkal nad tím, jak
se kód stává ošklivým. Místo $this->ruleToNonTerminal nebo
$this->actionLength se v kódu objevovalo řvoucí
$this::RULE_TO_NON_TERMINAL a $this::ACTION_LENGTH a
vypadalo to fakt hnusně. Zatuchlý závan ze sedmdesátých let.
Až jsem zaváhal, jestli vůbec chci koukat na tak hnusný kód, a jestli
raději nezůstanu u proměnných, případně statických proměnných.
A tehdy mi to došlo: Hele nejsi jen ve vleku cargo kultu?
No jasně že jsem. Proč by měla konstanta řvát? Proč by měla na sebe
upozorňovat v kódu, být vyčnívajícím elementem v toku programu? Fakt,
že struktura slouží jen ke čtení, není důvod PRO ZASEKNUTÝ CAPSLOCK,
AGRESIVNÍ TÓN A HORŠÍ ČITELNOST.
TRADICE VELKÝCH PÍSMEN POCHÁZÍ Z JAZYKA C, KDE SE TAKTO OZNAČOVALY
MAKROKONSTANTY PREPROCESORU. BYLO UŽITEČNÉ NEPŘEHLÉDNUTELNĚ ODLIŠIT KÓD
PRO PARSER OD KÓDU PRO PREPROCESOR. V PHP SE ŽÁDNÉ PREPROCESORY NIKDY
NEPOUŽÍVALY, TAKŽE NENÍ ANI DŮVOD psát konstanty velkými písmeny.
Ještě ten večer jsem je všude zrušil. A stále nemohl pochopil, proč
mě to nenapadlo už před dvaceti lety. Čím větší blbost, tím tužší
má kořínek.
Vždycky mi vadila jakákoliv nadbytečnost nebo duplicita v kódu. Už jsem
o tom psal před mnoha lety. Při
pohledu na tento kód prostě trpím:
interface ContainerAwareInterface
{
/**
* Sets the container.
*/
public function setContainer(ContainerInterface $container = null);
}
Obsahovou zbytečnost komentáře u metody ponechme stranou.
A protentokrát i projev nepochopení dependency injection, pokud knihovna
potřebuje disponovat takovým rozhraním. O tom, že použití slova
Interface v názvu rozhraní je pro změnu projevem nepochopení
objektového programování, chystám samostatný článek. Koneckonců jsem si
tím sám prošel.
Ale proč proboha uvádět viditelnost public? Vždyť je to pleonasmus. Kdyby to nebylo
public, tak to pak není rozhraní, ne? No a ještě někoho napadlo z toho
udělat „standard“ ?♂️
Uff, omlouvám se za dlouhý úvod, to, kam celou dobu směřuju, je zda
psát volitelné nullable typy s otazníkem nebo bez. Tj:
// bez
function setContainer(ContainerInterface $container = null);
// s
function setContainer(?ContainerInterface $container = null);
Osobně jsem se vždycky klonil k první možnosti, protože informace daná
otazníkem je redundantní (ano, oba zápisy znamenají z pohledu jazyka
totéž). Zároveň se tak zapisoval veškerý kód do příchodu PHP 7.1, tedy
verze, která otazník přidala, a musel by být dobrý důvod jej
najednou měnit.
S příchodem PHP 8.0 jsem názor změnil a vysvětlím proč. Otazník
totiž není volitelný v případě properties. Na tomhle PHP zařve:
class Foo
{
private Bar $foo = null;
}
// Fatal error: Default value for property of type Bar may not be null.
// Use the nullable type ?Bar to allow null default value
A dále od PHP 8.0 lze používat promoted
properties, což umožňuje psát takovýto kód:
class Foo
{
public function __construct(
private ?Bar $foo = null,
string $name = null,
) {
// ...
}
}
Zde je vidět nekonzistence. Pokud je v kódu použito ?Bar
(což je nutnost), mělo by o řádek níže následovat ?string.
A pokud v některých případech budu psát otazník, měl bych ho psát
ve všech.
Zůstává otázka, zda není lepší používat místo otazníku přímo
union typ string|null. Pokud bych třeba chtěl zapsat
Stringable|string|null, verze s otazníkem možná
vůbec není.
Aktualizace: vypadá to, že PHP 8.4 bude zápis s otazníkem přímo
vyžadovat.
Ukončení požadavku v PHP se skládá z těchto kroků prováděných
v uvedeném pořadí:
Volání všech funkcí registrovaných pomocí
register_shutdown_function()
Volání všech metod __destruct()
Vyprázdnění všech output bufferů
Ukončení všech rozšíření PHP (např. sessions)
Vypnutí výstupní vrstvy (odeslání HTTP hlaviček, vyčištění output
handlerů atd.)
Zaměříme se podrobněji na krok č. 2, tedy volání destruktorů.
Samozřejmě už v prvním kroku, tedy při volání registrovaných shutdown
funkcí, může dojít k destrukci objektů, např. pokud některá z funkcí
držela poslední referenci na nějaký objekt nebo pokud byla samotná shutdown
funkce objektem.
Volání destruktorů probíhá takto:
PHP se nejprve pokusí zrušit objekty v globální tabulce symbolů.
Poté volá destruktory všech zbývajících objektů.
Pokud je provádění zastaveno např. kvůli exit(),
zbývající destruktory se nevolají.
ad 1) PHP projde globální tabulku symbolů pozpátku, tj. začne od
proměnné, která byla vytvořena jako poslední, a postupuje k proměnné,
která byla vytvořena jako první. Při procházení zruší všechny objekty
s refcount=1. Tato iterace se provádí, dokud takové objekty existují.
V podstatě se tedy dělá to, že a) odstraní všechny nepoužívané
objekty v globální tabulce symbolů b) pokud se objeví nové nepoužívané
objekty, odstraní je také c) a tak dále. Tento způsob destrukce se
používá proto, aby objekty mohly být závislé na jiných objektech
v destruktoru. Obvykle to funguje dobře, pokud objekty v globálním oboru
nemají komplikované (např. kruhové) vzájemné vazby.
Destrukce globální tabulky symbolů se výrazně liší od destrukce
ostatních tabulek symbolů, viz dále. Pro globální tabulku symbolů tedy PHP
používá chytřejší algoritmus, který se snaží respektovat závislosti
objektů.
ad 2) Ostatní objekty se prochází v pořadí podle jejich vytvoření a
zavolá se jejich destruktor. Ano, PHP pouze zavolá __destruct,
ale ve skutečnosti objekt nezruší (a dokonce ani nezmění jeho refcount).
Pokud se tedy na objekt budou odkazovat jiné objekty, bude stále k dispozici
(i když destruktor již byl zavolán). V jistém smyslu budou používat
jakýsi „napůl zničený“ objekt.
ad 3) V případě, že je provádění zastaveno během volání
destruktorů např. kvůli exit(), zbývající destruktory se
nevolají. Místo toho PHP označí objekty za již destruované. Důležitý
důsledek je, že volání destruktorů není jisté. Případy, kdy se tak
stane, jsou spíše vzácné, ale stát se to může.
Pokud píšete vlastní error handler pro PHP, je
bezpodmínečně nutné dodržet několik pravidel. Jinak může nabourat
chování dalších knihoven a aplikací, které nečekají v error
handleru zradu.
Parametry
Signatura handleru vypadá takto:
function errorHandler(
int $severity,
string $message,
string $file,
int $line,
array $context = null // pouze v PHP < 8
): ?bool {
...
}
Parametr $severity obsahuje úroveň chyby
(E_NOTICE, E_WARNING, …). Pomocí handleru nelze
zachytávat fatální chyby, jako třeba E_ERROR, takže těchto
hodnot nikdy nebude parametr nabývat. Naštěstí fatální chyby v podstatě
z PHP zmizely a byly nahrazeny za výjimky.
Parametr $message je chybová hláška. Pokud je zapnutá
direktiva html_errors,
jsou speciální znaky jako < apod. zapsány jako HTML entity,
takže do podoby plain textu je musíte dekódovat.
Ovšem pozor, některé znaky jako entity zapsány nejsou, což je bug.
Samotné zobrazování chyb v čistém PHP je tak náchylné na XSS.
Parametry $file a $line představují název
souboru a řádek, kde k chybě došlo. Pokud chyba nastala uvnitř
eval(), bude $filedoplněný o tuto informaci.
A nakonec parametr $context obsahuje pole lokálních
proměnných, což představuje pro debugování užitečnou informaci, ale od
PHP 8 je zrušený. Pokud má handler fungovat v PHP 8, parametr vynechte nebo
mu dejte výchozí hodnotu.
Návratová hodnota
Návratová hodnota handleru může být null nebo
false. Pokud handler vrátí null, nestane se nic.
Pokud vrátí false, zavolá se ještě standardní PHP handler.
Ten podle konfigurace PHP může chybu vypsat, zalogovat atd. Co je důležité,
tak že také naplní interní informaci o poslední chybě, kterou
zpřístupňuje funkce error_get_last().
Potlačené chyby
V PHP lze potlačit zobrazování chyb buď pomocí shut-up operátoru
@ nebo pomocí error_reporting():
// potlač chyby úrovně E_USER_DEPRECATED
error_reporting(~E_USER_DEPRECATED);
// potlač všechny chyby při volání fopen()
$file = @fopen($name, 'r');
I při potlačení chyb dojde k volání handleru. Proto je nejprve
nutné ověřit, zda chyba je potlačená, a pokud ano, tak musíme
vlastní handler ukončit:
if (!($severity & error_reporting())) {
return false;
}
Ale pozor, musíme je v tomto případě ukončit pomocí
return false, aby se spustil ještě standardní error handler. Ten
nic nevypíše ani nezaloguje (protože chyba je potlačená), ale zajistí, že
chybu půjde zjistit pomocí error_get_last().
Ostatní chyby
Pokud náš handler chybu zpracuje (například vypíše vlastní hlášku
atd.), už není potřeba volat standardní handler. Sice pak nebude možné
chybu zjistit pomocí error_get_last(), ale to v praxi nevadí,
protože tato funkce se používá především v kombinaci s shut-up
operátorem.
Pokud handler naopak chybu z jakéhokoliv důvodu nezpracuje, měl by
vrátit false, aby ji nezatajil.
Ukázkový příklad
Takto by vypadal kód vlastního error handleru, který transformuje chyby na
výjimky ErrorException:
set_error_handler(function (int $severity, string $message, string $file, int $line) {
if (!(error_reporting() & $severity)) {
return false;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});