Na navigaci | Klávesové zkratky

Translate to English… Ins Deutsche übersetzen…

PHP triky: standardní výjimky

Kromě základní třídy Exception disponuje PHP počínaje verzí 5.1.0 celou škálou dalších předpřipravených výjimek. Než si tedy vytvoříte novou třídu, zkuste se podívat, jestli by vám nevyhovovala některá z těchto:

  • LogicException – předvídatelné již při návrhu programu
    • BadFunctionCallException – chyba při volání funkce; funkce nenalezena; volání nepovoleno
      • BadMethodCallException – totéž pro metody
    • InvalidArgumentException – špatný argument předaný funkci
    • OutOfRangeException – index mimo rozsah pole či kolekce
    • LengthException – hodnota překračuje povolenou délku
    • DomainException – hodnota nespadá do požadované domény, rozsahu
  • RuntimeException – zjistitelné pouze za běhu programu
    • OverflowException – přetečení bufferu či aritmetické operace; více dat než očekáváno
    • UnderflowException – podtečení bufferu či aritmetické operace; méně dat než očekáváno
    • OutOfBoundsException – index mimo rozsah pole či kolekce
    • RangeException – hodnota nespadá do požadovaného rozsahu
    • UnexpectedValueException – neočekávaná hodnota (např. návratová hodnota funkce)

Tyto výjimky nemají žádnou funkčnost navíc, jen tvoří předpřipravený strom identifikátorů. Sice jejich použití doporučuji, ale vadí mi „akademický“ duch, tj. že nejsou zvoleny prakticky. Rozdíl mezi OutOfBoundsException, OutOfRangeException a RangeException mi připadá mlhavý, zatímco nějaká ***NotFoundException docela chybí.

Jak chápat LogicException vs. RuntimeException?

LogicException představuje výjimečný stav, kterému můžeme předejít správným návrhem programu. Například InvalidArgumentException vyhodí funkce, která jako argument očekávala pole a dostala řetězec. Jde o signál pro programátora, že má v kódu chybu a proto je lepší je nechat probublat až nahoru a zalogovat. Takže k nim připojujte co nejvýstižnější zprávu, naopak specifická třída není podstatná.

Naproti tomu RuntimeException reprezentuje chybu zjistitelnou pouze za běhu programu. Příkladem může být otevření souboru, které skončí chybou ‚soubor nenalezen‘. I kdybychom existenci souboru ověřili, tak v mezičase před otevřením by mohl být smazán (viz atomické operace se soubory). Běhové výjimky mohou být opět adresovány člověku (neřešit, probublat, zalogovat), nebo naopak aplikaci samotné. V tom případě jejich zachycení představuje jakési „IF“, za kterým následuje kód řešící situaci. Pak používejte a odvozujte specifické třídy, naopak zpráva není klíčová.

Zend Framework – tak tudy ne, přátelé

Správně navrhnout hierarchii výjimek není legrace a příkladem slepé a možná i hluché uličky je třeba Zend Framework. Některé konvence tohoto jinak zdatného frameworku jsou nešťastné a hierarchie výjimek, která otrocky kopíruje strukturu tříd, je jednou z nich. Každá třída vyhazuje pouze výjimky s názvem {NAZEV_TRIDY}_Exception, ať už je důvod chyby jakýkoliv. Přitom třídu, která výjimku vyhodila, mohu zjistit z backtrace, zatímco pro zpracování nebo kaskádovité zachytávání mi její znalost příliš nepomůže.

Kupříkladu knihovna Zend_Mail vyhazuje při

  • volání dosud neimplementované metody,
  • užití nesprávného argumentu,
  • chybě při mazání e-mailu

výjimku Zend_Mail_Storage_Exception, jen s různým textem zprávy. Užitečnější by bylo vyhodit BadMethodCallException, InvalidArgumentException a teprve ve třetím případě vlastní Zend_Mail_Storage_Exception.

Komentáře

  1. ivan_d #1

    ‚Tyto výjimky nemají žádnou funkčnost navíc‘ – mají: zabírají na onom hnojišti jmeného prostoru místo. Ještě štěstí, že je dále nelze modifikovat

    před 9 lety
  2. jonson #2

    Mohl bych položit možná trochu amatérský dotaz (pokud jsem si toho nevšiml v textu článku, omluvte mne, je teprve 8:40).
    Existují knihovní funkce PHP (pro práci s poli, s řetězci, s deskriptory atd.), které by zmíněné nebo klidně i zatajené výjimky vyhazovaly? Nechci být nevěřící Tomáš…

    před 9 lety | reagoval [4] David Grudl
  3. Jakub Podhorský #3

    avatar

    musím s tebou dgx souhlasit že hiearchie výjimek v Zend Frameworku je opravdu nešťastná…snad na to jednou pánové přijdou ale to už může být pozdě na to to předělat

    před 9 lety | reagoval [5] Ronnie [6] Jan Tichý
  4. David Grudl http://davidgrudl.com #4

    avatar

    #2 jonsone, funkce (tj. nikoliv metody tříd) samozřejmě výjimky nevyhazují. Jednak nemá logiku, aby nonOOP část jazyka vyhazovala objektové výjimky (a žádné neobjektové tam nejsou). Ale především, není možné provést tak zásadní zásah do kompatibility, jakým by bylo vyhazování výjimek u existujících knihovních funkcí. Tím by přestal fungovat jakýkoliv kód. Docela žasnu, jak někdo může po takové úpravě volat.

    Naopak metody tříd, které se objevili v PHP 5 (tj. knihovna SPL) některé z těchto výjimek vyhazují. Například ArrayIterator vyhazuje OutOfBoundsException a RuntimeException.

    před 9 lety | reagoval [8] jonson
  5. Ronnie http://www.ronnieweb.net #5

    avatar

    #3 Jakube Podhorský, Problém by to být nemusel, stačí vytvořit potomka současné výjimky pro konkrétní případ, což by mělo být bez problémů zpětně kompatibilní.

    Je škoda, že tohle už dávno není v Zendu řešeno. Taky mi to vadí. Pak jsou v dokumentaci uváděny příklady typu

    } catch (Zend_Pdf_Exception $e) {
        if ($e->getMessage() == 'Can not open \'' . $argv[12196] . '\' file for reading.') {

    před 9 lety | reagoval [6] Jan Tichý
  6. Jan Tichý http://www.phpguru.cz/ #6

    avatar

    #3 Jakube Podhorský, Přesně tuhle výtku proti výjimkám v Zend Frameworku jsem už nedávno nadhodil v českém ZF fóru. Kupodivu jsem zjistil, že mezi tamním osazenstvem jsem byl se svým názorem spíš osamocený.

    #5 Ronnie, Ano, přes zpětně kompatibilní potomky to řešit lze, jenom se u některých výjimek (například typicky u Zend_Pdf_Exception) musí každému potomkovi natvrdo nastavit i jeho příslušný interní číselný status code.

    před 9 lety
  7. Dundee http://blog.milde.cz #7

    avatar

    O těchle výjimkách jsem nevěděl. Díky za dobrý podnět, píšu si to do todo fw :)

    před 9 lety
  8. jonson #8

    #4 Davide Grudle, Díky za odpověď, je fakt, že dotaz jsem položil nejapně ohledně knihovních funkcí, které objektové nejsou.

    Nicméně rád bych si nechal poradit: kdybych stál např. o práci se soubory a adresáři objektově a s výjimkami (soubor nelze otevrit pro zapis, neexistuje cesta, neni dost mista na disku …), měl bych použít třeba Pear nebo SPL?
    Zkrátka chtěl bych používat něco, kde bude kupříkladu otevřený soubor reprezentován instancí nějaké třídy, ta dává k dispozici metody pro čtení nebo zápis apod. a v případě neúspěchu akce bych nemusel testovat návratové hodnoty, ale mohl zachytávat výjimky. Ideálně aby takové knihovny byly součástí distribuce interpreteru nebo jeho snadno doinstalovatelným rozšířením.

    Zkusil jsem ArrayObject z SPL, chtěl jsem výjimku o neexistujícím klíči slovníku, ale nepřišel jsem na to:

    $x = new ArrayObject();
    $x[‚ahoj‘] = 124;
    var_dump($x[‚ahoj‘]);
    try {
    var_dump($x[‚vyskoc‘]);
    } catch (Exception $e) {
    echo(‚Heureka!‘);
    }

    před 9 lety | reagoval [9] David Grudl [10] jonson
  9. David Grudl http://davidgrudl.com #9

    avatar

    #8 jonsone, Objektový přístup k souborům řeší SplFileObject a SplFileInfo, přes adresáře je tu DirectoryIterator nebo RecursiveDirectoryIterator. S dokumentací je to u SPL tradičně slabší, takže doporučuji googlit. Sám jsem je nikdy nepoužil.

    Že by měl ArrayObject vyhazovat výjimku jsem v dokumentaci nenašel. Samozřejmě se dá taková vlastnost snadno přidat do potomka třídy.

    před 9 lety
  10. jonson #10

    #8 jonsone, Díky za tipy :) , progooglím a podívám se na ně.

    před 9 lety
  11. optik #11

    SPL snad jako jediné z core PHP vyhazuje výše uvedené výjimky a to proto, že implementace SPL ty výjimky obsahuje, pokud by nebylo SPL, nebudou tam asi ani ty výjimky.

    Jinak „zobjetovateď“ nějakou core součást, tak to se týká nejen SPL ale třeba také DateTime nebo ZIPu, uvidíme, jak to bude dál pokračovat.

    Jinak SPL je fakt pěkné, Marcus je maximalni borec, nicméně pokud vám jde o výkon, na SPL budete muset zapomenout, je to několikanásobně pomalejši než ekvivalentní procedurálni kód.

    SPL bude asi jen objektový wrapper napsaný v céčku (pěkný protimluv).

    před 9 lety
  12. fiso #12

    avatar

    Rád by som začal používať vyššie uvedené výnimky aj vo svojich kódoch, no zvykol som si vďaka svojmu frameworku na zápis výnimiek v asi takomto štýle:

    throw new EException('Item', $item, 'was not found');
    // co by teraz bolo asi
    throw new OutOfBoundsException('Item '.$item.' was not found');

    V čom je rozdiel? Pri renderovaní chybovej stránky mám to $item pekne zvýraznené (do <code>), kdežto ak by som používal OutOfBounds by sa mi hodnota $item opticky stratila v chybovej hláške. Je to blbina, ale moc som si na ňu zvykol. Ono by sa to dalo riešiť niečim ako spraviť si odvodený výnimku EOutOfBoundsException, ale to už nie je také elegantné. Nejaký nápad ako to inak riešiť, okrem prepísania php interpreta ;-)?

    před 9 lety
  13. ATom #13

    Mě na Zend Frameworku nejvíce vadí, ze z Exception vzniklé během SQL dotazu nejde zjistit samotný SQL dotaz. Z důvodu logování SQL chyb.

    před 9 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.