Výjimky a zpracování chyb (error handling)
K čemu je to dobré?
- Error Handling
- vědět, co se stalo - např. zaznamenat do logu
- zotavit se z chyby, pokud je to možné
- PHP má dva mechanismy
- výjimky - objekty, které se vyhazují a zachytávají (
throw,try/catch); stejný princip jako v C++ - chyby (errors) - historický mechanismus vestavěných funkcí (warning, notice, …); řeší se přes error handler
- výjimky - objekty, které se vyhazují a zachytávají (
Výjimky
- Referenční dokumentace
- vyhození příkazem
throw, od PHP 8 jethrowvýraz (lze použít např. za??) - zachycení blokem
try/catch, volitelněfinally - každá výjimka je objekt třídy implementující rozhraní
Throwable - nezachycená výjimka ukončí skript fatální chybou (příp. ji zpracuje
set_exception_handler(), viz níže) - výjimka postupuje zásobníkem volání nahoru, dokud ji někdo nezachytí
function divide(float $a, float $b): float {
if ($b == 0) {
throw new InvalidArgumentException("Nelze dělit nulou");
}
return $a / $b;
}
try {
echo divide(10, 2) . "\n"; // 5
echo divide(1, 0) . "\n"; // vyhodí výjimku, další řádek se neprovede
echo "Tohle se nevypíše\n";
} catch (InvalidArgumentException $e) {
echo "Chyba: " . $e->getMessage() . "\n";
} finally {
echo "Provede se vždy (úklid zdrojů apod.)\n";
}Hierarchie výjimek
Throwable- kořenové rozhraní, nelze implementovat přímo (jen skrzExceptionneboError)Exception- výjimky aplikace a knihoven, základ pro vlastní výjimky- SPL výjimky
LogicException(InvalidArgumentException,OutOfRangeException, …)RuntimeException(OutOfBoundsException,UnexpectedValueException, …)
- výjimky PHP rozšíření:
JsonException,PDOException, …
- SPL výjimky
Error- chyby interpretu, dříve fatální chybyTypeError(špatný typ argumentu),ValueError(správný typ, ale nesmyslná hodnota),ArgumentCountError,DivisionByZeroError,ArithmeticError, …
catch (Exception $e)vestavěné chyby nezachytí; pro obojí použijtecatch (Throwable $e)- pro vlastní výjimky použijte nejbližší vhodnou SPL výjimku jako rodiče
try {
intdiv(1, 0);
} catch (Exception $e) {
echo "Sem se nedostaneme\n";
} catch (Error $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n"; // DivisionByZeroError: Division by zero
}Zachycení výjimek
- bloků
catchmůže být více, vyhodnocují se v pořadí zápisu → od nejkonkrétnější k nejobecnější - jeden blok pro více typů:
catch (A | B $e) - od PHP 8 lze proměnnou vynechat, pokud výjimku nepotřebujeme:
catch (A) - výjimku lze v
catchznovu vyhodit (throw $e;) nebo zabalit do jiné (viz níže)
try {
$data = json_decode($input, flags: JSON_THROW_ON_ERROR);
process($data);
} catch (JsonException $e) {
echo "Neplatný JSON: " . $e->getMessage() . "\n";
} catch (InvalidArgumentException | UnexpectedValueException $e) {
echo "Neplatná data: " . $e->getMessage() . "\n";
} catch (Throwable $e) {
echo "Neočekávaná chyba: " . get_class($e) . "\n";
throw $e; // znovu vyhodíme, ať se o ni postará někdo výš
}Metody výjimky
getMessage()- text zprávygetCode()- číselný kód (význam určuje ten, kdo výjimku vyhazuje; např. HTTP status, kód chyby databáze)getFile(),getLine()- kde byla výjimka vytvořenagetTrace(),getTraceAsString()- zásobník volánígetPrevious()- předchozí výjimka (řetězení, viz níže)__toString()- vše výše v jednom řetězci, vhodné do logu
Vlastní výjimky
- třída dědící od
Exception(nebo od vhodné SPL výjimky) - konstruktor
__construct(string $message = "", int $code = 0, ?Throwable $previous = null) - může mít vlastní atributy a metody (např. jaký záznam nebyl nalezen)
class ProductNotFoundException extends RuntimeException {
public function __construct(
public readonly int $productId,
?Throwable $previous = null,
) {
parent::__construct("Produkt $productId nebyl nalezen", 404, $previous);
}
}
function findProduct(int $id): array {
$products = [1 => ['name' => 'Mitsubishi Lancer']];
return $products[$id] ?? throw new ProductNotFoundException($id);
}
try {
findProduct(42);
} catch (ProductNotFoundException $e) {
echo $e->getMessage() . " (id " . $e->productId . ", kód " . $e->getCode() . ")\n";
}Řetězení výjimek
- nízkoúrovňovou výjimku (databáze, síť, JSON) zabalíme do výjimky s významem pro naši aplikaci
- původní výjimka zůstane dostupná přes
getPrevious()→ nepřijdeme o informaci, kde chyba skutečně vznikla
function loadConfig(string $json): array {
try {
return json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException("Konfiguraci nelze načíst", previous: $e);
}
}
try {
loadConfig('{"debug": tru}');
} catch (RuntimeException $e) {
echo $e->getMessage() . "\n"; // Konfiguraci nelze načíst
echo $e->getPrevious()->getMessage() . "\n"; // Syntax error
}Chyby (errors)
- starší mechanismus, používá ho většina vestavěných funkcí
- funkce zpravidla vypíše warning nebo notice a vrátí
false(příp.null), skript běží dál- např.
file_get_contents()neexistujícího souboru,fopen(),mkdir()
- např.
- od PHP 8 vestavěné funkce při špatných argumentech vyhazují
TypeError/ValueError; některé funkce mají přepínač, který výjimky zapnejson_decode()- příznakJSON_THROW_ON_ERRORPDO-PDO::ERRMODE_EXCEPTION(viz 7. cvičení)
- úrovně chyb:
E_ERROR,E_WARNING,E_NOTICE,E_DEPRECATED, uživatelskéE_USER_*(vyvolání funkcítrigger_error())
Konfigurační hodnoty
error_reporting- jaké chyby se budou oznamovat- konstanty E_* jako bitová maska
display_errors- zda se budou chyby vypisovat na (chybový) výstuplog_errors- záznam chyb (na produkci vhodnější neždisplay_errors)
Vlastní error handler
set_error_handler( callable $error_handler [, int $error_types = E_ALL ] )set_exception_handler()- zavolá se pro výjimku, kterou nezachytil žádnýcatch; poslední instance před ukončením skriptu (typicky zalogování a zobrazení chybové stránky)callable set_exception_handler ( callable $exception_handler )restore_error_handler(),restore_exception_handler()
Vlastní error handler
function myErrorHandler($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) {
// This error code is not included in error_reporting
// Return false to indicate "we did not handle this anyhow"
return false;
}
switch ($errno) {
case E_USER_ERROR:
...
}
}Převod chyb na výjimky
- obvyklý postup v moderních aplikacích: error handler každou chybu převede na výjimku
ErrorException - výsledkem je jednotné zpracování:
try/catchfunguje i pro warningy vestavěných funkcí - takto se chovají i frameworky a nástroje typu Tracy (viz 5. cvičení)
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
$content = file_get_contents('/neexistujici/soubor');
} catch (ErrorException $e) {
echo "Soubor nelze načíst: " . $e->getMessage() . "\n";
}Nezotavitelné chyby
nelze zpracovat vlastním error handlerem
- obvyklé -
E_ERROR,E_PARSE - speciální -
E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING