OOP v PHP: Výjimky v OOP

15. února 2006

PHP 5, narozdíl od předešlých verzí, umožňuje využívání výjimek stejně jako ostatní programovací jazyky. Výjimky rozšiřují možnosti ošetřování vzniklých chyb v PHP.

Výjimky jsou objekty. Pro jednodušší práci je v PHP zabudovaná třída Exception, která je navržena speciálně pro výjimky. Výjimka může být hozena (thrown), ošetřena (try) a chycena (catch). Blok try (to, čím výjimku vyhazujete) musí obsahovat alespoň jeden blok catch (to, čím výjimku zachycujete).

Jak to všechno probíhá?

Nejprve je výjimka vyhozena, objekt Exception je zachycen a provádění kódu se zastaví. Cokoli následuje dále, se neprovede. Poté se začne provádět blok, který výjimky zachytává (catch). Pokud není v daném bloku žádný kód pro zpracování výjimky, hledá se blok catch v bloku kódu volajícího. To se opakuje tak dlouho, dokud není nalezen kód pro zpracování výjimky nebo dokud není dosažen vrchol v hierarchii volání. Výjimka může být vyhozena i uvnitř catch bloku. Nezachycená výjimka je fatální chyba!

Ukázka:

<?php 

try { 
    $zprava = 'Inteligentní zpráva'; 
    throw new Exception($zprava); 

    // Tohle už se nevykoná 
    echo 'Ja jsem zpráva, kterou nikdo nepřečte'; 

} catch (Exception $e) { 

    echo 'Výjimka zachycena: '. $e->getMessage(); 

} 
?>

Třída exception

PHP má už přednadstavenou třídu Exception, která má definované své přednadstavené metody a vlastnosti. Zjednodušeně vypadá asi takto:

<?php 
class Exception{ 
    protected $message = 'Unknown exception'; // zpráva výjimky 
    protected $code = 0; // kód výjimky 
    protected $file; // soubor, kde byla výjimka zachycena 
    protected $line; // řádek, kde byla výjimka zachycena 

    function __construct($message = null, $code = 0); 
    final function getMessage(); // zpráva výjimky 
    final function getCode(); // kód výjimky 
    final function getFile(); // soubor 
    final function getLine(); // řádek 

    function __toString(); // formátování řetězce pro výstup 
} 
?>

Hierarchie výjimek

Jak už bylo napsáno dříve, můžeme samozřejmě používat více bloků catch na jeden try. S náročnějšími skripty se zvyšuje i počet možných vyhozených výjimek a tím i riziko, že některou nezachytíme. Proto je dobré používat nějakou hierarchii výjimek.

<?php 
class zdedenaVyjimka extends Exception {} 
class dalsiZdedenaVyjimka extends zdedenaVyjimka {} 
class jesteJedna extends dalsiZdedenaVyjimka {} 

function faktorial($n){ 
  if($n < 0){ 
    throw new zdedenaVyjimka; 
  }else if($n == 0 || $n == 1){ 
    return $n; 
  }else if($n > 170){ 
    throw new jesteJedna; 
  }else{ 
    return $n * faktorial($n - 1); 
  } 

} 

try { 

  $n = 5; 
  $faktorial = faktorial($n); 
  echo "$n = $faktorial"; 

} 
catch(dalsiZdedenaVyjimka $e){ 
    echo "Příliš velká hodnota"; 
} 
catch(zdedenaVyjimka $e){ 
    echo "Špatná hodnota"; 
} 

catch (Exception $e){ 
    echo "Vyskytla se jiná chyba"; 
} 

?>

Nejprve jsou definovány tři třídy (budou fungovat jako výjimky). Jedná se o dědičný strom, druhá dělí vlastnosti první, třetí vlastnosti druhé a tak dále.

Pokud bude proměnná $n menší než nula, pak bude vyhozena zdedenaVyjimka, která bude zachycena druhým blokem catch a vypíše se hodnota „Špatná hodnota“. Pokud bude proměnná $n větší než nula, pak bude vyhozena výjimka jesteJedna. Neexistuje však žádný blok catch, který by ji dokázal zachytit, skript tedy začne hledat rodiče této výjimky. Tím je dalsiZdedenaVyjimka a pro tuto výjimku už blok catch existuje, vypíše se hodnota „Příliš velká hodnota“. Nakonec je definován blok catch pro výjimku Exception. Ten se vykoná pouze tehdy, byla-li vyhozena výjimka, pro kterou neexistuje žádný block catch (tato výjimka však musí být vzdáleným potomkem Exception).

Jednoduše řečeno, pokud PHP nenajde catch blok pro nějakou výjimku, půjde tak dlouho po dědičném stromě, dokud nedosáhne nějaké výjimky, pro kterou je blok catch definován. Tím v podstatě vylučujeme šanci, že by se vyskytla nějaká chyba, kterou nemůžeme zpracovat. Pokud bude vyhozena nějaká nepředpokládaná výjimka, zpracuje ji poslední blok.

Řazení výjimek do kaskády

V PHP existuje možnost vyhazovat výjimky i uvnitř bloku catch. To se může hodit tehdy, pokud chceme zachycenou výjimku zpracovat a předat dále. Třeba dojde-li k chybě v MySQL, skript vyhodí výjimku, kterou zachytí nějaký blok catch. Nějak výjimku zpracuje a podle jejího typu ji pošle dalšímu bloku catch, který si s daným typem chyby poradí lépe. Například George Schlossnagle uvádí tento kód (MysqlException je potomkem Exception):

class MysqlException { 

  //... 
  static function createError($message=false, $code=false){ 
    if(!$code) { 
      $code = mysql_errno(); 
    } 
    if(!$message) { 
      $message = mysql_error(); 
    } 
    swich($code) { 
      case 1062: 
        return new Mysql_Dup_On_Index($message, $code); 
        break; 
      default: 
        return new Mysql_Extension($message, $code); 
        break; 
    } 

}

Pak voláte pouze MysqlException::createError();…

Velmi elegantně můžete zpracovávat chyby v konstruktoru. Například takto:

<?php 

class ToBudeChyba{ 
  public function __construct(){ 
    throw new Exception; 
  } 
  public function __destruct(){ 
    echo "Konec"; 
  } 
} 

try{ 
  $i = new ToBudeChyba; 
} 

catch(Exception $e) {} 

?>

Destruktor se nevykoná, uvnitř konstruktoru byla vyhozena výjimka, objekt reálně neexistuje, dokud neproběhne celý konstruktor.

Funkce pro zpracování výjimek

PHP umožňuje použití několika funkcí, které jsou určeny pro výjimky.

set_exception_handler();

Funkce set_exception_handler() umožňuje nastavit defaultní funkci, která bude volána, pokud výjimka nebude zachycena. Obsahuje pouze jeden parametr, a to právě jméno funkce, která bude volána (tato funkce musí být v kódu uvedena dříve než set_exception_handler()).

<?php 
function exception_handler($exception) { 
  echo "Zpráva výjimky: " , $exception->getMessage(); 
} 

set_exception_handler('exception_handler'); 

throw new Exception('Nezachycená výjimka'); 
?>

Jak jste si všimli, vyhazovat výjimku můžeme i mimo blok try. Za výjimku můžeme vložit nějaké hodnoty, zprávu výjimky a kód.

<?php 
function exception_handler($exception) { 
  echo "Zpráva výjimky: " , $exception->getMessage(); 
  echo "Kód výjimky: " , $exception->getCode(); 
} 

$funkce = set_exception_handler('exception_handler'); 

throw new Exception('Nějaká zpráva',50); 
?>

Pokud funkci set_exception_handler() přiřadíme nějakou proměnnou (v předchozím případě $funkce), má to i jisté výhody. Uživatelsky definované funkce jsou uloženy v zásobníku, takže můžete obnovit starou funkci buď uložením další kopie staré funkce do zásobníku…

set_exception_handler($funkce);

…nebo vyzvednutím ze zásobníku takto:

restore_exception_handler();

Pozn. red.: Zdůrazňujeme, že tento text se týká PHP 5.

Štítky: oop-php

Mohlo by vás také zajímat

Nejnovější

2 komentářů

  1. rePhat

    Zář 25, 2014 v 13:52

    Narazil jsem na malou chybku pod zdrojovým kódem u „Hierarchie výjimek“, kde v druhém odstavci stojí: „Pokud bude proměnná $n větší než nula, pak bude vyhozena výjimka jesteJedna“. Myslím že tam mělo být $n větší než 170.

    Odpovědět
  2. miro

    Pro 7, 2016 v 16:20

    máte u nastavené display:block;, tzn každý token v příkladech kódu je na vlastním řádku - musel jsem si to vypnout, ať se ten kód dá rozumně přečíst.

    Odpovědět

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *