Certyfikat ZendPHP

Obsługa Błędów

Obsługa błędów uwzględnia niektóre z największych zmian między PHP 5.6 a PHP 7.1 i warto omówić ten ważny temat osobno, nawet jeśli dotknęliśmy go w odpowiednich miejscach w innym miejscu tego kursu.

Throwable

Będziemy przyglądać się klasom Error i Exception . Na razie wszystko, co musisz wiedzieć, to to, że oba implementują interfejs Throwable, który został wprowadzony w PHP 7. Wskazówka : Powodem, dla którego PHP zdefiniowało nową klasę Error, która nie rozszerza klasy Exception, było zachowanie wstecznej kompatybilności z kodem PHP5.6.

Interfejs Thromble

Zarówno wyjątki, jak i wyjątki błędów implementują interfejs Throwable, dzięki czemu można wychwycić oba typy w jednym bloku, takim jak ten:

< ?php
try {
// ...kod
} catch (Throwable $ e) {
echo "Była klasa, która dziedziczy po Exception lub ErrorException
catch ";
}

Za chwilę zobaczymy pasujące reguły, których PHP używa do porównywania wyjątków błędów i wyjątków z blokami catch. Możesz znaleźć metody zdefiniowane w interfejsie Throwable w podręczniku PHP, ale tutaj są one dla Twojej wygody:

Throwable {
/ * Metody * /
abstract public string getMessage ( void )
abstract public int getCode ( void )
abstract public string getFile ( void )
abstract public int getLine ( void )
abstract public array getTrace ( void )
abstract public string getTraceAsString ( void )
abstract public Throwable getPrevious ( void )
abstract public string __toString ( void )
}

Wskazówka : Te metody mogą być bardzo przydatne do rejestrowania błędu

Błędy

W starszych wersjach PHP błędy były traktowane zupełnie inaczej niż wyjątki. Błąd występował w silniku i pod warunkiem, że nie był śmiertelny, mógł być obsługiwany przez funkcję zdefiniowaną przez użytkownika. Problem polegał na tym, że było kilka błędów krytycznych, które nie mogły być obsługiwane przez program obsługi błędów zdefiniowany przez użytkownika. Oznaczało to, że nie można z wdziękiem obsługiwać krytycznych błędów w PHP5.6. Było kilka skutków ubocznych, które były problematyczne - takich jak utrata kontekstu uruchomieniowego, nie wywołano destruktorów, a radzenie sobie z nimi było niezgrabne. W PHP 7 błędy krytyczne są teraz wyjątkami i są o wiele łatwiejsze do rozwiązania.

Uwaga : Tylko błędy krytyczne powodują zgłoszenie wyjątku błędu. Musisz obsługiwać błędy niekrytyczne za pomocą funkcji obsługi błędów.

Oto przykład łapania krytycznego błędu w PHP 7.1. Zauważ, że nie jest wychwytywany błąd niekrytyczny.

< ?php
try {
// generuje błąd powiadomienia (nieprzechwycony)
echo $thisVariableIsNotSet;
// to byłby błąd krytyczny (został przechwycony)
badFunction();
} catch (Error $e) {
echo "Error caught: " . $e->getMessage();
}

Ten skrypt wyświetli błąd powiadomienia przy próbie dostępu do niepoprawnej zmiennej. Próba wywołania funkcji, która nie istnieje, spowodowałaby błąd krytyczny we wcześniejszych wersjach PHP, ale w PHP 7.1 można go złapać. Oto dane wyjściowe skryptu:

Uwaga: Niezdefiniowana zmienna: thisVariableIsNotSet in / in / lQC3F w linii 5 Błąd przechwycony: Wywołanie niezdefiniowanej funkcji badFunction ()

Stałe błędu

PHP ma wiele stałych, które są używane w odniesieniu do błędów. Stałe te są używane podczas konfigurowania PHP do ukrywania lub wyświetlania błędów niektórych klas. Oto niektóre z najczęściej spotykanych kodów błędów:

Kod : Opis : Skrypt : Zgłasza błąd?

E_DEPRECATED : Interpreter wygeneruje ostrzeżenia tego typu, jeśli używasz funkcji języka, która jest przestarzała : Kontynuuje działanie : NIE

E_STRICT : Podobnie jak E_DEPRECATED, wskazuje, że używasz funkcja języka, która nie jest obecnie standardem i może nie działać w przyszłości : Kontynuuje działanie : NIE

E_PARSE : Nie można przeanalizować składni, więc twój skrypt się nie uruchomi : W ogóle nie będzie działać : NIE

E_NOTICE : Komunikat informacyjny : Kontynuuje działanie : NIE

E_WARNING : Są to ostrzeżenia inne niż śmiertelne : Kontynuuje działanie : NIE

E_ERROR : Skrypt nie może kontynuować działania i jest kończony : Przerywa, chyba że ty poradzisz sobie z obsługą błędów : TAK

E_RECOVERABLE_ERROR : Błąd był prawdopodobnie wystarczająco niebezpieczny, aby być śmiertelnym, ale silnik nie jest w stanie i nie można kontynuować : Przerywa, chyba że poradzisz sobie z obsługą błędów : TAK

Korzystanie z funkcji obsługi błędów

Funkcja set_error_handler ()3 służy do informowania PHP, jak obsługiwać standardowe błędy silnika, które nie są instancjami klasy wyjątku Error. Nie można użyć funkcji obsługi błędów w przypadku błędów krytycznych; musisz je złapać jako wyjątki błędu. set_error_handler () akceptuje callable4 jako swój parametr. Callables w PHP można określić na dwa sposoby: albo przez ciąg oznaczający nazwę funkcji, albo przez przekazanie tablicy zawierającej obiekt i nazwę metody (w tej kolejności).

Uwaga : Możesz określić chronione i prywatne metody w obiekcie jako wywoływalne.

Możesz także przekazać null, aby powiedzieć PHP, aby powrócił do korzystania ze standardowego mechanizmu obsługi błędów. Jeśli program obsługi błędów nie zakończy programu i powróci, skrypt będzie kontynuował wykonywanie w wierszu po wystąpieniu błędu. PHP przekazuje parametry do funkcji obsługi błędów. Możesz opcjonalnie zadeklarować je w podpisie funkcji, jeśli chcesz ich użyć w swojej funkcji.

< ?php
function myHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Error #[$errNo] occurred in [$file] at line [$line]: [$errMsg]";
}
set_error_handler('myHandler');
try {
// This does not throw an Error
5 / 0;
} catch ( Throwable $e ) {
echo 'Caught error : ' . $e->getMessage();
}
/*
Error #[2] occurred in [/in/Xa0Td] at line [11]: [Division by zero]
*/

W poprzednim przykładzie dzielimy liczbę pięć przez zero. W PHP powoduje to ostrzeżenie, więc błąd nie jest generowany. Jednak ustawiliśmy funkcję myHandler () jako funkcję obsługi błędów klienta, która jest wywoływana, gdy PHP napotka ostrzeżenie. Błędy, które powodują zakończenie skryptu, nie mogą zostać przechwycone przez procedurę obsługi błędów użytkownika; obejmują one E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR i E_COMPILE_WARNING.

Wyświetlanie lub ukrywanie komunikatów o błędach innych niż krytyczne Ogólnie rzecz biorąc, chcesz ukryć wszystkie komunikaty o błędach systemu podczas produkcji, a kod powinien działać bez generowania ostrzeżeń lub komunikatów. Jeśli chcesz wyświetlić komunikat o błędzie, upewnij się, że jest to wygenerowany komunikat, który nie zawiera informacji, które mogłyby pomóc atakującemu włamać się do Twojego systemu. W środowisku programistycznym chcesz wyświetlać wszystkie błędy, aby można było rozwiązać wszystkie problemy, których dotyczą, ale podczas produkcji chcesz ukryć wszelkie komunikaty systemowe wysyłane do użytkownika. Aby to osiągnąć, musisz skonfigurować PHP przy użyciu następujących ustawień w pliku php.ini:

•  display_errors można ustawić na false, aby ukryć wiadomości
•  log_errors można wykorzystać do przechowywania komunikatów o błędach w plikach dziennika
•  można ustawić raportowanie błędów, aby skonfigurować, które błędy wyzwalają raport

Najlepszą praktyką jest z obsługa błędów w aplikacji. W środowisku produkcyjnym powinieneś raczej rejestrować nieobsługiwane błędy zamiast pozwolić, aby były wyświetlane użytkownikowi.

Uwaga : Przyjrzeliśmy się operatorowi tłumienia błędów @ w pierwszym rozdziale. Pamiętaj, że najlepiej go unikać.

Za pomocą funkcji error_log () 5 można wysłać komunikat do jednej z procedur obsługi błędów systemu. Nie należy mylić go z opcją konfiguracji dziennika błędów. Opcja konfiguracji określa sposób postępowania z dziennikami, a funkcja służy do wysyłania wiadomości. Możesz także użyć funkcji error_log() do wysyłania e-maili, ale osobiście nie zrobiłbym tego i wolałbym to zrobić w kodzie lub skorzystać z usługi takiej jak Rollbar.

Funkcje obsługi błędów

PHP ma wiele funkcji związanych z obsługą błędów. Ta tabela zawiera ich podsumowanie.

Funkcja : Cel

debug_backtrace : Generuje ślad
debug_print_backtrace : Drukuje ślad. Zachowaj ostrożność podczas korzystania z tej funkcji ponieważ może generować dużo wyników!
error_clear_last : Usuwa ostatni błąd.
error_get_last : Pobiera ostatni występujący błąd.
error_log : Wysyła komunikat o błędzie do zdefiniowanej obsługi rutynowych błędów.
error_reporting : Ustawia zgłaszane błędy PHP.
restore_error_handler Przywraca poprzednią funkcję modułu obsługi błędów.
restore_ exception_handler : Przywraca poprzednio zdefiniowaną funkcję obsługi wyjątków.
set_error_handler : Ustawia zdefiniowaną przez użytkownika funkcję obsługi błędów.
set_exception_handler : Ustawia zdefiniowaną przez użytkownika funkcję obsługi wyjątku.
trigger_error :Generuje komunikat o błędzie / ostrzeżeniu / zawiadomieniu na poziomie użytkownika.
user_error : Alias z trigger_error.

Wyjątki

Wyjątki są podstawową częścią programowania obiektowego i zostały po raz pierwszy wprowadzone w PHP 5.0. Wyjątkiem jest stan programu, który wymaga specjalnego przetwarzania, ponieważ nie działa w oczekiwany sposób. Możesz użyć wyjątku, aby zmienić przepływ programu, na przykład, aby przestać coś robić, jeśli nie zostaną spełnione określone warunki wstępne. Wyjątek będzie przechodził przez stos wywołań, jeśli go nie złapie. Spójrzmy na przykład:

< ?php
function A() {
/ Wyjątek zgłoszony w C spowoduje bąbelkowanie do A
// ponieważ nie jest obsługiwane w C lub B
try {
B();
} catch (Exception $e) {
echo "Caught exception in " . __METHOD__;
}
}
function B() {
// nie przechwytujemy wyjątków w B
C();
}
function C() {
// // nie przechwytujemy wyjątku tam, gdzie jest on zgłaszany
throw new Exception('Bubble');
}
A();
/*

Dane wyjściowe:
Caught exception in A and the program ends successfully
*/

Ten program wywołuje funkcję A, która wywołuje B, która następnie wywołuje C. Funkcja C zgłasza wyjątek, ale nie łapiemy go w C. Wyjątek przechodzi do B, ale tam też go nie złapano. Wyjątek nadal rośnie do A, gdzie go łapiemy. Gdybyśmy nie wyłapali wyjątku w punkcie A, wówczas pojawiłby się globalny zasięg, w którym mielibyśmy ostatnią szansę na jego złapanie. Jeśli wyjątek nie zostanie wykryty, PHP szuka domyślnego modułu obsługi wyjątków, a ostatecznie, jeśli nie ma modułu obsługi, spowoduje to błąd krytyczny.

Rozszerzanie klas wyjątków

PHP zawiera kilka standardowych typów wyjątków, a standardowa biblioteka PHP (SPL) zawiera kilka innych. Chociaż nie musisz korzystać z tych wyjątków, oznacza to, że możesz użyć bardziej szczegółowego wykrywania błędów i raportowania. Klasy wyjątków i błędów implementują interfejs Throwable i, podobnie jak wszystkie inne klasy, można rozszerzyć. Umożliwia to tworzenie elastycznych hierarchii błędów i dostosuj obsługę wyjątków. Tylko klasa, która implementuje klasę Throwable, może być używana ze słowem kluczowym throw. Innymi słowy, nie możesz zadeklarować własnej klasy podstawowej, a następnie zgłosić ją jako wyjątek. Na przykład stwórzmy klasę wyjątków, której możemy użyć, aby zasygnalizować, że wystąpił problem ze sprawdzaniem poprawności formularza:

< ?php
class ValidationException extends Exception { }
function myValidation() {
if (empty($_POST)) {
throw new ValidationException('No form fields entered');
}
}

Spójrzmy na składnię, a następnie omówmy bardziej szczegółowo wyjątki:

< ?php
class ParentException extends Exception {}
class ChildException extends ParentException {}
try {
// jakiś kod
throw new ChildException('My Message');
} catch (ParentException $e) {
// // pasuje do tej klasy z powodu dziedziczenia
echo "Parent Exception :" . $e->getMessage();
} catch (ChildException $e) {
// / dokładnie pasuje do tej klasy
echo "Child Exception :" . $e->getMessage();
} catch (Exception $e) {
// pasuje do tej klasy z powodu dziedziczenia
echo "Exception :" . $e->getMessage();
}

Dane wyjściowe tego przykładu to Wyjątek nadrzędny: Moja wiadomość.

W tym przykładzie zgłaszamy wyjątek ChildException, który dziedziczy od ParentException, który z kolei rozszerza podstawową klasę Exception. Bloki są oceniane w kolejności od góry do dołu. Pierwszy dopasowany blok zostanie wykonany. Klasa zgłaszanego wyjątku jest dopasowywana do nazwy klasy podanej jako parametr do klauzuli catch. Kryteria pasujące są następujące:

•  Dokładnie takie same lub
•  Zgłoszony wyjątek jest przodkiem wyjątku w instrukcji catch

W tym przykładzie zgłosiliśmy wyjątek ChildException, który dziedziczy po ParentException. Dlatego wyjątek jest dopasowywany do pierwszego bloku catch i kod jest wykonywany. Podstawowy wyjątek umieszczam na dole listy bloków catch, ponieważ wszystkie niestandardowe wyjątki dziedziczą po nim, co czyni go catchall.

Hierarchia wyjątków

Do tej pory zrozumieliśmy, że zarówno błędy, jak i wyjątki implementują interfejs Throwable. Właśnie widzieliśmy, że zarówno klasy Error, jak i Exception można rozszerzyć.

Wbudowana hierarchia wyjątków PHP 7 wygląda następująco:



Jak widać, istnieje kilka predefiniowanych klas błędów, które tworzą hierarchię pod Error. Poniższa tabela podsumowuje ich przeznaczenie:

Klasa : Cel

TypeError : Błąd TypeError jest generowany, gdy argument przekazywany do funkcji nie odpowiada odpowiadającemu mu zadeklarowanemu typowi parametru, lub gdy funkcja nie zwraca oczekiwanego typu.
ArgumentCountError : ArgumentCountError jest generowany, gdy jest za mało argumentów przekazanych do funkcji lub metody zdefiniowanej przez użytkownika.
ParseError : Błąd ParseError jest generowany, gdy wystąpi błąd podczas analizowania kodu PHP, na przykład, gdy wywołujesz eval() lub dołączasz plik.
ArithmeticError : Błąd arytmetyczny występuje podczas próby przesunięcia bitów o ujemną ilość lub wywołanie funkcji intdiv(), która by to spowodowała w wartości poza granicami liczby całkowitej w bieżącym systemie.
DivisionByZeroError : DivisionByZeroError występuje, jeśli spróbujesz podzielić przez zero.
AssertionError : Błąd AssertionError jest generowany, gdy wykonywane jest stwierdzenie konstrukcja języka assert() kończy się niepowodzeniem.

Obsługa wyjątków

Solidny kod może napotkać błąd i sobie z nim poradzić. Rozsądne obchodzenie się z wyjątkami poprawia bezpieczeństwo aplikacji oraz ułatwia logowanie i debugowanie. Zarządzanie błędami w aplikacji pozwoli również zaoferować użytkownikom lepsze wrażenia. W tej sekcji omawiamy sposoby przechwytywania i obsługi błędów występujących w kodzie.

Łapanie wyjątków

Pamiętaj, że wcześniej zdefiniowaliśmy ValidationException w następujący sposób:

< ?php
class ValidationException extends Exception { }
function myValidation() {
if (empty($_POST)) {
throw new ValidationException('No form fields entered');
}
}

Kontynuujmy odtąd i wyobraźmy sobie, że wywołujemy funkcję myValidation() i chcemy wychwycić wyjątki. Składnia przechwytywania wyjątku jest następująca:

< ?php
try {
// zakładamy, że jeśli występuje problem z weryfikacją, powoduje to wyrzucenie ValidationException
myValidation();
} catch (ValidationException $e) {
echo "Validation exception caught ";
echo $e->getMessage();
} catch (Exception $e) {
echo "General exception type caught";
}

Zauważ, że istnieją dwie klauzule catch. Wyjątki będą dopasowywane do klauzul od góry do dołu, aż typ wyjątku będzie pasował do klauzuli catch.

Uwaga : Kryteria dopasowania są takie, że klasy są albo dokładnie takie same, albo klasa wyjątku zgłoszonego wyjątku jest przodkiem klasy wyjątku w instrukcji catch.

Ponieważ myValidation zgłasza ValidationException, spodziewalibyśmy się, że zostanie on przechwycony w pierwszym bloku, ale jeśli w funkcji zostanie zgłoszony inny typ wyjątku, zostanie on przechwycony w drugim bloku catch. Zauważ też, że metoda getMessage() jest wywoływana na obiekcie wyjątku. Inne metody w podstawowej klasie wyjątków podadzą kody błędów, ślady stosu i inne informacje. Podręcznik PHP dotyczący wyjątków jest najlepszym odniesieniem do prototypu obiektu wyjątku. Możliwe jest zgłoszenie wyjątku w bloku catch. To pozwala złapać wyjątek, a następnie w razie potrzeby ponownie go rzucić.

Wskazówka : Powinieneś zawsze zamawiać swoje bloki od najbardziej szczegółowych na górze do najbardziej ogólnych na dole - pamiętaj, że bloki są chciwe! Blok catch może określać wiele klas wyjątków, oddzielając je znakiem potoku (|). W poniższym przykładzie blok catch dopasuje wyjątki, które należą do klasy MyException lub klasy AnotherException.

< ?php
class MyException extends Exception {}
class AnotherException extends Exception {}
try {
throw new AnotherException;
} catch (MyException | AnotherException $e) {
echo "Caught : " . get_class($e);
}
/*
Caught : AnotherException
*/

Uwaga : Blok try musi mieć co najmniej jeden blok catch.



Ostatnia klauzula, na którą spojrzymy, to finally. Ten blok kodu będzie zawsze wykonywany, niezależnie od tego, czy zostanie zgłoszony wyjątek, czy nie. Jest wykonywany po zakończeniu bloku try lub po zakończeniu bloku wyjątku. Jednym z powszechnych zastosowań bloku finally jest zamknięcie uchwytu pliku, ale w końcu można go użyć wszędzie tam, gdzie chcesz, aby kod zawsze był wykonywany.

< ?php
try {
// wykonanie pewnych funkcji
} catch (Exception $e) {
// obsługa błędu
} finally {
// zawsze uruchamia te instrukcje
}

Ustawianie domyślnego modułu obsługi wyjątków

Każdy wyjątek, który nie zostanie wychwycony, powoduje błąd krytyczny. Jeśli chcesz z wdziękiem reagować na wyjątki, które nie są wychwytywane w blokach catch, musisz ustawić funkcję jako domyślną procedurę obsługi wyjątków. Aby to zrobić, należy użyć funkcji set_exception_handler (), która przyjmuje parametr callable jako parametr. Twój skrypt zakończy się po wykonaniu wywołania. Funkcja restore_exception_handler() przywróci obsługę wyjątków do poprzedniej wartości.


QUIZ



P1: Co wygeneruje ten kod?

< ?php
$handler = function($errorNumber, $errorMessage, $file, $line) {
echo "Error [$errorNumber] in [$file] at line [$line]:
'[$errorMessage]'\r\n";
};
set_error_handler($handler);
try {
echo $a;
session_start();
echo session_id();
} catch (Throwable $e) {
echo "Error caught!";
}

--------------------------------------
Wystąpił błąd!
Losowy ciąg znaków, który jest identyfikatorem sesji
Błąd powiadomienia i ostrzeżenie
Dwie sformatowane linie, każda zawierająca informacje o błędzie

P2: Co wygeneruje ten kod?

< ?php
$handler = function($errorNumber, $errorMessage, $file, $line) {
echo "Error [$errorNumber] in [$file] at line [$line]: '[$errorMessage]'\
r\n";
};
set_error_handler($handler);
this_function_is_not_defined();

--------------------------------------
Nic, działa bezbłędnie
Komunikat PHP o normalnym błędzie krytycznym
Sformatowany wiersz informacji o błędzie
Żadne z powyższych

P3: Co wygeneruje ten kod?

< ?php
class IndianaError extends ArithmeticError {}
define('PI', 3);
try {
if (is_int(PI)) {
throw new IndianaError('Oops');
}
} catch (Exception $e) {
echo $e->getMessage();
}

--------------------------------------
Nic, ten kod działa bezbłędnie
Ups!
Błąd krytyczny PHP
Żadne z powyższych

P4: Co wygeneruje ten kod?

set_error_handler (function ($ errorNumber, $ errorMessage, $ file, $ line) {
debug_print_backtrace ();
});
trigger_error ("Witaj świecie", E_USER_WARNING);

--------------------------------------
Witaj świecie
Dwie linie informacji
Błąd krytyczny PHP
Żadne z powyższych

P5: Co wygeneruje ten kod?

< ?php
try {
echo 50/0;
} catch (Exception $e) {
echo "Exception caught!";
} catch (Throwable $e) {
echo " Throwable caught!";
} catch (Error $e) {
echo "Error caught!";
} catch (DivisionByZeroError $e) {
echo "DivisionByZeroError caught!";
}

--------------------------------------
Przechwycono wyjątek!
Przechwycono throwable!
Wystąpił błąd!
Prechwycono DivisionByZeroError!
Żadne z powyższych


ODPOWIEDZI



•  Dwie sformatowane linie, każda zawierająca informacje o błędzie

•  Sformatowany wiersz informacji o błędzie

•  Błąd krytyczny PHP

•  Dwie linie informacji

•  Prechwycono DivisionByZeroError!