Błędy zdarzają się wszędzie i każdemu, natomiast raczej nie jest wskazane „chwalenie się” nimi przed użytkownikami aplikacji.
Konfiguracja
Podstawowyą kwestią jest ich ukrycie na środowisku produkcyjnym przed oczami użytkownika, tym bardziej że w stosie wywołań mogą się pojawić wrażliwe dane użytkownika/aplikacji/systemu/inne. Do ukrycia „wpadki” używa się właściwości display_errors
(php.ini
). Właściwość pozwala włączyć lub wyłączyć pokazywanie błędów oraz skierować je na odpowiednie wyjście:
Off
– wyłączenie pokazywania błędów (do użycia na „produkcji”)
stderr
– przekierowanie błędów na STDERR
(działa tylko z CGI/CLI)
On
/stdout
– pokazanie błędów/przekierownie na standardowe wyjście, czyli pokazanie komunikatów w treści strony wyświetlonej użytkownikowi (przydatne w trakcie programowania)
Jednak ukrycie komunikatów błędów w żaden sposób nie rozwiązuje faktycznego problemu w aplikacji. Do tego potrzebne są komunikaty błędów i to tych znaczących. Do filtrowania komunikatów służy dyrektywa error_reporting
. Jest to poziom raportowania błędów, które będą wyświetlane/pokazywane – możliwych wartości jest cała masa, a dodatkowo można je łączyć – lista stałych
.
Obie dyrektywy (w zależności od uprawnień czy konfiguracji środowiska) mogą zostać ustawione na poziomie pliku php.ini
, definicji vhosta, pliku .htaccess
, czy w samym kodzie PHP. Ostatni wariant wygląda następująco:
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
var_dump(ini_get('display_errors')); // pokaże: string '1' (length=1)
var_dump(ini_get('error_reporting')); // pokaże: string '22519' (length=5)
W PHP wartości dyrektyw można zmieniać i odczytywać funkcjami ini_set()
/ ini_get()
. Dla dyrektywy error_reporting
jest dedykowana funkcja – wywołana z argumentem zmienia wartość, natomiast bez argumentu zwraca obecną wartość, nie zmienia to jednak faktu, że można używać standardowych funkcji. Oczywiście nie wszystkie dyrektywy można zmieniać w powyższy sposób – tutaj znajduje się lista dyrektyw z oznaczeniem sposobu zmiany wartości i opis poziomów.
Wywołanie błędu
<?php
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Do wywołania błędu z poziomu kodu służy funkcja trigger_error()
– pierwszy argument to własny komunikat, natomiast drugi to typ błędu. W zależności od tego jak bardzo błąd jest poważny wykonywanie skryptu jest kontynuowane lub przerywane, co widać poniżej w wyniku wykonania powyższego kodu:
punkt 1
Notice: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 12
Call Stack
# Time Memory Function Location
1 0.0001 230360 {main}( ) ../error.php:0
2 0.0002 231520 trigger_error ( ) ../error.php:12
punkt 2
Fatal error: Test wywolania bledu in /home/cim/public_html/Testy/error.php on line 14
Call Stack
# Time Memory Function Location
1 0.0001 230360 {main}( ) ../error.php:0
2 0.0003 231520 trigger_error ( ) ../error.php:14
Po wystąpieniu błedu typu „Notice” wykonywanie przebiega dalej (np. pokazanie „punkt 2”), natomiast błąd typu „Fatal error” przerywa wykonanie (brak wyświetlenia „punkt 3”).
Przechwytywanie
Błąd został zgłoszony i przy odpowiedniej konfiguracji został wyświetlony na ekranie. Docelowo jednak efekt ma być zupełnie inny, błędy powinny być zgłaszane do odpowiednich osób. PHP posiada przydatną funkcję – error_log
– która wspomaga wysyłanie e-maili, dodawnie komuniaktów błędów do systemowego logu, tworzenie własnego logu. Jednak można również samemu wysyłać e-maile (poprzez serwer SMTP) czy zapisywać do pliku.
W obu przypadkach należy w jakiś sposób przechwycić informację o wystąpieniu błędu. Realizuje się to poprzez utworzenie własnej funkcji i zarejestrowanie jej za pomocą set_error_handler()
.
<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
function obsluga($kodBledu, $komunikat, $plik, $linia, $zmienne) {
$elementy = array(
date('H:i:s'),
$kodBledu,
$komunikat,
$plik,
$linia,
//print_r($zmienne, true)
);
error_log(implode(LOG_SEPARATOR, $elementy) . PHP_EOL, 3, 'logi_'. date('Ymd') .'.log');
}
set_error_handler('obsluga');
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Powyższy kod spowoduje, że na ekranie zostanie wyświetlone tylko: „punkt 1, punkt 2, punkt 3”. Natomiast zarejestrowana funkcja przechwytująca informację o błędzie obsłuży w sposób „cichy” testowe wywołania błędów. Do funkcji przekazywane są takie informacje jak: poziom błędu, komunikat oraz plik i linia, w których błąd został znaleziony/zgłoszony i zmienne towarzyszące kontekstowi wywołania.
W tym przypadku dodatkowo dane zostały zapisane do pliku (generowanego osobno dla każdego dnia) z logami, gdzie poszczególne dane (oddzielone średnikami) zawarte są w jednym wierszu razem z godziną wystąpienia problemu. W 13 wierszu znajduje się przekazanie do pliku z logami zrzutu zmiennych, które w tej chwili jest nie aktywne, ponieważ zaburzałoby jednowierszowy podział dla zdarzeń. Jednak warto zapisywać również te dane, ponieważ pomagają w odnalezieniu źródła problemu.
Przy rozwiązywaniu problemu lub diagnozie błędu, który występuje w bardzo specyficznych okolicznościach przydatna jest funkcja debug_backtrace()
. Zwraca ona tablicę ze stosem wywołań od uruchomienia skryptu do wywołania jej samej – dla każdego wywołania podane są szczegółowe informacje pozwalające prześledzić kolejne kroki wykonania skryptu.
Zamiana na wyjątki
Ostatnią kwestią, którą poruszę jest zamiana wystąpień klasycznych ostrzeżeń/błędów na wyjątki. W PHP istnieje specjalnie zdefiniowany w tym celu typ wyjątku – ErrorException
. Sposób użycia został pokazany poniżej poprzez modyfikację poprzedniego przykładu.
<?php
define('LOG_SEPARATOR', ';');
ini_set('display_errors', 1);
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
function obsluga($kodBledu, $komunikat, $plik, $linia) {
throw new ErrorException($komunikat, $kodBledu, 0, $plik, $linia);
}
set_error_handler('obsluga');
echo 'punkt 1'. PHP_EOL;
trigger_error('Test wywolania notice', E_USER_NOTICE);
echo 'punkt 2'. PHP_EOL;
trigger_error('Test wywolania bledu', E_USER_ERROR);
echo 'punkt 3'. PHP_EOL;
Natomiast efekt uruchomienia będzie się prezentował następująco (tylko początek):
punkt 1
Fatal error: Uncaught exception 'ErrorException' with message 'Test wywolania notice' in ...
Funkcja przechwytująca błędy przekazała swoje parametry do wywołania wyjątku ErrorException
, wyjątek został rzucony… i niestety zakończyło to się błędem, ponieważ wyjątek nie został złapany. Żeby temu zaradzić należałoby każde miejsce, w którym błąd jest zgłaszany ręcznie zamknąć w bloku try
/catch
i/lub zdefiniować globalną funkcję obsługującą nieprzechwycone wyjątki. To jest jednak temat na jeden z następnych wpisów.
Klasyczne błędy generowane przez interpreter lub zgłąszane ręcznie pozwalają na zakomunikowanie programiście, że coś jest nie tak i pomagają w rozwiązaniu problemu. Jednak spełniają rolę tylko informacyjną – zapisanie do logu, wysłanie powiadomienia na e-mail czy pokazanie użytkownikowi strony z informacją o błędzie aplikacji. Natomiast w oparciu o mechanizm obsługi błędów nie ma możliwości reagowania na konkretne i przewidywalne problemy (jeżeli pojawia się wywołanie trigger_error()
to programista przewidział ewentualny problem). Rozwiązaniem jest wykorzystywanie wyjątków, które mogą być przechwytywane globalnie (tak jak błędy) oraz lokalnie dając programiście szanę na obsługę konkretnego zaistniałego problemu (a nie tylko w oparciu o poziom błędu).
Linki: