Obsługa nieprzechwyconych wyjątków

Wyjątki są lepszą alternatywą dla obsługi błędów w aplikacjach od wywoływania trigger_error czy zwracania jakiegoś umówionego kodu w wypadku wystąpienia błędu. W związku z tym w ramach kontynuacji wątku z poprzedniego wpisu dziś kilka słów na temat wyjątków w PHP z nastawieniem na obsługę niewyłapanych przez catch wyjątków.

Obsługa wyjątków

Klasyczne podejście do obsługi wyjątków zakłada użycie throw i try/catch (od PHP 5.5 można również używać finally). Może to wyglądać w taki sposób:

<?php

$id = 1;
// kod wykonany bez wzgledu na wystapienie wyjatku
echo 'punkt 1' . PHP_EOL;
try
{
    echo 'punkt 2' . PHP_EOL;
    // kod wykonany bez wzgledu na wystapienie wyjatku
    if (is_int($id)) {
        echo 'punkt 3' . PHP_EOL;
        // zrob cos dla podanego ID
    } else {
        echo 'punkt 4' . PHP_EOL;
        throw new Exception('Błędne ID');
    }
    echo 'punkt 5' . PHP_EOL;
    // kod wykonany TYLKO jezeli nie bylo wyjatku
    
} catch (Exception $ex) {
    echo 'punkt 6' . PHP_EOL;
    // kod wykonany tylko w razie wystapienia wyjatku
    // np. "uratowanie zaistniałej sytuacji", zapis do logu, e-mail do admina
}
echo 'punkt 7' . PHP_EOL;
// kod wykonany bez wzgledu na wystapienie wyjatku

Dla wartości $id = 1; przebieg będzie wyglądał następująco, punkty: 1, 2, 3, 5, 7. Natomiast dla $id nie będącego liczbą całkowitą wykonanie podąży ścieżką: 1, 2, 4, 6, 7.

Powyższy przykład jest mocno uproszczony, ale można zaobserwować pewną rzecz – wyjątki pozwalają na łatwe poinformowanie o jakimś problemie jak również jego obsługę i w dodatku są luźno powiązane. W jednym fragmencie wykonanie kodu jest przerywane poprzez rzucenie wyjątku i nie ma potrzeby zajmowania się jego obsługą. Tym zajmuje się inny fragment kodu, który w bloku try/catch zamyka wywołania mogące sprawiać problemy.

Większego sensu nabiera to w momencie, kiedy w różnych warstwach aplikacji ma miejsce rzucenie wyjątku i jego złapanie. Dodatkowo wyjątki mogą być rzucane również z poziomu bloku catch, na przykład warstwa utrwalania danych łapie wyjątek związany z błędem zapisu do bazy, wykonuje cofnięcie transakcji i ponownie wrzuca wyjątek. Następnie jest on łapany w „wyższej” warstwie aplikacji i w ramach jego obsługi jest pokazywany komunikat dla użytkownika o „problemach technicznych z wykonaniem operacji”. Kolejnym przykładem jest stosowanie w aplikacji bibliotek zewnętrznych – autor biblioteki umieszcza w kodzie tylko rzucanie wyjątków. Natomiast złapaniem i obsługą tych wyjątków zajmuje się programista pracujący nad projektem, w którym dana biblioteka została wykorzystana.

Przechwytywanie nie wyłapanych wyjątków

W ramach ludzkiego błędu, zmiany w kodzie biblioteki lub innego powodu może się pojawić rzucenie wyjątku w miejscu, które nie było na to przygotowane. W efekcie zamiast strony/aplikacji w przeglądarce pojawia się komunikat:

Fatal error: Uncaught exception '[typ_wyjatku]' with message '[komunikat_wyjatku]' 
in [sciezka_pliku] on line [numer_linii]

Sposobem na takie sytuacje jest utworzenie i zarejestrowanie własnej funkcji obsługującej globalnie przypadki nie złapania rzuconych wyjątków – chodzi o funkcję set_exception_handler. Oprócz wywołania tej funkcji należy wcześniej zdefiniować funkcję, która będzie odpowiadała za faktyczną obsługę wyjątków. Poniżej przykład:

<?php
function obsluzWyjatek($wyjatek) {
  echo 'Wyjatek [' . get_class($wyjatek) .']: '. $wyjatek->getMessage() . PHP_EOL;
}
set_exception_handler('obsluzWyjatek');

echo 'punkt 1' . PHP_EOL;
throw new UnexpectedValueException('Podales zla wartosc');
echo 'punkt 2' . PHP_EOL;

Uruchomienie tego kodu spowoduje wyświetlenie:

punkt 1
Wyjatek [UnexpectedValueException]: Podales zla wartosc

Co istotne nie zostanie wykonany żaden kod po rzuceniu wyjątku (tutaj: wypisanie „punkt 2”) – w zwykłym podejściu „pole rażenia” wyjątku (czyli operacje, które nie zostaną wykonane jeżeli wyjątek się pojawi) jest określone blokiem try. natomiast w tym wypadku go nie ma, więc wykonanie całej reszty skryptu zostaje udaremnione. Wyjątkiem jest uruchomienie zdefiniowanej funkcji obsługującej nieprzechwycone wyjątki. Co się w niej znajdzie zależy od programisty, natomiast najważniejszy jest fakt, że można się przed takimi sytuacjami zabezpieczyć i zamiast błędu PHP pokazać użytkownikowi chociażby własną wersję „blue screen”. Można również nieco rozbudować obsługę wyjątków w funkcji w zależności od typu (w przykładzie został użyty wyjątek UnexpectedValueException). W przeciwieństwie do wielu instrukcji catch wyłapujących wyjątki o określonych, coraz bardziej ogólnych typach, funkcja obsługi jest jedna dla wszystkich typów.

Linki:

Dodaj komentarz