Chybová hlášení v PHP
Řekneme si něco o chybových hlášeních jak v PHP, tak v MySQL, nakonec se podíváme vlastní řešení chybových hlášení.
PHP se zpracovává na straně serveru a od toho se taky odvíhí způsob hlášení chyb, které vznikají při parsování kódu.
- parser errors
- Fatal Errors
- Warnings
- Notices
- | – nebo
- ~ – bez této hodnoty
- & – i tuto hodnotu
- mysql_errno – vrací číslo chyby, která byla způsobena v příkazu
- mysql_error – vrací chybové hlášení vyvolané příkazem
- E_USER_ERROR – vytvoří chybové hlášení s prioritou E_ERROR
- E_USER_WARNING – vytvoří chybové hlášení s prioritou E_WARNING
- E_USER_NOTICE – vytvoří chybové hlášení s prioritou E_NOTICE
- číslo chyby – je to bitová maska chyby
- zpráva – je to zpráva, kterou zasílá jako chybu
- soubor – soubor, ve kterém se chyba vyskytla
- řádek – číslo řádku na kterém se chyba nachází
- chyba je vytvořena pomocí trigger_error. Chyba je typu E_USER_ERROR, porto ji musíme nadefinovat jako E_WARNING, jinak by ji naše funkce zpracovala jako UNDEFINED.
- chyba je vytvořena pomocí funkce fopen, která se pokouší číst neexistující soubor.
- Notice chyba způsobená neexistující proměnnou.
- funkcemi fread a fclose. Soubor se nepodařilo otevřít, takže i tyto funkce budou vykazovat WARNING
Parse Error
Parse Errors, neboli chyby při čtení jsou většinou chyby syntaktické, které jsou odhaleny ještě před vyhodnocením skriptu. Parser před spuštěním celý skript analizuje a na takovou chybu ve většině případů upozorní.
Parser nás tím upozorní na skutečnost, že bez opravení chyby nemůže pokračovat a skript nebude vůbec spuštěn. Pak tedy například kód:
Objective-C123print 'a';print 'b'print "c";nevrací žádný výsledek, jenom parse error:
Objective-C12<b>Parse error: </b>parse error, unexpected T_PRINT in<b>C:wwwwww6ob.php</b> on line <b>5</b>čímž nás informuje o chybě na pátém řádku a celý skript nebude vůbec spuštěn.
Fatal error
Fatal errors, neboli závažné chyby jsou většinou chyby sémantické nebo chyby, které vznikají v prostředí:
Objective-C123print "a";show_sourse("ob.php");print "a";Po spuštění tohoto zdrojového kódu dojde k Fatal error chybě, neboť voláme funkci, která neexistuje. Na výstup dostaneme všechno co je správně a je voláno před chybou (toto závisí na verzi PHP), takže v tomto případě:
Objective-C123a<b>Fatal error: </b>Call to undefined function show_sourse() in<b>C:wwwwww6ob.php</b> on line <b>2</b>Nic z toho co je za chybovým hlášením se neprovede a skript bude zastaven až do opravení chyby.
Warnig
Warning, neboli varování o chybě.
Při varování se provádění skriptu nezastaví, ale parser se pokouší pokračovat v dalším provádění skriptu.
Varování vznikají v situaci, kdy chyba není natolik závažná, aby ovlivnila provádění celého skriptu. Jedná se většinou o chyby, které jsou ovlivněny prostředím. Takže například pokud nemůže najít daný soubor, který se pokoušíme otevřít nebo k němu nemá přístup, vzniká WARNING.
Pokud se navíc jedná o příkaz, který ovlivní další funkce, upozorní nás parser o všech těchto funkcích jako chybách. Může se jednat o otevření souboru, nebo o připojení k databázi:
Objective-C123$f = fopen("b.txt", "r");fread($f, $a);fclose($f);Tento kód způsobí tři varování (Run-time warnings). Nepodaří se otevřít soubor, to je varování první. Tato chyba ovšem ovlivní i čtení ze souboru, to je druhé varování. A navíc se soubor nemůže podařit zavřít, protože není otevřen a to je varování třetí:
Objective-C1234567891011<b>Warning:</b> fopen(b.txt) [function.fopen]: failedto open stream:No such file or directory in <b>C:wwwwww6ob.php</b>on line <b>2</b><b>Warning:</b> fread(): supplied argument is not avalid stream resource in<b>C:wwwwww6ob.php</b> on line <b>3</b><b>Warning:</b> fclose(): supplied argument is not avalid stream resource in <b>C:wwwwww6ob.php</b> on line <b>4</b>Notice
Notice, neboli upozornění se zobrazuje u méně závažných chyb, které parser většinou vyřeší po svém. Tato chybová hlášení jsou většinou vypnuta a nezobrazují se.
Klasickou ukázkou jsou neinicializované proměnné, se kterými se snažíme udělat nějakou operaci. Tyto proměnné jsou většinou přetypovány na nulovou hodnotu. Proto je tyto chyby těžké odhalit, neboť si je nemusíme všimnout. Skript je totiž vyhodnocen správně, jenom nepracuje tak jak se od něho očekává.
Objective-C1print (8 * $a);Pokud není nastaveno vypisování chyb Notice proběhne skript a vypíše 0. pokud je ovšem výpis povolen, vypíše se notice chyba, protože $a je v tomto případě nedefinovaná proměnná:
Objective-C12<b>Notice:</b> Undefined variable: a in<b>C:wwwwww6ob.php</b> on line <b>2</b>V případě notice chyby skript dále pokračuje a je vyhodnocován.
Error_reporting – úroveň chyb
V případě že potřebujete, aby se vám některá chybová hlášení vypisovala a jiná ne, můžete použít funkci error_reporting, která dokáže nastavit a ovlivnit způsob výpisu chybových hlášení. Toto je užitečné hlavně při ladění aplikace, kdy je nutné ošetřovat všechny chyby.
Funkce má jediný parametr a tím je level hodnota výpisu chyb, který se bude vypisovat. Tato hodnota může být číselná nebo zadána jako seznam chybových hlášení, která se mají vypisovat:
Bitová maska úroveň chyby popis 1 E_ERROR závažná chyba 2 E_WARNING varování před chybou 4 E_PARSE syntaktická chyba 8 E_NOTICE upozornění na chybu 2047 E_ALL
vztahuje se na všechny chyby tabulka 1: Hodnoty pro error reporting Konstanty a hodnoty uvedené v tabulce můžeme rozšířit ještě o další, které jsou pouze pro PHP4 a v PHP5 se již většinou nevyskytují:
Bitová maska úroveň chyby popis 16 E_CORE_ERROR závažné chyby při inicializaci PHP 32 E_CORE_WARNING varování při inicializaci PHP 64 E_COMPILE_ERROR závažné chyby při kompilování 128 E_COMPILE_WARNING varování při kompilování 256 E_USER_ERROR závažná chyba definovaná uživatelem 512 E_USER_WARNING varování na způsobenou chybu definovanou uživatelem 1024 E_USER_NOTICE uživatelem definovaná varování 2048 E_STRICT upozornění na chybu během spuštění aplikace tabulka 2: Další hodnoty pro error reporting S PHP4 se v dnešní době setkáváme stále ve velké míře, ale i tak je důležitá spíše první tabulka, kterou určitě využijete častěji.
Možností jak zapsat úroveň vypisování chyby, kterou chceme zobrazovat, je více. První a nejjednodušší způsob je výpisem úrovně chyby, které uvedeme jako seznam. Při tomto zadávání využíváme tří operátorů:
A v praxi potom zapisujeme přímo do zdrojového kódu většinou na začátek souboru takto:
Objective-C12error_reporting(E_NOTICE | E_WARNING & ~ E_PARSE);print (6 * $a);V takovémto případě uvedeme jako parametr všechny úrovně chybových hlášení, která chceme, nebo nechceme vypisovat včetně příslušných operátorů. Ostatní úrovně, které nejsou uvedeny se zobrazovat nebudou. Defaultní hodnota není nastavena na vypisování úrovně chyb E_NOTICE, pomocí tohoto způsobu jej jednoduše ovlivníte. Tento příklad vám určitě vypíše E_NOTICE chybu.
Další možností, která je asi nejvyužívanější je číselná hodnota uvedená jako level vypisování úrovně chyb. Tato hodnota se udává jenom jako celé číslo, které je součtem všech bitových masek úrovní, jež chceme vypisovat. Pokud bychom tedy chtěli nastavit zobrazování všech chybových hlášení, nastavili bychom hodnotu na číslo 15 (E_ERROR – 1, E_WARNING – 2, E_PARSE – 4, E_NOTICE – 8 => 1 + 2 + 4 + 8 = 15).
Objective-C12error_reporting(15);print (6 * $a);V tomto případě se nám zase vypíše i NOTICE, neboť jsme natavili vypisování všech chybových hlášení.
Při číselném vyjádření můžeme zase využívat operátorů, které umístíme mezi číselné hodnoty masek. Pokud bychom chtěli dosáhnout toho, aby se nevypisovali žádná chybová hlášení, nastavíme hodnotu na nulovou:
Objective-C1error_reporting(0);Toto je při ladění aplikace ovšem velmi nebezpečné, neboť nebudete informováni o žádné chybě, kterou skript způsobí a tím pádem lehce přehlídnete i zásadní chybu, která se bude bez hlášení jenom těžko hledat. Naopak výhodné to může být na serveru, kde máte umístěn web v ostrém provozu, protože když se nezobrazují chybová hlášení, je menší riziko útoku, neboť útočník snadněji přehlídne zranitelnost.
Výchozí hodnota je u error_reporting nastavena na hodnotu sedm (error_reporting(7)), což způsobuje právě zobrazování pouze E_PARSE, E_WARNING, E_ERROR. Defaultní hodnotu dostaneme taktéž zavoláním funkce bez parametru (error_reporting()).
Poslední způsob jak vypsat parametr je kombinací předešlých dvou způsobů, kdy můžeme kombinovat bitové masky s názvem úrovně chyby a využívat k němu operátorů.
Objective-C12345678//vypisuje jenom cyhbová hlášení typu NOTICE a WARNINGerror_reporting(E_NOTICE | E_WARNING & ~ E_PARSE);//vypisuje všechna chybová hlášeníerror_reporting(1 | 2 | 4 | 8);//vypisuje ERROR a PARSEerror_reporting(E_PARSE | 1);//vypisuje všechny kromně WARNING a NOTICEerror_reporting(E_ALL & ~ E_WARNING & ~ E_NOTICE);Potlačení chybových hlášení
Při práci se vám určitě stalo, že jste chtěli zabezpečit nějakou funkci, ale ona pořád hlásí chybu, i když se snažíte jak snažíte. Typická ukázka může být otevření souboru, který neexistuje, nebo připojení k databázi, které se nezdaří:
Objective-C12345if( ($f = fopen("file.txt", "r")) == FALSE)print 'soubor se nepodarilo otevrit';if( mysql_connect("db", "db", "db") == FALSE)print 'spojeni s databazi se nezdarilo';Na první pohled vypadá že je vše ošetřeno a nemůže nastat žádná chyba. Ovšem, pokud soubor neexistuje, bude parser neustále hlásit chybu i když je celé otevírání v podmínce if. Chyba bude typu WARNING a na výstupu dostaneme:
Objective-C12345678<b>Warning: </b>fopen(file.txt) [function.fopen]:failed to open stream: No such file or directory in<b>C:wwwwwwpokusob.php</b> on line <b>3</b>soubor se nepodarilo otevrit<b>Warning: </b>mysql_connect() [function.mysql-connect]:Unknown MySQL Server Host 'db' (11001) in<b>C:wwwwwwpokusob.php</b> on line <b>6</b>spojeni s databazi se nezdariloPrvním způsobem, jak dosáhnout aby se hlášení o chybách nevypisovaly je nastavit pomocí nám již známého error_reporting úrovně výpisu:
Objective-C1error_reporting(E_ALL & ~ E_WARNING);ale tento způsob je dost nepraktický, neboť se potom vztahuje na celý skript a hlavně v průběhu ladění, kde chceme aby se nám vypisovali všechna chybová hlášení bychom museli hodnotu měnit.
Naštěstí nám PHP nabízí řešení ve tvaru potlačení chybových hlášení. Řešení spočívá v operátoru “@”. Zavináč potom stačí umístit na začátek řádku, kde nám parser vypisuje chybová hlášení a jeho výpis bude potlačen:
Objective-C1234if( (@$f = fopen("file.txt", "r")) == FALSE)print 'soubor se nepodarilo otevrit';if( @mysql_connect("db", "db", "db") == FALSE)print 'spojeni s databazi se nezdarilo';V případě neúspěchu se nyní vypíše pouze text uvedený v print. To ale neznamená že funkce i v případě úspěchu vrací FALSE hodnotu. Opak je pravdou. Funkce vrací stejnou hodnotu, jako kdyby řádek neobsahoval operátor zavináče.
Časté chyby
V PHP se objevují chyby, kterých se dopouští snad každý začínající programátor a většinou neví jak chybu odstranit. První z těchto častých chyb je odesílání header informací pomocí funkce Leader ze špatného místa scriptu. Při práci s touto funkcí jste se určitě někdy setkali s hlášením typu:
Objective-C1234<b>Warning: </b>Cannot modify header information- headers already sent by (output started atC:wwwwwwpokusob.php:2) in<b>C:wwwwww6ob.php</b> on line <b>3</b>Parser nám tím sděluje, že nemůže modifikovat Header informace. Header informace se totiž musí odesílat před jakýmkoli výstupem stránky. Pokud se této chyby chcete vyvarovat, musíte zabránit jakémukoli výstupu. Tyto chyby často vznikají při includování, kdy do skriptu jednoduše vložíte kód, který již něco vypisuje. Bohužel jako výstup se zde myslí i prázdné znaky jako je mezera, nebo tabulátor, takže je nutné se vyvarovat všecho.
Poslední chybové hlášení, které si rozebereme a se kterým jste se již v praxi určitě setkali je:
Objective-C12<b>Fatal error:</b> Maximum execution time of 30seconds exceeded in <b>C:wwwwww6ob.php</b> on line <b>5</b>Chyba nastává v případě, že je doba vykonáváni skriptu delší než je na serveru nastaveno – v našem případě 30 sekund, což je většinou nastaveno na všech serverech. Delší doby můžeme dosáhnout velmi snadno a proto, když si budeme vědomi, že skript se může provádět déle, musíme tomuto zabránit. PHP nám zase nabízí řešení v podobě funkce set_time_limit. Pomocí této funkce můžeme ovlivnit dobu provádění skriptu. Jako parametr funkce se udává čas v sekundách:
Objective-C12//nastavíme dobu na 200 sekundset_time_limit(200);Chybová hlášení v MySQL
Stejně jako nás webový server dokáže informovat o chybách, tak dokáže informovat o chybách i databázový server. Jedná se většinou o chyby, které vznikají při provádění příkazu na databázi a z nějakého důvodu se nemohou vykonat.
Chyb, proč se příkaz neprovede, může být celá spousta a pokud chceme znát původ chyby, je pro nás nejjednodušší si nechat vypsat chybové hlášení. Databázový server MySQL má, stejně jako webový server Apache, přehledně zpracována chybová hlášení a můžeme z nich většinou na první pohled poznat důvod, nebo příčinu chyby.
Pro výpis chybových hlášení z MySQL databáze můžeme v PHP využít dvou příkazů, které PHP podporuje již od verze PHP 3:
Při používání těchto dvou funkcí je důležité si uvědomit, že funkce vrací chybové zprávy vztahující se jenom k jednomu, poslednímu vykonanému příkazu na databázi. Při použití musíme tedy kontrolovat obsah chybové zprávy vždy před voláním dalšího příkazu.
V praxi potom celá operace výpisu chybových zpráv může vypadat:
Objective-C12345function select_n(){$select = "SELECT nazev FROM neexistujici_tabulka WHERE 1;";if(mysql_query($select, $this->link) == FALSE)print 'Num:' . mysql_errno() . ' Message: ' . mysql_error() . '<br />';}V dotazu na databázi se pokusíme vybrat data. Bohužel se nám stalo, že jsme špatně napsali název tabulky a tabulka neexistuje. Pokud bychom si nenechali vypsat chybové hlášení, těžko bychom hledali proč se dotaz nevykonal správně, hlavně jednalo by se o nepatrný překlep, který není na první pohled vidět.
V tomto případě nám dá databázový server přesně vědět co se stalo a proč se dotaz nevykonal:
Objective-C12Num:1146 Message: Table 'data.neexistujici_tabulka'doesn't exitZ chybového hlášení mysql_error() je na první pohled jasné co se stalo. Tabulka neexistující_tabulka v databázi data neexistuje. Nyní není problém dotaz opravit bez zbytečného hledání chyby v PHP kódu.
Všimněte si, že v případě chybného dotazu na databázi není vyvoláno v PHP žádné chybové hlášení, ba dokonce ani WARNING, proto je nutné aby sám programátor tyto zbytečné chyby hlídal a ošetřoval.
Vlastní řešení chybových hlášení
Při práci můžeme narazit na situaci, kdy bychom chtěli chybu ošetřit jinak, než ji ošetří parser. I tuto možnost PHP nabízí v podobě několika funkcí:
trigger_error
Funkce trigger_error slouží k vytvoření vlastní chyby, kterou by parser neošetřil. Může se nám stát, že za určitě situace chceme mít kód pod kontrolou takovým způsobem, že vytvoříme vlastní chybová hlášení při nesplnění nějaké podmínky. Při vytváření chybového hlášení máme k dispozici ze tří úrovní:
Další parametr, který musí funkce obsahovat je chybové hlášení, jež se bude vypisovat na výstupu.
V praxi můžeme tuto funkci použít podobně tomuto příkladu:Objective-C12if(!isset($a))trigger_error('Zapoměl jsi definovat proměnnou $a <br />', E_USER_NOTICE);Jestliže nebude definována proměnná $a, dostaneme jako výstup chybové hlášení s prioritou NOTICE:
Objective-C12<b>Notice:</b> Zapoměl jsi definovat proměnnou $ain <b>C:wwwwwwpokusob.php</b> on line <b>3</b>set_error_handler
Další funkcí, kterou můžeme ovlivňovat způsob výpisu chybových hlášení je funkce set_error_handler. Funkce umožňuje úplně měnit chybová hlášení, která parser produkuje jako výstup. Funkce umí tato chybová hlášení zpracovat podle přání programátora.
Použití je velmi jednoduché, vytvoříme si funkci, která bude naše chybová hlášení zpracovávat, a aplikujeme ji pomocí set_error_handler:
Objective-C1$error_handler = set_error_handler("MojeOsetreniChyb");Pokud je funkce nastavena, předávají se jí čtyři parametry:
Nyní již stačí vytvořit funkci MojeOsetreniChyb a nechat ji zpracovávat chybová hlášení:
Objective-C1234567891011121314151617181920212223242526272829303132333435363738394041424344//nastavíme error_reporting, abychom měli přehled o všech chybových hlášenícherror_reporting (E_FATAL | E_ERROR | E_WARNING | E_NOTICE);//a vytvoříme funkci pro zpracování chybových hlášenífunction MojeOsetreniChyb ($error_num, $error_msg, $error_file, $error_line){print '<span style="color:red">';<?php//nastavíme error_reporting, abychom měli přehled o všech chybových hlášenícherror_reporting (E_FATAL | E_ERROR | E_WARNING | E_NOTICE);//a vytvoříme funkci pro zpracování chybových hlášenífunction MojeOsetreniChyb ($error_num, $error_msg, $error_file, $error_line) {print '<span style="color:red">';//v proměnné error_num je bitová maska chybyswitch( $error_num ){case 1:print 'ERROR: Nastala chyba"' . $error_msg . '" na radku: "' . $error_line . '"<br /> v souboru: "' . $error_file . '"<br />';//vypisujeme: číslo chyby, řádek na kterém chyba nastala a soubor, ve kterém chyba nastalabreak;case 2:print 'WARNING: Nastala chyba"' . $error_msg . '" na radku: "' . $error_line . '"<br /> v souboru: "'. $error_file . '"<br />';break;case 8:print 'NOTICE: Nastala chyba"' . $error_msg . '" na radku: "' . $error_line . '"<br /> v souboru: "' . $error_file . '"<br />';break;//ještě můžeme nastavit defaultní hodnotudefault: ;print 'UNDEFINED: Nastala chyba"' . $error_msg . '" na radku: "' . $error_line . '"<br /> v souboru: "' . $error_file . '"<br />';}print '</span>';}//pomocí funkce set_error_handler natavíme funkci ve které//se budou chybová hlášení zpracovávat$error_handler = set_error_handler("MojeOsetreniChyb");//můžeme vytvořit tigger_error, který naše funkce zpracujedefine(E_WARNING, "E_USER_WARNING");trigger_error("Chyba vytvorena pomoci trigger_error", E_USER_WARNING);//a dále úmyslně vytvoříme další chyby:$f = fopen("b.txt", "r");fread($f, $a);fclose($f);?>Pokud se podíváte na konec skriptu, udělali jsme úmyslně několik chyb:
Na výstupu dostaneme:
Objective-C1234567891011121314151617WARNING: Nastala chyba"Invalid error type specified"na radku: "34"v souboru: "C:wwwwwwpokusob.php"WARNING: Nastala chyba"fopen(b.txt) [function.fopen]:failed to open stream: No such file or directory"na radku: "37"v souboru: "C:wwwwwwpokusob.php"NOTICE: Nastala chyba"Undefined variable: a"na radku: "38"v souboru: "C:wwwwwwpokusob.php"WARNING: Nastala chyba"fread(): suppliedargument is not a valid stream resource"na radku: "38"v souboru: "C:wwwwwwpokusob.php"WARNING: Nastala chyba"fclose(): supplied argumentis not a valid stream resource" na radku: "39"v souboru: "C:wwwwwwpokusob.php"
doporucuji na jeste lepsi chyby nainstalovat na ladeni xdebug
Dobrá práce, takovýhle přehled se šikne, když člověk přemýšlí nad chybou.