12. PHP – wyjątki, try…catch…


W trakcie wykonywania skryptów, często okazuje się, że natrafiamy na błędy. W takiej sytuacji skrypt kończy swoje działanie, a istnieją sytuacje, w których nie jest to konieczne np. praca na nieistniejącym pliku.

I tutaj z pomocą przychodzą nam wyjątki, które wyświetlają stosowną informację dla użytkownika w przypadku błędu i umożliwiają kontynuację pracy skryptu.

Mówimy, że wyjątki przechwytują błędy.

throw…, try…, catch…

Wyjątki są obiektami klasy Exception, które posiadają własny komunikat i kod błędu. Mogą one zostać wyłapane (try), rzucone (throw) i przechwycone (catch).

Wyobraźmy sobie, że mamy funkcję zwracającą odwrotność podanej liczby, która nie ma zabezpieczenia przed odwracaniem zera. Jak zabezpieczyć taki kod?

Przykład z nieobsłużonym błędem:

<?php

   function odwrotnosc($x){
        if(!$x){
           throw new Exception('Dzielenie przez 0'); //jeżeli nastąpi dzielenie przez 0 lub null to wyrzucimy wyjątek
        }
        return 1/$x;
    }

//przykład bez przechwycenia błędu
echo 'Odwrotność 5 to '.odwrotnosc(5).'<br>';
echo 'Odwrotność 0 to '.odwrotnosc(0).'<br>';//problematyczny fragment kodu, który generuje błąd
echo 'Odwrotność 7 to '.odwrotnosc(7).'<br>';//to nie zostanie wywołane
echo 'Skrypt kontynuuje swoją pracę';   //to też nie zostanie wykonane
?>

Wynik w przeglądarce:
Fatal error: Uncaught Exception: Dzielenie przez 0 in D:\xampp\htdocs\zajęcia\wyjatki.php:4 Stack trace: #0 D:\xampp\htdocs\zajęcia\wyjatki.php(20): odwrotnosc(0) #1 {main} thrown in D:\xampp\htdocs\zajęcia\wyjatki.php on line 4
 
A teraz rozwiązanie z obsługą błędów:
 
<?php
    function odwrotnosc($x){
        if(!$x){
            throw new Exception('Dzielenie przez 0');
        }
        return 1/$x;
    }

//przykład z przechwyconym błędem
    try{
        echo 'Odwrotność 5 to '.odwrotnosc(5).'<br>';
        echo 'Odwrotność 0 to '.odwrotnosc(0).'<br>';//problematyczny fragment kodu, który generuje wyjątek
        echo 'Odwrotność 7 to '.odwrotnosc(7).'<br>';//to nie zostanie wywołane   
    } catch (Exception $e){
        echo 'Przechwycony wyjątek: '.$e->getMessage().'<br>';
    }

    echo 'Skrypt kontynuuje swoją pracę';
?>
 
 

MOŻLIWE METODY WYJĄTKÓW

Zaglądając do wnętrza klasy Exception widzimy, że poza getMessage() zawiera ona znacznie więcej metod do wykorzystania.
 
final public  function getMessage();         // message of exception
final public  function getCode();               // code of exception
final public  function getFile();                  // source filename
final public  function getLine();                 // source line
final public  function getTrace();               // an array of the backtrace()
final public  function getPrevious();          // previous exception
final public  function getTraceAsString();  // formatted string of trace
 
Przykład użycia:
try{
        echo 'Odwrotność 5 to '.odwrotnosc(5).'<br>';
       echo 'Odwrotność 0 to '.odwrotnosc(0).'<br>' 
    } catch (Exception $e){
        echo 'Przechwycony wyjątek: '.$e->getMessage().' - metoda getMessage()<br>';
        echo 'Przechwycony wyjątek: '.$e->getCode().' - metoda getCode()<br>';
        echo 'Przechwycony wyjątek: '.$e->getFile().' - metoda getFile()<br>';
        echo 'Przechwycony wyjątek: '.$e->getLine().' - metoda getLine()<br>';
        echo 'Przechwycony wyjątek: '.var_dump($e->getTrace()).' - metoda getTrace()<br>';
        echo 'Przechwycony wyjątek: '.$e->getPrevious().' - metoda getPrevious()<br>';
        echo 'Przechwycony wyjątek: '.$e->getTraceAsString().' - metoda getTraceAsString()<br>';
    }

    echo 'Skrypt kontynuuje swoją pracę';
 

FINALLY

Finally służy do określenia kodu, który będzie wykonywany zawsze, niezależnie od wykrycia wyjątku.

try{
    echo 'Odwrotność 5 to '.odwrotnosc(5).'<br>';
} catch (Exception $e){
    echo 'Przechwycony wyjątek: '.$e->getMessage().' - metoda getMessage()<br>';
} finally{
    echo 'konstrukcja finally, wyjątek nie zaistniał<br><br>';
}

try{
    echo 'Odwrotność 0 to '.odwrotnosc(0).'<br>';
} catch (Exception $e){
    echo 'Przechwycony wyjątek: '.$e->getMessage().' - metoda getMessage()<br>';
} finally{
    echo 'konstrukcja finally, wyjątek zaistniał<br><br>';
}

echo 'Skrypt kontynuuje swoją pracę';

Należy jednak uważać podczas używania finally z  return!

Jeżeli w bloku try… catch… zostanie wykryta instrukcja return, blok finally i tak zostanie wykonany. Dodatkowo, jeżeli blok finally też zawiera return, to zwrócona zostanie zawartość finally.

function test_f_r(){
    try {
       throw new Exception;
    }catch(Exception $e){
        echo "złapany wyjątek<br>";
        return 1;
    }finally{
        echo "finally<br>";
        return 2;
    }
    return 3;
}
//możemy zauważyć, że pomimo tego, że wyjątek został przechwycony zwracana jest wartość z bloku finally
$out=test_f_r();
var_dump($out);
echo '<br>Skrypt kontynuuje swoją pracę';

 

TWORZENIE WŁASNYCH WYJĄTKÓW

Wyjątki możemy tworzyć wg własnego uznania. Wystarczy, że stworzymy klasę dziedziczącą po klasie Exception.

class MyException extends Exception{
    public function __construct(){
        parent::__construct("To jest mój autorski wyjątek");
    }
}

function test_MyException(){
    throw new MyException;
}

try{
    test_MyException();
}catch(MyException $e){
    echo "błąd: ".$e->getMessage();
}

 

Zadanie:

Napisz funkcję, która będzie zwracała długość ciągu. W przypadku przekazania pustego ciągu lub null  “rzuć” nowy wyjątek i wypisz “Ciąg jest pusty”. Za pomocą bloku try..catch.. przechwyć go.