27 grudnia 2019

Wyjątki w PHP try i catch

Lecimy z tematem…

Wstęp

Wyobraźmy sobie pewien scenariusz

Pobierasz z api pewne dane. Przekazujesz je do konstruktora swojej własnej klasy. Tworzysz obiekt owej klasy, wywołujesz poszczególne funkcje w celu otrzymania odpowiednio sformatowanych danych.

Co może pójść nie tak ?

Możesz z api nie dostać danych których oczekujesz, lub mogą być w innym formacie. Możesz w takich sytuacjach otrzymać logi z błędami zapisane na serwerze, mogą co gorsza zostać wypisane na ekranie użytkownika aplikacji. Może twój program przestać nagle działać.

Z pomocą przychodzi nam obsługa wyjątków.

Lecimy z prostym przykładem:

class Entity
{
    private $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function getName() : string
    {
        return $this->data['name'];
    }
}

Powyższy przykład celowo jest BARDZO prosty.

Załóżmy, że nie istnieje taki index jak ‚name’, lub jest w złym formacie ?

Zmieńmy zatem funkcję „getName„:

public function getName() : string
    {
        if (
            !isset($this->data['name'] || 
            !is_string($this->data['name'])
        ) {
            throw new \Exception('Brak indeksu name, lub nie jest stringiem');
        }
        return $this->data['name'];
    }

Wywołaliśmy powyżej wyjątek, w sytuacji gdy zabraknie nam indeksu „name” lub nie będzie w oczekiwanym formacie.

Klasa „Exception” jest klasą bazową. w PHP mamy do dyspozycji kilka klas dziedziczących z „Exception”, możemy również bezproblemowo stworzyć kolejne.

Możemy w tej sytuacji wywołać metodę „getName” w sposób zaprezentowany poniżej:

try {
    $entity->getName();
} catch (\Throwable $th) {
    #metoda getName() zwróciła wyjątek
}

Jeśli metoda „getName” zwróci wyjątek, wywołany zostanie kod zawarty wewnątrz bloku „catch„.

Metody klasy „Exception”

W kodzie klienckim wiemy już, że wywoływana przez nas metoda „getName” zwróciła wyjątek, ale nie wiemy tak na prawdę dlaczego.

W celu ustalenia jaki powód był zwracanego wyjątku, posłużą nam metody klasy „Exception„:

  • getMessage” -> zwraca treść wyjątku
  • getCode” -> zwraca kod wyjątku
  • getFile” -> zwraca nazwę pliku w którym wystąpił wyjątek
  • getLine” -> zwraca linię w której wystąpił wyjątek
  • getTrace” -> zwraca tablicę „Trace
  • getTraceAsString” -> zwraca tablicę „Trace” w formacie string

Metod klasy „Exception” możemy użyć tak jak na przykładzie poniżej:

try {
    $entity->getName();
} catch (\Throwable $th) {
    echo $th->getMessage();
}

Powyższy przykład na ekranie wyświetli nam informację:

„Brak indeksu name, lub nie jest stringiem”.

Informacja powyższa będzie dokładnie taka sama jak, ta którą zobaczymy na ekranie po uruchomieniu kodu powyżej. Dlaczego ?

Ponieważ wywołując klasę „Exception” podaliśmy w parametrze „Message” równy wyświetlonemu powyżej. Jako drugi parametr moglibyśmy podać „Code„.

Exception, Message, Code

throw new \Exception('treść', 88);

Wyjątek zapisany powyżej, przechwycony przy pomocy poniższego kodu:

try {
    $entity->getName();
} catch (\Throwable $th) {
    echo $th->getMessage();
    echo '<br />';
    echo $th->getCode();
}

Wyświetli na ekranie treść:

treść
88

Wbudowane wyjątki w PHP

W ramach testu, stwórzmy nową funkcję „getException

public function getException()
{
    throw new \InvalidArgumentException("test", 1);
}

Złapać taki wyjątek możemy w sposób następujący:

try {
    $entity->getException();
} catch (\DomainException $de) {
    #wyjątek DomainException
} catch (\InvalidArgumentException $ia) {
    #wyjątek InvalidArgument
} catch (\Exception $ex) {
    #cała reszta
}

Jeśli funkcja „getException” zwróci wyjątek „InvalidArgumentException„, zostanie w kodzie klienckim wykonany kod zawarty w odpowiednim bloku catch. Jak się domyślasz będzie to w tej sytuacji kod który możemy umieścić w miejscu linii komentarza #wyjatek invalidArgument.

Gdyby funkcja „getException” zwróciła wyjątek „DomainException„, wykonał by się kod kliencki zawarty w miejscu w którym jest aktualnie linia #wyjatek DomainException.

Pamiętaj, blok catch przechwyci podany przez Ciebie wyjątek, ale również wyjątki pochodne.

W PHP mamy kilka wbudowanych wyjątków:

  • BadFunctionCallException
  • BadMethodCallException
  • DomainException
  • InvalidArgumentException
  • „LengthException
  • „LogicException
  • „OutOfBoundsException
  • OutOfRangeException
  • OverflowException
  • RangeException
  • RuntimeException
  • UnderflowException
  • UnexpectedValueException

Więcej o wbudowanych wyjątkach przeczytasz tutaj.

Własne wyjątki

Nic nie stoi na przeszkodzie, abyśmy stworzyli własne wyjątki, jeśli powyższa lista nas nie satysfakcjonuje.

Na początek należy stworzyć nową klasę którą w ramach testu możemy nazwać jakkolwiek, poniżej w ramach przykładu stworzyłem klasę „CustomExtends” która dziedziczy z „Exception„:

class CustomExtends extends \Exception 
{

}

Teraz zmodyfikujmy naszą funkcję „getException

public function getException()
{
    throw new CustomExtends("test", 1);
}

Wyjątek w ten sam sposób możemy przechwycić jak w poprzednich przykładach:

try {
    $entity->getException();
} catch (\CustomExtends $ce) {
    #wyjątek CustomExtends
} catch (\Exception $ex) {
    #cała reszta
}

Blok „finally

Możemy część kodu, który chcemy aby został wykonany zawsze gdy przechwycimy wyjątek (niezależnie od tego, jakiego typu owy wyjątek będzie) do bloku „finally„.

Nie powinniśmy bowiem powielać kodu wklejając go do wszystkich bloków „catch„, przykład:

try {
    $entity->getException();
} catch (\CustomExtends $ce) {
    #wyjątek CustomExtends
} catch (\Exception $ex) {
    #cała reszta
} finally {
    #kod wykonany po przechwyceniu wyjątku
}

Zakończenie

Cieszę się, że to już koniec tego wpisu. Pisałem go bardzo długo (brak motywacji).
Mam nadzieję że temat jest oczywisty i wszyscy doświadczeni programiści z wyjątków korzystają, a wpis ten przyda się początkującym.