niedziela, 20 października 2019

Standardowe błędy PHP


Dzielą się na FATAL i NON-FATAL:

                                                                                                                               
+---------------------------------------------------------+         
|                          E_ALL                          |
+---------------------------------------------------------+ 
   __            __             __          __          __
  |  |          |  |           |  |        |  |        |  | 
  ERROR   RECOVERABLE_ERROR   WARNING     NOTICE    DEPRECATED
  |  |          |  |           |  |        |  |        |  | 
CORE_ERROR      |  |        CORE_WARNING   |  |        |  |  
  |  |          |  |           |  |        |  |        |  |
COMPILE_ERROR   |  |       COMPILE_WARNING |  |        |  | 
  |  |          |  |           |  |        |  |        |  |
USER_ERROR      |  |       USER_WARNING USER_NOTICE USER_DEPRECATED
  |  |          |  |           |  |        |  |        |  |
  PARSE         |  |           |  |        |  |       STRICT
  |  |          |  |           |  |        |  |        |  | 
__|  |__      __|  |__       __|  |__    __|  |__    __|  |__  
\      /      \      /       \      /    \      /    \      / 
 \    /        \    /         \    /      \    /      \    / 
  \  /          \  /           \  /        \  /        \  /
   \/            \/             \/          \/          \/  

+-----------------------+    +-------------------------------+
|         FATAL         |    |           NON FATAL           |
+-----------------------+    +-------------------------------+ 


Możemy też przyjąć inny podział, ze względu na poziom zagrożenia: 


+-----------------+  +-----------------+  +-----------------+
|      NOTICE     |  |     WARNING     |  |      ERROR      |
|                 |  |                 |  |                 |
| *NOTICE         |  | *WARNING        |  | *ERROR          |
| *USER_NOTICE    |  | *USER_WARNING   |  | *USER_ERROR     |
*STRICT         |  | *COMPILE_WARNING|  | *COMPILE_ERROR  | 
| *DEPRECATED     |  | *CORE_WARNING   |  | *CORE_ERROR     |
| *USER_DEPRECATED|  |                 |  | *PARSE          |
|                 |  |                 |  | *RECOVERABLE    |
+-----------------+  +-----------------+  +-----------------+

ze względu na jego źródło:

+-----------------+  +-----------------+  +-----------------+
|       USER      |  |    COMPILER     |  |     RUNTIME     |
|                 |  |                 |  |                 |
| *USER_ERROR     |  | *PARSE          |  | *ERROR          |
| *USER_WARNING   |  | *CORE_ERROR     |  | *STRICT         |
| *USER_ERROR     |  | *CORE_WARNING   |  | *RECOVERABLE    |
| *USER_DEPRECATED|  | *COMPILE_ERROR  |  | *WARNING        |
|                 |  | *COMPILE_WARNING|  | *NOTICE         |
|                 |  |                 |  | *DEPRECATED     |
+-----------------+  +-----------------+  +-----------------+

czy są możliwe do przechwycenia przez procedurę obsługi zadeklarowaną w funkcji set_error_handler():

⛔ E_ERROR
⛔ E_PARSE
⛔ E_CORE_ERROR, E_CORE_WARNING
⛔ E_COMPILE_ERROR, E_COMPILE_WARNING
⛔ E_STRICT (większość)
✅ E_NOTICE
✅ E_WARNING
✅ E_DEPRECATED
✅ E_RECOVERABLE_ERROR
✅ E_USER_ERROR, E_USER_WARNING, E_USER_DEPRECATED, E_USER_NOTICE

Jest tak ponieważ możliwe do obsługi są jedynie błędy występujące w czasie run-time, wyjątkiem od tej reguły jest E_ERROR i E_STRICT. Warto nadmienić, że konfiguracja zawarta w funkcji error_reporting() nie będzie mieć wpływu na to czy handler przechwyci błąd czy nie.


 E_WARNING  - (2) ostrzeżenie powstałe w czasie wykonywania (run-time) - nie powoduje zatrzymania wykonywania kodu. Wyrzucane gdy:

 inkludujemy nieistniejący plik,
❌ użyjemy nieistniejącej stałej,
❌ wywołujemy nie-statyczną metodę klasy w statyczny sposób,
❌ nie zapewnimy wszystkich wymaganych argumentów funkcji/metody klasy, ale tylko do wersji 7.0 włącznie. Od PHP 7.1 wyrzucony będzie Error ArgumentCountError. W przypadku gdy funkcja będzie wymagała dwa argumenty, a wywołując ją nie dodamy żadnego, będziemy mieli dwa wywołane komunikaty ostrzegające.

Różne sposoby by ostrzeżenia NIE pokazywać: 
  • error_reporting(E_ALL ^ E_WARNING);
  • error_reporting(E_ALL & ~E_WARNING);
  • ini_set('error_reporting', E_ALL & ~E_WARNING);
  • error_reporting(E_ERROR | E_STRICT);

 E_COMPILE_WARNING  (128) - ostrzeżenia wygenerowane w czasie kompilowania skryptu przez silnik Zend domyślnie logowany do pliku error_log. Tego typu komunikaty otrzymać można wywołując taki kod:

echo "some message";
declare(unexistedDirective='someValue');

Tak jak w przypadku zwykłego ostrzeżenia, wykonywanie programu nie zostanie przerwane lecz z tą różnicą, że komunikat ostrzeżenia wyświetli się na samym początku:

Warning: Unsupported declare 'unexistedDirective'
some message

Jak widać developer nie jest jawnie informowany o ostrzeżeniu w czasie kompilacji, więc można to łatwo pomylić ze zwykłym ostrzeżeniem. Warto mieć to na uwadze podczas implementacji custom'owego handler'a z rozróżnieniem na poszczególne błędy. 

 E_CORE_WARNING  (32)   - generowany przez silnik PHP w czasie jego inicjalizacji.
 E_USER_WARNING  (512) - możliwy do wygenerowania przez developera za pomocą funkcji trigger_error().


 E_NOTICE  - (8) nie do końca jest to błąd ponieważ podczas wykonywania kodu powodującego ten komunikat, proces nie zostaje wstrzymany. Jest to po prostu informacja dla programisty, że coś przebiegło nie tak. Kilka przykładów, które powodują wygenerowanie tego komunikatu:

❌ użycie zmiennej która nie istnieje,
❌ użyjemy pola obiektu które nie istnieje,
❌ odwołanie się do indeksu tablicy, który nie istnieje,


Ukrycie komunikatu: error_reporting(E_ALL ^ E_NOTICE);

bądź w pliku php.ini deklarując wartość dyrektywy na to samo co w nawiasach funkcji error_reporting().

 E_USER_NOTICE  (1024) - możliwy do wygenerowania przez developera za pomocą funkcji trigger_error() przekazując tylko jeden argument typu String będący wiadomością. Drugi argument nie jest konieczny ponieważ jego wartość domyślna to 1024.


 E_STRICT  - (2048) - Jest to podpowiedź od interpretera, że konkretne rozwiązanie nie jest najlepszym standardem/dobrą praktyką lub może zostać wycofane w przyszłych wersjach języka. Wchodzi w skład predefiniowanej stałej E_ALL dopiero od wersji PHP 5.4 choć dostępny jest od 5.0. Od wersji 7.0 już nie zdarzy się nam ujrzeć tego typu błędu ponieważ został zastąpiony komunikatami NOTICE, WARNING czy DEPRECATED. Ze względu na utrzymanie kompatybilności wstecznej stała E_STRICT została jednak zachowana. Powodem tych zmian było zapewnienie prostszego systemu błędów w PHP w którym rola E_STRICT była dość niejasna. Więcej informacji na ten temat znajduje się w linku RFC:reclassify_e_strict. We wcześniejszych wersjach języka błąd E_STRICT otrzymywaliśmy np. gdy:


❌ odwoływaliśmy się do nie-statycznej metody klasy w sposób statyczny,
❌ sygnatura metody w klasie jest inna niż sygnatura tej samej metody w nad klasie,
❌ w klasie zadeklarujesz dwa konstruktory: za pomocą metody magicznej __construct() i starym sposobem gdzie nazwa konstruktora jest taka sama jak nazwa klasy. 
masz metodę która zwraca array'a, wywołujesz ją w funkcji array_pop() - dostajesz komunikat: DEBUG Only variables should be passed by reference.
❌ zadeklarujemy sygnaturę abstrakcyjnej metody statycznej,


 E_DEPRECATED  (8192)  E_USER_DEPRECATED  - (16384) - dostępne od wersji 5.3. Jest to ostrzeżenie wyrzucane w czasie run-time o użytych w kodzie funkcjach\klasach\konstruktach czy rozwiązaniach, które będą wycofane w przyszłych wersjach języka. O tym czy dany wyżej wymienione będą uznawane za przestarzałe decydują ludzie zaangażowani w rozwój języka. Komunikaty te będą się różnić w zależności od wersji języka z której aktualnie korzystasz. W przypadku własnych bibliotek, sam możesz wygenerować błąd E_USER_DEPRECATED za pomocą wbudowanej w język funkcji user_error() będącej aliasem trigger_error() np w taki sposób:
trigger_error('Some message', E_USER_DEPRECATED);

Co powoduje wyrzucenie komunikatu E_DEPRECATED:

7.3
❌ podczas deklarowania stałej case-insensitive za pomocą funkcji define() - czyli przekazując jako trzeci parametr TRUE,
❌ flagi dla funkcji filter_var(): FILTER_FLAG_SCHEME_REQUIRED, FILTER_FLAG_HOST_REQUIRED.

7.2
❌ korzystanie z create_function(), each()__autoload(),
❌ rzutowanie wartości na NULL: (unset) $someVariable,
❌ dyrektywa php.ini track_errors,
❌ assert() z parametrem typu String,
❌ zmienna $php_errormsg,
używanie funkcji parse_str() bez podawania drugiego argumentu,

7.1
❌ korzystanie z rozszerzenia mcrypt z racji tego, że nie jest wspierane i trudne w użyciu. Usunięty z PHP w wersji 7.2 i zasobów PECL,
❌ modyfikator e dla funkcji (Multibyte String Function) mb_ereg_replace() i mb_eregi_replace(),

7.0
❌ konstruktory klasy w stylu PHP 4 czyli taki gdzie nazwa funkcji jest taka sama jak nazwa klasy w której się znajduje,

Wymienione wyżej przypadki zostaną usunięte w PHP 8.0.

 E_RECOVERABLE_ERROR  (4096) - dostępny od wersji 5.2. Jest to właściwie Fatal Error możliwy do przechwycenia przez handler zdefiniowany za pomocą funkcji set_error_handler(). Kod który powoduje wyrzucenie tego komunikatu jest poważnym błędem, ale nie pozostawia silnika PHP w niestabilnym stanie, dlatego jeżeli tylko zostanie przechwycony, wykonywanie programu nie zostaje przerwane (w przeciwnym razie program zachowa się jak w przypadku E_ERROR).

Co wyrzuca 'odzyskiwalne błędy' pisałem w tym wpisie. To jedyne przypadki, które udało mi się jak dotąd namierzyć. Ale... instancjonowanie obiektu Closure inaczej można obsłużyć w zależności od wersji:

PHP 5.6 w set_error_handler() ponieważ wyrzucany jest E_RECOVERABLE_ERROR.

PHP 7.0: w bloku try/catch ponieważ wyrzuci to E_ERROR.

Co ciekawe w przypadku błędów związanych z __toString() (ze wspomnianego wpisu) one w dalszym ciągu są obsługiwane przez set_error_handler().




 E_ERROR  (1) - błąd krytyczny wywoływany w czasie wykonywania powodujący wyświetlenie komunikatu typu Fatal Error, a w związku tym natychmiastowe przerwanie skryptu. Przechwycenie tego typu błędu we własnej procedurze obsługi zadeklarowanej za pomocą funkcji set_error_handler() nie jest możliwe. Różnica między PHP 5.6 a 7.0 jest tak, że teraz mamy możliwość godnego obsłużenia niektórych błędów krytycznych:

Błędy które do wersji 5.6  powodowały nieobsługiwane Fatal Error'y, a od 7.0 można je przechwytywać w bloku try/catch, TypeHint'ując na klasę Error:
 wywołanie nieistniejącej funkcji,
 instancjonowanie nieistniejącej klasy,
 instancjonowanie interfejsu,
 odwołanie się do nieistniejącego statycznego pola klasy,
 odwołanie się do nieistniejącej metody statycznej klasy,

Błędy które nawet w wersjach 7.x nie są możliwe do obsługi:
❌ implementowanie interfejsu który nie istnieje,
❌ dziedziczenie po nieistniejącej klasie,
❌ próba przypisania do pola, pola statycznego, stałej klasy innej wartości niż literał np. funkcji anonimowej,
❌ dodanie ciała metody w interfejsie,
próba przypisania wartości do $this,
❌ stosowanie isset() na literałach np. isset(0.0);



 E_CORE_ERROR  (16) - generowany przez silnik PHP. Błąd wystąpi gdy mamy PHP 5.4 (i większe) a w php.ini mamy zadeklarowaną dyrektywę safe_mode=On. Kod developera nie zostaje w ogóle wykonywany.

 E_COMPILE_ERROR  (64) - wyrzucane gdy na zmiennej będziemy próbowali zastosować ::class. Otrzymamy wtedy komunikat: FATAL ERROR Dynamic class names are not allowed in compile-time ::class fetch.

 E_USER_ERROR  (256) - możliwy do wygenerowania przez developera za pomocą funkcji trigger_error()i tym razem - inaczej niż w przypadku E_ERROR - możliwy do obsługi za pomocą set_error_handler().


 E_PARSE  (4) - pojawia się gdy kompilator nie może zinterpretować kodu developera - niemożliwy do obsługi w jakikolwiek sposób. Powoduje wyświetlenie komunikatu typu Parse error: syntax errorKod developera nie zostaje w ogóle wykonywany. Występuje między innymi w następujących przypadkach zaimplementowanego kodu:

❌ final interface MyInterface {}
❌ abstract interface MyInterface {}
❌ class MyClass() {}
❌ echo 'something'
❌ if (if(true) {} else {}) {}


 FATAL ERROR  - jest to błąd krytyczny i gdy wystąpi następuje przerwanie wykonywania programu. Domyślne zachowanie sprowadza się do wyświetlenia komunikatu błędu.

 FATAL ERROR: UNCAUGHT ERROR  - jest to Fatal Error możliwy do przechwycenia. Dopisek Uncaught error daje informację o tym iż jest to błąd do przechwycenia w klauzuli try/catch, a więc TypeHint'ując w bloku catch na klasę Error albo Throwable mamy możliwość jego obsługi. Procedura obsługi w funkcji set_error_handler() nie jest w stanie obsłużyć tego błędu. Rozszerzony komunikat Fatal Error pojawił się dopiero w PHP 7.0.

 FATAL ERROR: UNCAUGHT EXCEPTION  - tak samo jak powyżej analogicznie do nieprzechwyconych wyjątków (czyli jak ich jawnie nie obsłużymy to zamieniają się w Fatal Error'y).

Standardowe błędy PHP i klasa ErrorException


Dokumentacja zaleca by przechwytywać ostrzeżenia WARNING czy komunikaty NOTICE w procedurze obsługi zdefiniowanej w funkcji set_error_handler() i tam wyrzucać wyjątek ErrorException (PHP 5.1) stworzony z intencją o takim zastosowaniu. Dzięki temu będziemy mogli obsłużyć wyrzucone ostrzeżenie (przykład poniżej) w kontekście jego wywołania:


set_error_handler(function(int $errorCode) {
    if ($errorCode === E_WARNING) {
        throw new ErrorException('Some warning');
    }
});

try {
    echo A;
} catch (ErrorException $e) {
    echo $e->getMessage();
}



Na sam koniec wrzucam fajny kalkulator do wyliczania kodu błędów. 



Źródła:








Brak komentarzy:

Prześlij komentarz