Skip to content

7. Wyjątki i błędy

Gdy metoda klasy napotka błąd nieodwracalny (nieistniejący plik, brak połączenia z bazą danych, brak połączenia sieciowego), nie wyświetla ona błędu na konsoli (plik, baza danych), lecz generuje wyjątek. Wszystkie wyjątki dziedziczą po klasie [\Exception]. Oprócz wyjątków wewnętrzne działanie klasy PHP generuje również błędy, których klasą bazową jest klasa [\Error]. Obie klasy implementują interfejs PHP [\Throwable].

7.1. Struktura drzewa skryptów

Image

7.2. Interfejs [\Throwable]

Interfejs [\Throwable] ma następujący wygląd:

Image

Rola metod interfejsu jest następująca:

Image

7.3. Wyjątki zdefiniowane w PHP 7

PHP 7 definiują kilka klas wyjątków:

Image

  • w [1] wyjątki zdefiniowane w PHP;
  • W bibliotece [2] wyjątki z biblioteki SPL (Standardowa biblioteka PHP) z PHP 7. Biblioteka SPL stanowi zbiór klas i interfejsów przeznaczonych do rozwiązywania problemów, z którymi często borykają się programiści.

7.4. Błędy zdefiniowane w bibliotece PHP 7

PHP 7 definiuje kilka klas błędów:

Image

Klasa [\Error] jest klasą nadrzędną wszystkich błędów zdefiniowanych w PHP. Klasa [ErrorException] umożliwia umieszczenie instancji klasy [\Error] w instancji klasy [\Exception]. Pozwala to na ujednolicenie obsługi błędów poprzez przetwarzanie wyłącznie wyjątków.

7.5. Przykład 1

Pierwszy przykład [exceptions-01.php] przedstawia zarówno błędy PHP, jak i wyjątek:


<?php

// wyświetlenie wszystkich błędów
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// kod --------
$var=[];
// nieznany klucz
print $var["abcd"];
// dzielenie przez zero
$var=7/0;
var_dump($var);
// tablica o stałych granicach
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// indeks poza granicami
$array[5]=8;

Komentarze

  • wiersz 4: funkcja PHP otrzymuje polecenie zgłaszania wszystkich błędów. Drugim parametrem jest żądany poziom błędów:

Image

Image

  • wiersz 5: żądamy wyświetlenia błędów na konsoli;
  • wiersz 9: następuje próba dostępu do nieistniejącego elementu tablicy [$var];
  • wiersz 11: wykonano dzielenie przez zero;
  • wiersz 14: tworzona jest instancja klasy [SplFixedArray]. Klasa ta umożliwia utworzenie tablicy o stałych granicach i indeksach całkowitoliczbowych;
  • wiersz 18: następuje dostęp do nieistniejącego elementu tablicy;

Wyniki

1
2
3
4
5
6
7
8
9
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 9

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 11
float(INF)

Fatal error: Uncaught RuntimeException: Index invalid or out of range in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php:18
Stack trace:
#0 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 18

Komentarze

  • wiersz 1 wyników: próba uzyskania dostępu do nieistniejącego klucza tablicy powoduje błąd PHP o poziomie [E_NOTICE]. Nie powoduje to przerwania wykonywania skryptu;
  • wiersz 3 wyników: dzielenie liczby przez zero powoduje błąd PHP o poziomie [E_WARNING]. Nie przerywa to wykonywania skryptu;
  • wiersze 6–9 wyników: próba dostępu do nieistniejącego indeksu tablicy o numerze [SplFixedArray] powoduje wyjątek typu [RuntimeException] i przerywa wykonywanie skryptu;

7.6. Obsługa wyjątków

Skrypt [exceptions-02.php] pokazuje, jak obsługiwać wyjątki:


<?php

// wyświetla się wszystkie błędy
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// kod jest otoczony blokiem try / catch
try {
  $var = [];
  // nieznany klucz
  print $var["abcd"];
  // dzielenie przez zero
  $var = 7 / 0;
  var_dump($var);
  // tablica o stałych granicach
  $array = new \SplFixedArray(5);
  $array[1] = 2;
  $array[4] = "foo";
  // indeks poza zakresem
  $array[5] = 8;
  // sprawdzanie
  print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
  // \Throwable to interfejs implementowany przez większość błędów i wyjątków
  // wyświetla się wyjątek
  print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}

Komentarze

  • skrypt jest tym samym, który został przedstawiony w poprzednim akapicie. Tylko że teraz kod w wierszach 8–19, który może powodować błędy, został otoczony strukturą try / catch: jeśli kod w wierszach 8–21 wywoła wyjątek lub błąd, zostanie on obsłużony przez klauzulę catch w wierszach 22–26;
  • wiersz 22: parametrem klauzuli [catch] jest typ wyjątku lub błędu, który chcemy obsłużyć. Ustawiając jako typ [\Throwable], który jest interfejsem, wskazujemy, że chcemy obsłużyć każdą instancję klasy implementującej interfejs [\Throwable]. Ponieważ wszystkie klasy błędów i wyjątków implementują ten interfejs, klauzula [catch] obsługuje tutaj każdy błąd/wyjątek zawarty w klasie;
  • wiersz 19: instrukcja, która wywołuje błąd i powoduje wystąpienie wyjątku. Gdy tylko wystąpi wyjątek, następuje przejście do klauzuli [catch]. Kod znajdujący się za wierszem 19 nie zostanie zatem wykonany;

Wyniki

1
2
3
4
5
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 10

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 12
float(INF)
erreur, message : Index invalid or out of range, type : RuntimeException

Komentarz do wyników

  • wiersze 1 i 3: występują błędy o poziomie [E_NOTICE] i [E_WARNING]. Błędy te nie są wyjątkami, a zatem nie są obsługiwane przez klauzulę [catch];
  • wiersz 5: pojawia się komunikat o błędzie zapisany w klauzuli [catch]. Oznacza to, że wystąpił wyjątek wywodzący się z [\Exception] lub błąd wywodzący się z [\Error]. Widzimy tutaj, że chodzi o klasę [\RuntimeException];

7.7. Parametry klauzuli [catch]

Przyjrzyjmy się poniższemu skryptowi [exceptions-03.php]:


<?php

// wyświetlane są wszystkie błędy
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");

// tablica o stałych granicach
$array = new \SplFixedArray(5);
try {
  // indeks poza granicami
  $array[5] = 8;
} catch (\Throwable $ex) {
  // wyświetlanie komunikatu o błędzie
  print "Erreur 1 : " . $ex->getMessage() . "\n";
}

try {
  // wskaźnik poza granicami
  $array[5] = 8;
} catch (\Exception $ex) {
  // wyświetlanie komunikatu o błędzie
  print "Erreur 2 : " . $ex->getMessage() . "\n";
}

try {
  // wartość poza zakresem
  $array[5] = 8;
} catch (\RuntimeException $ex) {
  // wyświetlenie komunikatu o błędzie
  print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
  // dzielenie przez 0
  intdiv(5, 0);
} catch (\Throwable $ex) {
  // wyświetlenie komunikatu o błędzie
  print "Erreur 4 : " . $ex->getMessage() . "\n";
}

try {
  // dzielenie przez 0
  intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
  // wyświetlenie komunikatu o błędzie
  print "Erreur 5 : " . $ex->getMessage() . "\n";
}

try {
  // dzielenie przez 0
  intdiv(5, 0);
} catch (\Error $ex) {
  // wyświetlenie komunikatu o błędzie
  print "Erreur 6 : " . $ex->getMessage() . "\n";
}

try {
  // dzielenie przez 0
  intdiv(5, 0);
} catch (\Exception $ex) {
  // wyświetlenie komunikatu o błędzie
  print "Erreur 6 : " . $ex->getMessage() . "\n";
}

Komentarze

  • wiersze 8–31: trzy różne sposoby obsługi wyjątku generowanego przez użycie nieprawidłowego indeksu w klasie [\SplFixedArray]. Widzieliśmy, że błąd ten generuje wyjątek typu [RuntimeException];
    • wiersz 12: obsługuje błąd typu [\Throwable]. Jest to poprawne, ponieważ typ [RuntimeException] wywodzi się z typu [\Exception], który implementuje interfejs [\Throwable];
    • wiersz 20: obsługuje błąd typu [\Exception]. Jest to poprawne, ponieważ typ [RuntimeException] wywodzi się z typu [\Exception];
    • wiersz 28: obsługuje błąd typu [\RuntimeException]. Jest to preferowana metoda, ponieważ jest to dokładny typ wygenerowanego wyjątku;
  • wiersze 32–62: 4 różne sposoby obsługi wyjątku generowanego przez funkcję [intdiv] w przypadku przekazania jej dzielnika równego 0. Funkcja [ intdiv ( int $dividend , int $divisor ) : int] wykonuje dzielenie całkowitoliczbowe $dividend / $divisor. Gdy dzielnik wynosi zero, generowany jest wyjątek [\DivisionByzeroError];
    • wiersz 35: przechwytuje się każdy błąd implementujący interfejs [\Throwable]. Jest to poprawne;
    • wiersz 43: przechwytuje się dokładny typ błędu: jest to preferowana metoda;
    • wiersz 51: przechwytuje się typ [\Error]. Jest to poprawne, ponieważ klasa [DivisionByzeroError] dziedziczy po klasie [Error];
    • wiersz 59: przechwytuje się typ [\Exception]. Jest to nieprawidłowe, ponieważ klasa [DivisionByzeroError] nie ma żadnego związku z klasą [\Exception];

Wyniki

Erreur 1 : Index invalid or out of range
Erreur 2 : Index invalid or out of range
Erreur 3 : Index invalid or out of range
Erreur 4 : Division by zero
Erreur 5 : Division by zero
Erreur 6 : Division by zero

Fatal error: Uncaught DivisionByZeroError: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php:58
Stack trace:
#0 C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php(58): intdiv(5, 0)
#1 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php on line 58

7.8. Klauzula [finally]

Konstrukcja try / catch może zawierać trzeci element i przybrać postać try / catch / finally. Kod klauzuli [finally] jest wykonywany w obu poniższych przypadkach:

  • klauzula [try] nie generuje wyjątku. Jest wówczas wykonywana w całości, a następnie wykonanie kodu przechodzi do klauzuli [finally], która jest wykonywana w całości;
  • klauzula [try] generuje wyjątek. Jest ona wówczas wykonywana do instrukcji, która generuje wyjątek. Następnie wykonanie kodu przechodzi do klauzuli [catch], która jest wykonywana w całości. Następnie wykonanie kodu przechodzi do klauzuli [finally], która jest wykonywana w całości;

Ostatecznie kod klauzuli [finally] jest nadal wykonywany. Ten scenariusz jest przydatny w następującym przypadku:

  • w klauzuli [try] kod uzyskał zasoby (pliki, bazy danych, połączenia sieciowe, kolejki). Zazwyczaj zasoby te są kosztowne pod względem pamięci. Należy je zatem zwolnić (najczęściej mówi się o „zamknięciu”) tak szybko, jak to możliwe;
  • jeśli zasoby zostały pobrane w scenariuszu [try], ich zwrot zostanie umieszczony w scenariuszu [finally]. Dzięki temu mamy pewność, że w każdym przypadku (niezależnie od tego, czy wystąpił błąd, czy nie) pobrane zasoby zostaną zwrócone do systemu;

Poniższy skrypt [exemples/exceptions/exceptions-04.php] ilustruje działanie klauzuli [finally] w różnych sytuacjach:


<?php

// lub tworzy instancję wyjątku
$e = new \Exception("Erreur…");    
var_dump($e);

// pierwszy test
try {
  print "Premier test\n";
  throw $e;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

// drugi test
try {
  print "Second test\n";
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

// trzeci test
try {
  print "Troisième test\n";
  return;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

Komentarze do kodu

  • wiersz 4: $e jest instancją predefiniowanej klasy [\Exception]. Zostanie ona wywołana w różnych miejscach;
  • wiersze 8–15: wyjątek $e jest wywoływany w [try] (wiersz 10);
  • wiersz 11: wyjątek [\Exception] zostaje przechwycony, a komunikat o błędzie zapisany na konsoli;
  • wiersze 13–15: klauzula [finally] zapisuje komunikat. Zgodnie z tym, co zostało powiedziane wcześniej, komunikat ten powinien być zapisywany zawsze, niezależnie od tego, czy w [try] wystąpił błąd, czy nie;
  • wiersze 18–24: w [try] nie ma błędu. W tym miejscu również powinno nastąpić przejście do [finally];
  • wiersze 27–34: w bloku try znajduje się instrukcja [return] i nie ma błędu. Można zatem zastanawiać się, czy nastąpi przejście do klauzuli [finally]. Wykonanie pokazuje, że tak;

Wyniki

1
2
3
4
5
6
7
Premier test
Erreur…
Terminé
Second test
Terminé
Troisième test
Terminé

Przyjrzyjmy się innemu przypadkowi [exceptions-05.php]:


<?php

// czwarty test
try {
  print "Quatrième test\n";
  exit;
} finally {
  print "Terminé\n";
}

Komentarze

  • wiersz 6: instrukcja [exit] natychmiast zatrzymuje wykonywanie skryptu: klauzula [finally] nie jest wykonywana;
  • wiersze 4–9: przykład konstrukcji try / catch / finally bez klauzuli [catch]. Jest to możliwe;

Wyniki

Quatrième test

7.9. Tworzenie własnych klas wyjątków

W nieco większym projekcie warto rozróżniać różne błędy, umieszczając je w różnych klasach wyjątków. W poprzednim skrypcie widzieliśmy, że każdy wyjątek może zostać przechwycony przez klauzulę [catch (\Throwable]. Jest to zalecane, jeśli nie mamy pojęcia, jaki błąd został przechwycony, a sposób obsługi jest taki sam dla wszystkich błędów. Czasami tak właśnie jest, ale często trzeba dostosować sposób obsługi do konkretnego rodzaju błędu. W takim przypadku należy rozróżnić poszczególne błędy.

Przyjrzyjmy się poniższemu skryptowi [exceptions-06.php]:


<?php

// definiujemy własną rodzinę wyjątków
class Exception1 extends \RuntimeException {
  
}

class Exception2 extends \RuntimeException {
  
}

// lub korzystamy z naszych wyjątków
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);

// pierwszy test
print ("premier test\n");
try {
  // rzucamy wyjątek typu Exception1
  throw $e1;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}

// drugi test
print ("second test\n");
try {
  // wywołujemy wyjątek typu Exception2
  throw $e2;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}

// trzeci test
print ("troisième test\n");
try {
  // wywołano wyjątek typu Exception1
  throw $e1;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}

// czwarty test
print ("quatrième test\n");
try {
  // wywołano wyjątek typu Exception2
  throw $e2;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}

Komentarze

  • wiersze 4–10: definiujemy dwie klasy [Exception1] i [Exception2], obie wywodzące się z predefiniowanej klasy [\RuntimeException]. Treść tych klas jest pusta. Innymi słowy, wykorzystuje się je wyłącznie ze względu na ich typy: właśnie dzięki temu, że mają one różne typy, będziemy mogli rozróżnić te dwa wyjątki w klauzulach [catch];
  • wiersze 13–16: definiujemy dwie zmienne $e1 i $e2 o typach odpowiednio [Exception1] i [Exception2];
  • wiersze 20–29: zastosowano strukturę try / catch / catch. Pozwala to na obsługę różnych typów wyjątków za pomocą różnych klauzul [catch];
  • wiersz 23: przechwytuje wyjątki typu [Exception1];
  • wiersz 26: przechwytuje wyjątki typu [Exception2];
  • wiersz 49: przechwytuje wyjątki typu [Exception1] lub (|) [Exception2];

Wyniki

object(Exception1)#1 (7) {
  ["message":protected]=>
  string(10) "Erreur1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(13)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
object(Exception2)#2 (7) {
  ["message":protected]=>
  string(10) "Erreur2…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(15)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
premier test
Exception 1
Erreur1…
second test
Exception 2
Erreur2…
troisième test
Exception 1 ou 2
Erreur1…
quatrième test
Exception 1 ou 2
Erreur2…

Komentarze do wyników

  • wiersze 1–17: „treść” wyjątku:
    • wiersze 2–3: komunikat o błędzie;
    • wiersze 6–7: kod błędu;
    • wiersze 8–9: nazwa pliku, w którym wystąpił wyjątek;
    • wiersze 10–11: wiersz, w którym wystąpił wyjątek;
    • wiersze 15–16: poprzedni wyjątek. Wyjątek może zawierać w sobie inny wyjątek, tworząc w ten sposób stos wyjątków. Atrybut [previous] umożliwi wykorzystanie tego stosu;

7.10. Ponowne wywołanie wyjątku

Wyjątek może zostać wywołany wielokrotnie, jak pokazuje poniższy skrypt [exceptions-07.php]:


<?php

try {
  try {
    // wywołujemy wyjątek
    throw new \Exception("test");
  } catch (\Exception $ex) {
    // ponownie rzucamy przechwycony wyjątek
    throw $ex;
  } finally {
    // program poprawnie przechodzi do bloku finally
    print "finally 1\n";
  }
} catch (\Exception $ex2) {
  // początkowy wyjątek jest prawidłowo przechwycony
  print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
  // program przejdzie do bloku finally
  print "finally 2\n";
}

Komentarze

  • wiersz 6: wywołujemy wyjątek;
  • wiersz 7: przechwytuje się go;
  • wiersz 9: ponownie wywołuje się wyjątek. Przechodzi on wówczas do struktury try / catch / finally wyższego poziomu;
  • wiersz 14: ponownie przechwytuje się wyjątek;
  • wiersze 10–12: wykonanie pokazuje, że nawet po wywołaniu [throw] w wierszu 9 program przechodzi do klauzuli [finally] w bloku try / catch / finally;

Wyniki

1
2
3
finally 1
test dans try / catch / finally externe
finally 2

7.11. Wykorzystanie stosu wyjątków

Wyjątek może zawierać w sobie inny wyjątek, który z kolei może zawierać kolejny, tworząc w ten sposób stos wyjątków. Oto przykład [exceptions-08.php]:

<?php

// definiujemy własną rodzinę wyjątków
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// lub korzystamy z naszych wyjątków
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// obsługa bieżącego wyjątku
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
   // poprzedni wyjątek
  $e = $e->getPrevious();
   // komunikat o błędzie
  print $e->getMessage() . "\n";
}

Komentarze

  • wiersze 4–14: definiują trzy klasy wyjątków wywodzące się z predefiniowanego wyjątku [RuntimeException];
  • wiersz 17: instancja klasy [Exception3] jest zawarta w instancji klasy [Exception2], która z kolei jest zawarta w instancji klasy [Exception1]. W tym przypadku użyto konstruktora klasy [Exception]:

Image

Trzeci parametr konstruktora pozwala na enkapsulację wyjątku. Może to być przydatne w następującym scenariuszu:

  • definiujemy metodę M, która może wygenerować wyjątek typu [Exception1] i wyłącznie tego typu, na przykład ze względu na kompatybilność z interfejsem;
  • jednak w metodzie M mogą wystąpić inne typy wyjątków. Aby zgłosić błąd do kodu wywołującego metodę M, należy zatem enkapsulować te wyjątki w typie [Exception1], który następnie zostanie wywołany. Pozwala to uniknąć utraty informacji zawartej w enkapsulowanym wyjątku, który był pierwotną przyczyną błędu;
  • wiersze 20–27 pokazują, jak zarządzać stosem wyjątków wewnętrznych w ramach jednego wyjątku;

Wyniki


object(Exception1)#1 (7) {
  ["message":protected]=>
  string(11) "Erreur 1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(1)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
  ["line":protected]=>
  int(17)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  object(Exception2)#2 (7) {
    ["message":protected]=>
    string(11) "Erreur 2…"
    ["string":"Exception":private]=>
    string(0) ""
    ["code":protected]=>
    int(2)
    ["file":protected]=>
    string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
    ["line":protected]=>
    int(17)
    ["trace":"Exception":private]=>
    array(0) {
    }
    ["previous":"Exception":private]=>
    object(Exception3)#3 (7) {
      ["message":protected]=>
      string(11) "Erreur 3…"
      ["string":"Exception":private]=>
      string(0) ""
      ["code":protected]=>
      int(0)
      ["file":protected]=>
      string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
      ["line":protected]=>
      int(17)
      ["trace":"Exception":private]=>
      array(0) {
      }
      ["previous":"Exception":private]=>
      NULL
    }
  }
}
Erreur 1…
Erreur 2…
Erreur 3…