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
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
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áš…
Jakub Podhorský #3
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
David Grudl #4
#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.
Ronnie #5
#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
…
…
Jan Tichý #6
#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.
Dundee #7
O těchle výjimkách jsem nevěděl. Díky za dobrý podnět, píšu si to do todo fw :)
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!‘);
}
David Grudl #9
#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.
jonson #10
#8 jonsone, Díky za tipy :) , progooglím a podívám se na ně.
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).
fiso #12
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:
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 😉?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.
Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.