PHP: Vlastní error handler

Sun, 14 Feb 2010 21:02:57 +1100

Je tomu již více jak tři roky, kdy můj internetový deníček střídavě funguje a nefunguje. Je proto nejvyšší čas rozdělit články v kategorii PHP na dvě další dle obtížnosti. Proto bych rád kategorii PHP pro pokročilé otevřel článkem, ve kterém ukážu jak na chyby se vším všudy.

Jelikož už se se standardními zprávami typu "Fatal error: Call to undefined function..." nespokojíme, je potřeba si rozšířit znalosti o některé funkce a předdefinované třídy, jenž nám ulehčí vývoj aplikací.

Prosím berte v potaz, že článek je z roku 2010. Hodně se za tu dobu změnilo, a tak návod nemusí odpovídat dnešním standardům. Ukázka ale funguje a můžete ji využít jako první krok k lepšímu porozumnění PHP.

Chyby, které mohou v aplikaci nastat bych rád rozdělil na 4 skupiny: podrobně se budu věnovat prvním dvěma z nich, o těch dalších dvou třeba v pokračování. Půjde o následující skupiny:

1. Warning, notice, strict

2. Fatal a parse error

3. Database error

4. Exceptions: uměle vyvolané výjimky

Warning, notice, strict

Návod jak si "vytunit" chybová hlášení typu warning a notice je přímo uvedené i v oficiálním manuálu. Co už se ale nedočtete, je, že na fatální a syntaktické chyby je toto řešení krátké. Pokud totiž taková chyba vznikne, běh skriptu se okamžitě ukončí a server rozhodně nemá náladu na volání nějakých námi definovaných funkci.

Zpracování těchto chyb totiž spočívá v deklaraci vlastní funkce, kterou pomocí set_error_handler nastavíme jako primární ovladač chyb. Na co je dále nutné myslet je, že při použití set_error_handler přestane bez výjimky fungovat zavináčový hack. Ač od toho bývá spousta začátečníků odrazována, jedná se o výbornou věc... Tomu se ale dnes věnovat nebudu.

V následující ukázce kódu zpracujeme všechny chyby typu warning, notice, strict a jim podobné. Ukázka předpokládá, že všechna chybová hlášení jsou zapnutá jak přes display_errors, tak přes error_reporting.

function user_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
	static $type = array(
		E_ERROR => 'Fatal Error',
		E_PARSE => 'Parse Error',
		E_RECOVERABLE_ERROR => 'Recoverable Error',
		E_WARNING => 'Warning',
		E_NOTICE => 'Notice',
		E_CORE_ERROR => 'Core Error',
		E_CORE_WARNING => 'Core Warning',
		E_COMPILE_ERROR => 'Compile Error',
		E_COMPILE_WARNING => 'Compile Warning',
		E_USER_ERROR => 'User Error',
		E_USER_WARNING => 'User Warning',
		E_USER_NOTICE => 'User Notice',
		E_STRICT => 'Runtime Notice',
		E_ALL => 'All',
	);

	/**
	 * ošetření zavináčového hacku
	 * když ho použijeme, ani email nedojde
	 **/
	if (!ini_get('error_reporting')) {
		return true;
	}

	$e = new Exception();

$msg_begin = '' . $type[$errno] . ': ' . $errstr . ' in ' . $errfile . ':' . $errline . "\n";

	$msg_email = $msg_begin . "\nTrace:\n" . $e->getTraceAsString();
	$msg_print = $msg_begin . $e->getTraceAsString();

	$notif = user_error_notification(strip_tags($msg_email), $type[$errno]);

	if (!empty($GLOBALS['config']['error']['enable'])) {
echo ' ';
echo $msg_print . ' ' . $notif;
echo ' ';
	}
}

set_error_handler('user_error_handler');

Všimněte si podmínky uprostřed, která testuje ini_get('error_reporting'). Jedná o ošetření již zmiňovaného zavináčového hacku, aby se choval stejně jako před tím. Dále je vytvořena instance třídy Exception. Ta se postará o naši kompletní informovanost, co všechno chybě předcházelo.

Před vytištěním znění chyby ještě zavoláme ukázkovou funkci (kterou jsem pro příklad pojmenoval user_error_notification. Ta pošle e-mail, kde a kdy k chybě došlo. Vrátit může například zprávu, zda-li se e-mail povedlo či nepovedlo odeslat.

Ve finále podmiňuji globální proměnnou $config['error']['enable']. V té je nastavena boolean hodnota, díky které se chyba zobrazí či nezobrazí. To kvůli přechodu z vývojového na ostré prostředí, abychom návštěvníkům zbytečně nezobrazovali nesrozumitelná hlášení. Žádné vypínání error_reporting, ale efektivní řešení, kdy my jakožto vývojáři jsme informovaní o chybě, ale návštěvník nikoli.

Fatal & parse error

Jak jsem již zmiňoval, na tyto chyby je potřeba úplně jiným metrem. Existují dvě krásné direktivy, které pro náš účel použijeme. Půjde o parametry error_prepend_string a error_append_string, které jsou argumenty jak jinak než funkce ini_set. Do každé z nich vložíme část HTML kódu, který vytvoří pěkně nastylovanou stránku. Pomocí JavaScriptu skryjeme skutečné hlášení a uživateli zobrazíme pouze omluvnou zprávu.

E-mailové notifikace také nebudou obtížné: stačí přidat Ajaxový request na skript, který z GET proměnné vytáhne znění chyby a z HTTP_REFERERA url, kde se chyba stala. Použití třídy Exception zde samozřejmě odpadá.

$err_begin_pattern = <<<HTML





		/* CSS definice */

FATAL ERROR


FATAL ERROR
	%MSG%

HTML;

$fatal_error_begin = strtr($err_begin_pattern, array(
	'%MSG%' => $GLOBALS['config']['error']['alt_message']
));

$err_end_pattern = <<<HTML



//
		var httpRequest = null;
		var sendRequest = %SEND%;
		var sendUrl = '%AJAX%';
		/**
		 * zde zavoláme Ajaxem externí skript
		 * znění chyby vytáhneme ze span#fatal_error
		 **/
//



HTML;

$fatal_error_end = strtr($err_end_pattern, array(
	'%SEND%' => $GLOBALS['config']['error']['email_send'],
	'%AJAX%' => $GLOBALS['config']['error']['ajax_url'],
));

ini_set('error_prepend_string', $fatal_error_begin);
ini_set('error_append_string', $fatal_error_end);

Jak je vidět ve výše uvedené ukázce, opět využijeme nastavení v globálních proměnných. Tam nastavíme alternativní chybovou hlášku, url, kam se bude posílat request a samotné zapnutí / vypnutí notifikačních e-mailů.

Nakonec to zase tak složité nebylo. Na co je ovšem opravdu nutné myslet, je, ošetření samotného error handleru. V daném skriptů totiž nesmí dojít k sebemenší chybičce. Pokud totiž nastane chyba ve funkci, která se o chyby má starat, dojde k zacyklení a nekonečnému volání sebe sama.