piątek, 11 października 2019

Error & Exception


  • Błędów  E_NOTICE  i  E_WARNING  NIE przechwycimy klauzulą try/catch,
  • Error exception (Traditional error reporting mechanism) !== ErrorException (klasa)


Try/catch/finally: Łapie "od góry do dołu"

<?php
declare(strict_types=1);

function foo(int $value) {}                    ______ 
                                               |    |
try {                                          |    |
    foo(true); // throws TypeError             |    | 
} catch (TypeError $e) {                 Najbardziej konkretny    
  echo 'catch ' . get_class($e);             |    |  
} catch (Error $e) {                         Ogólniejszy   
  echo 'catch ' . get_class($e);             |    |
} catch (Throwable $e) {                  Najbardziej ogólny   
  echo 'catch ' . get_class($e);          ___|    |___
} finally {                                 \  Zawsze  /   
    echo '!';                                \ wykona /  
}                                             \      / 
                                               \    / 
// catch TypeError!                             \  /
                                                 \/ 
                                               ______ 
                                               |    |
                                            ___|    |___
// jeżeli w dalszym ciągu nie               \          /
// przechwycimy wyjątku, złapiemy            \        /
// go gdy mamy zadeklarowany                  \      /             
// set_exception_handler()                     \    /
                                                \  /
                                                 \/ 

// jeżeli zabraknie deklaracji set_exception_handler()
// implementacja set_error_handler() NIE przechwyci błędu
                            ______               
                           |      |
                    Wyjątek nie przechwycony,
                        konwertowanie do: 
                         __|      |__
                        \            /
                         \          /
                          \        /
                           \      /
                            \    /
                             \  /
                              \/ 
                          FATAL ERROR  
                           
    Interfejs Throwable został wprowadzona w wersji PHP 7.0 i dziedziczą po niem dwie gałęzie systemu błędów: Exception i Error. Jako, że klasa Exception istniała już od wersji 5.0 zapytasz jak to wtedy wyglądało? Otóż Exception nie rozszerzało żadnej klasy. 

Warto wspomnieć o tym, że pola klas Error i Exception:

  • message,
  • code,
  • file,
  • line,
Są inicjalizowane w czasie tworzenia obiektu, a nie podczas jego wyrzucania za pomocą słowa kluczowego throw


    Co istotnie, nie jest możliwe bezpośrednie implementowanie po interfejsie Throwable, ale śmiało można Type Hint'ować po nim w klauzulach catch. Cała hierarchia predefiniowanych klas przedstawia się następująco (najistotniejsze klasy z mojego punktu widzenia):


  • Throwable - Interfejs
    • Error - klasa bazowa dla wewnętrznych błędów PHP
      • TypeError,
        • ArgumentCountError PHP 7.1,
      • ParseError,
      • ArithmeticError,
        • DivisionByZeroError,
      • AssertionError,
      • CompileError PHP 7.3,
    • Exception
      • ErrorException PHP 5.1,
      • DOMException PHP 5.0,
      • LogicException SPL Exceptions (od PHP 5.1),
        • BadFunctionCallException,
          • BadMethodCallException,
        • LengthException,
        • InvalidArgumentException
        • DomainException,
        • OutOfRangeException,
      • RuntimeException SPL Exceptions (od PHP 5.1),
        • PDOException


    Co ciekawe możesz w catch'ach Type Hint'ować na nieistniejące klasy Error\Exception i z tego powodu nie będzie wyrzucany żaden błąd. Interpreter po prostu nie sparuje wyrzuconego np. wyjątku z nieistniejącą klasą i - jeżeli takowa istnieje - przejdzie do kolejnej klauzuli catch


    Rodziny klas Error/Exception posiadają zaimplementowaną metodę __toString() dzięki czemu możemy stosować echo bezpośrednio na przechwyconych w klauzulach catch obiektach $e


ErrorException


    W wersji PHP 5.1 pojawiła się nowa klasa dziedzicząca po klasie Exception - ErrorException.  Jak sugeruje dokumentacja języka - zalecane jest zadeklarowanie nowego error handler'a który przechwytuje błędy typu   E_WARNING   (2) i   E_NOTICE   (8) by w przypadku ich wystąpienia wyrzucić wyjątek klasy ErrorException. Dzięki temu obsługa błędów w klasach klienckich nabierze jeszcze bardziej obiektowego charakteru. 


ArgumentCountError


    Wprowadzony w wersji PHP 7.1. W poprzedniej wersji języka wyrzucany był błąd   E_WARNING  . W przypadku funkcji zadeklarowanych przez użytkownika sprawa wygląda tak jak napisałem powyżej, inaczej jest jednak w przypadku natywnych funkcji PHP. Gdy do count() nie przekażemy żadnego argumentu, pomimo że działamy w wersji 7.1, wyrzucony zostanie   E_WARNING  . By interpreter wyrzucił ArgumentCountError, musimy umieścić deklarację declare(strict_types=1);. Zachowanie to ma na celu utrzymanie kompatybilności wstecznej.

TypeError


    Z własnego doświadczenia powiem ,że w projekcie który był utrzymywany w wersji 7.0 nie miałem możliwości zadeklarowania typu argumentu metody by przyjmowała NULL albo String takim sposobem ?string $name, dlatego pomijałem całkowicie Type Hint'ing. Następnie na samym początku ciała metody umieszczałem sprawdzenie czy typ argumentu jest String'iem albo NULL'em. Jeżeli asercja zwracała false, wyrzucałem TypeError - dla mnie bardzo funkcjonalne zastosowanie.


InvalidArgumentException


    W kontekście powyżej opisanego TypeError pojawia się pytanie - kiedy wyrzucać InvalidArgumentException? Odpowiedź jest krótka: gdy argument jest niewalidowalny. Choć też w zależności od kontekstu warto byłoby się zastanowić nad użyciem klasy DomainException albo bardziej ogólnej LogicException

BadMethodCallException


    Używam gdy kolejność wykonywania metod danej klasy w klasach klienckich jest istotna, np. dla uzyskania odpowiedniego stanu obiektu. Klasą wyjątku można też fajnie uzupełnić metodę magiczną __call().


DivisionByZeroError


    Wystąpi jedynie podczas wykonywania operacji modulo:

try {
    5%0;
catch (DivisionByZeroError $e) {
    echo 'catch'// wpadnie

}

try {
    5/0;
catch (DivisionByZeroError $e) {
    echo 'catch';

}
nie wpadnie w blok catch tylko wyrzuci  E_WARNING 

Operator Pipeline


     W PHP 7.1 wprowadzono operator pipeline | który możemy stosować w klauzuli catch. Jeżeli dany kod będzie sprawdzał się do obsługi kilku wyjątków/błędów to zastosowanie operatora pipeline jest idealne. Polega on na tym, że możemy wielokrotnie Type Hint'ować różne klasy Exception/Error w jednej klauzuli catch. Oto przykład:

try {
    foo(); // nieistniejąca funkcja
} catch (TypeError | Error | Exception $e) {
    echo 'caught: ' . get_class($e);



Kompatybilność wsteczna


Z racji tego że klasy Error, które pojawiły się w wersji 7.0 nie dziedziczą po klasie Exception, dotychczas istniejące bloki try/catch w Twoim kodzie implementowane pod PHP 5.6 - nie zmienią swojego zachowania      
    Jeżeli migrujesz z wersji 5.6 na 7.0 i zadeklarowałeś klasę o nazwie 'Error' - to musisz ją zmienić ponieważ otrzymasz: 

 FATAL ERROR  Cannot declare class Error, because the name is already in use 

Brak komentarzy:

Prześlij komentarz