7. Uitzonderingen en fouten
Wanneer een methode van een klasse een onherstelbare fout tegenkomt (bestand bestaat niet, geen verbinding met de database, netwerkverbinding buiten gebruik), geeft deze geen foutmelding weer op een console (bestand, database), maar genereert een uitzondering. Alle uitzonderingen zijn afgeleid van de klasse [\Exception]. Naast uitzonderingen genereert de interne werking van PHP ook fouten waarvan de basisklasse de klasse [\Error] is. Beide klassen implementeren de interface PHP [\Throwable].
7.1. De scriptboomstructuur

7.2. De interface [\Throwable]
De interface [\Throwable] is als volgt:

De functies van de methoden van de interface zijn als volgt:

7.3. De vooraf gedefinieerde uitzonderingen in PHP 7
PHP 7 definiëren verschillende klassen van uitzonderingen:

- in [1], de vooraf gedefinieerde uitzonderingen in PHP;
- in [2], de uitzonderingen van de bibliotheek SPL (Standaardbibliotheek PHP) van PHP 7. De bibliotheek SPL is een verzameling klassen en interfaces die bedoeld zijn om problemen op te lossen waarmee ontwikkelaars vaak worden geconfronteerd.
7.4. De vooraf gedefinieerde fouten in PHP 7
PHP 7 definieert verschillende foutklassen:

De klasse [\Error] is de bovenliggende klasse van alle vooraf gedefinieerde fouten in PHP. Met de klasse [ErrorException] kan een instantie van de klasse [\Error] worden ingekapseld in een instantie van de klasse [\Exception]. Hierdoor kan het foutbeheer worden gestandaardiseerd door uitsluitend uitzonderingen te verwerken.
7.5. Voorbeeld 1
Het eerste voorbeeld [exceptions-01.php] toont zowel fouten van het type PHP als een uitzondering:
<?php
// weergave van alle fouten
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// code --------
$var=[];
// onbekende sleutel
print $var["abcd"];
// delen door nul
$var=7/0;
var_dump($var);
// tabel met vaste grenzen
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index buiten de grenzen
$array[5]=8;
Opmerkingen
- regel 4: PHP wordt gevraagd om alle fouten te melden. De tweede parameter is het gewenste foutniveau:


- regel 5: er wordt gevraagd om de fouten op de console weer te geven;
- regel 9: er wordt een beroep gedaan op een niet-bestaand element van de array [$var];
- regel 11: er wordt gedeeld door nul;
- regel 14: er wordt een instantie van de klasse [SplFixedArray] aangemaakt. Met deze klasse kan een array met vaste grenzen en gehele indices worden aangemaakt;
- regel 18: er wordt een beroep gedaan op een niet-bestaand element van de array;
Resultaten
Opmerkingen
- regel 1 van de resultaten: het benaderen van een niet-bestaande sleutel van een array veroorzaakt een fout PHP van niveau [E_NOTICE]. Dit onderbreekt de uitvoering van het script niet;
- regel 3 van de resultaten: het delen van een getal door nul veroorzaakt een fout PHP van niveau [E_WARNING]. Dit onderbreekt de uitvoering van het script niet;
- regels 6-9 van de resultaten: het benaderen van een niet-bestaande index van een array [SplFixedArray] veroorzaakt een uitzondering van het type [RuntimeException] en onderbreekt de uitvoering van het script;
7.6. Uitzonderingen afhandelen
Het script [exceptions-02.php] laat zien hoe uitzonderingen moeten worden afgehandeld:
<?php
// alle fouten worden weergegeven
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// de code wordt omgeven door een try/catch
try {
$var = [];
// onbekende sleutel
print $var["abcd"];
// delen door nul
$var = 7 / 0;
var_dump($var);
// array met vaste grenzen
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index buiten het bereik
$array[5] = 8;
// controle
print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
// \Throwable is de interface die door de meeste fouten en uitzonderingen wordt geïmplementeerd
// de uitzondering wordt weergegeven
print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}
Opmerkingen
- dit is hetzelfde script als in de vorige paragraaf. Alleen is nu de code in de regels 8-19, die fouten kan veroorzaken, omgeven door een try/catch-structuur: als de code in de regels 8-21 een uitzondering of fout veroorzaakt (trigger), wordt deze afgehandeld door de catch-clausule in de regels 22-26;
- regel 22: de parameter van de clausule [catch] is het type uitzondering of fout dat we willen afhandelen. Door als type [\Throwable] op te geven, wat een interface is, geven we aan dat we elke klasse-instantie willen afhandelen die de interface [\Throwable] implementeert. Aangezien alle fout- en uitzonderingsklassen deze interface implementeren, verwerkt de clausule [catch] hier elke fout/uitzondering die in een klasse is ingekapseld;
- regel 19: de instructie die de fout veroorzaakt en de uitzondering genereert. Zodra er een uitzondering optreedt, wordt er doorgeschakeld naar de clausule [catch]. De code achter regel 19 wordt dus niet uitgevoerd;
Resultaten
Opmerkingen bij de resultaten
- regels 1 en 3: hier zien we de fouten op niveau [E_NOTICE] en [E_WARNING]. Deze fouten zijn geen uitzonderingen en worden daarom niet afgehandeld door de clausule [catch];
- regel 5: hier staat de foutmelding vermeld in de clausule [catch]. Er is dus een uitzondering opgetreden die is afgeleid van [\Exception] of een fout die is afgeleid van [\Error]. We zien hier dat het om de klasse [\RuntimeException] gaat;
7.7. Parameters van de clausule [catch]
Laten we het volgende script [exceptions-03.php] eens bekijken:
<?php
// alle fouten worden weergegeven
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// een array met vaste grenzen
$array = new \SplFixedArray(5);
try {
// index buiten het bereik
$array[5] = 8;
} catch (\Throwable $ex) {
// foutmelding weergeven
print "Erreur 1 : " . $ex->getMessage() . "\n";
}
try {
// index buiten de grenzen
$array[5] = 8;
} catch (\Exception $ex) {
// weergave van foutmelding
print "Erreur 2 : " . $ex->getMessage() . "\n";
}
try {
// waarde buiten het bereik
$array[5] = 8;
} catch (\RuntimeException $ex) {
// foutmelding weergeven
print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
// delen door 0
intdiv(5, 0);
} catch (\Throwable $ex) {
// foutmelding weergeven
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
// delen door 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// foutmelding weergeven
print "Erreur 5 : " . $ex->getMessage() . "\n";
}
try {
// delen door 0
intdiv(5, 0);
} catch (\Error $ex) {
// foutmelding weergeven
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
try {
// delen door 0
intdiv(5, 0);
} catch (\Exception $ex) {
// foutmelding weergeven
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
Opmerkingen
- regels 8-31: 3 verschillende manieren om de uitzondering af te handelen die wordt gegenereerd door het gebruik van een onjuiste index met de klasse [\SplFixedArray]. We hebben gezien dat deze fout een uitzondering van het type [RuntimeException] genereert;
- regel 12: verwerkt een fout van het type [\Throwable]. Dit is geldig, aangezien het type [RuntimeException] is afgeleid van het type [\Exception], dat de interface [\Throwable] implementeert;
- regel 20: verwerkt een fout van het type [\Exception]. Dit is geldig, aangezien het type [RuntimeException] is afgeleid van het type [\Exception];
- regel 28: verwerkt een fout van het type [\RuntimeException]. Dit is de aanbevolen methode, aangezien dit het exacte type is van de gegenereerde uitzondering;
- regels 32-62: 4 verschillende manieren om de uitzondering af te handelen die wordt gegenereerd door de functie [intdiv] wanneer hieraan een deler gelijk aan 0 wordt doorgegeven. De functie [ intdiv ( int $dividend , int $divisor ) : int] voert de gehele deling $dividend / $divisor uit. Wanneer de deler nul is, wordt de uitzondering [\DivisionByzeroError] gegenereerd;
- regel 35: elke fout die de interface [\Throwable] implementeert, wordt opgevangen. Dit is geldig;
- regel 43: het exacte type van de fout wordt opgevangen: dit is de voorkeursmethode;
- regel 51: het type [\Error] wordt opgevangen. Dit is geldig, aangezien de klasse [DivisionByzeroError] een uitbreiding is van de klasse [Error];
- regel 59: het type [\Exception] wordt onderschept. Dit is ongeldig omdat de klasse [DivisionByzeroError] geen verband houdt met de klasse [\Exception];
Resultaten
7.8. Clausule [finally]
De try/catch-structuur kan een derde element bevatten en zo een try/catch/finally-structuur worden. De code van de clausule [finally] wordt in de volgende twee gevallen uitgevoerd:
- de clausule [try] genereert geen uitzondering. Deze wordt dan volledig uitgevoerd, waarna de uitvoering van de code overgaat naar de clausule [finally], die volledig wordt uitgevoerd;
- de clausule [try] genereert een uitzondering. Deze wordt dan uitgevoerd tot aan de instructie die de uitzondering genereert. De uitvoering van de code gaat vervolgens over naar de clausule [catch], die volledig wordt uitgevoerd. Vervolgens gaat de uitvoering van de code over naar de clausule [finally], die volledig wordt uitgevoerd;
Ten slotte wordt de code van de clausule [finally] nog steeds uitgevoerd. Dit scenario is nuttig in het volgende geval:
- in de clausule [try] heeft de code resources (bestanden, databases, netwerkverbindingen, wachtrijen) verkregen. Over het algemeen zijn deze resources geheugenintensief. Ze moeten daarom zo snel mogelijk worden vrijgegeven (meestal wordt dit ‘sluiten’ genoemd);
- als de resources zijn verkregen in [try], wordt het vrijgeven ervan in [finally] uitgevoerd. Dit zorgt ervoor dat in alle gevallen (fout of geen fout) de verkregen resources aan het systeem worden teruggegeven;
Het volgende script, [exemples/exceptions/exceptions-04.php], laat zien hoe de clausule [finally] in verschillende situaties werkt:
<?php
// of creëert een uitzonderingsexemplaar
$e = new \Exception("Erreur…");
var_dump($e);
// eerste test
try {
print "Premier test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// tweede test
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// derde test
try {
print "Troisième test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
Opmerkingen bij de code
- regel 4: $e is een instantie van de vooraf gedefinieerde klasse [\Exception]. We zullen deze op verschillende plaatsen starten;
- regels 8-15: de uitzondering $e wordt gegenereerd in [try] (regel 10);
- regel 11: de uitzondering [\Exception] wordt opgevangen en de foutmelding ervan wordt naar de console geschreven;
- regels 13-15: de clausule [finally] schrijft een bericht. Zoals eerder vermeld, zou dit bericht altijd moeten worden geschreven, ongeacht of er al dan niet een fout is in [try];
- regels 18-24: er zit geen fout in de [try]. Ook hier zou men door moeten gaan naar de [finally];
- regels 27-34: er staat een instructie [return] in de try-blok en er is geen fout. Men kan zich dan afvragen of er wordt doorgegaan naar de clausule [finally]. Uit de uitvoering blijkt dat dit inderdaad het geval is;
Resultaten
Laten we nog een ander geval bekijken, [exceptions-05.php]:
<?php
// vierde test
try {
print "Quatrième test\n";
exit;
} finally {
print "Terminé\n";
}
Opmerkingen
- regel 6: de instructie [exit] stopt de uitvoering van het script onmiddellijk: de clausule [finally] wordt niet uitgevoerd;
- regels 4-9: een voorbeeld van try / catch / finally zonder de clausule [catch]. Dit is mogelijk;
Resultaten
7.9. Eigen uitzonderingsklassen maken
In een wat groter project is het handig om onderscheid te maken tussen de verschillende fouten door ze in verschillende uitzonderingsklassen in te kapselen. In het vorige script hebben we gezien dat elke uitzondering kan worden opgevangen door een [catch (\Throwable]-clausule. Dit wordt aanbevolen als men geen idee heeft van de onderschepte fout en de afhandeling voor alle fouten hetzelfde is. Dit is soms het geval, maar vaak moet de afhandeling worden aangepast aan het exacte type fout. Dan is het nodig om onderscheid te maken tussen de fouten.
Laten we het volgende script [exceptions-06.php] eens bekijken:
<?php
// we definiëren onze eigen uitzonderingsfamilie
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// of we gebruiken onze uitzonderingen
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
// eerste test
print ("premier test\n");
try {
// we genereren een Exception1-type
throw $e1;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// tweede test
print ("second test\n");
try {
// we genereren een 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";
}
// derde test
print ("troisième test\n");
try {
// er wordt een Exception1-type gegenereerd
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
// vierde test
print ("quatrième test\n");
try {
// er wordt een Exception2-type gegenereerd
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
Opmerkingen
- regels 4-10: we definiëren twee klassen, [Exception1] en [Exception2], die beide zijn afgeleid van de vooraf gedefinieerde klasse [\RuntimeException]. De body van deze klassen is leeg. Met andere woorden, ze worden alleen gebruikt vanwege hun typen: juist omdat ze verschillende typen hebben, kunnen we deze twee uitzonderingen onderscheiden in de clausules [catch];
- regels 13-16: we definiëren twee variabelen $e1 en $e2 met respectievelijk de typen [Exception1] en [Exception2];
- regels 20-29: er is een try/catch/catch-structuur. Hiermee kunnen verschillende soorten uitzonderingen worden afgehandeld met verschillende clausules [catch];
- regel 23: vangt uitzonderingen van het type [Exception1] op;
- regel 26: vangt uitzonderingen van het type [Exception2] op;
- regel 49: vangt uitzonderingen van het type [Exception1] of (|) [Exception2] op;
Resultaten
Toelichting bij de resultaten
- regels 1-17: de ‘inhoud’ van een uitzondering:
- regels 2-3: de foutmelding;
- regels 6-7: de foutcode;
- regels 8-9: de naam van het bestand waarin de uitzondering is opgetreden;
- regels 10-11: de regel waarop de uitzondering zich heeft voorgedaan;
- regels 15-16: de voorgaande uitzondering. Een uitzondering kan een andere uitzondering omvatten en zo een uitzonderingsstapel vormen. Met het attribuut [previous] kan deze stapel worden benut;
7.10. Een uitzondering opnieuw uitvoeren
Een uitzondering kan meerdere keren worden gegenereerd, zoals te zien is in het volgende script [exceptions-07.php]:
<?php
try {
try {
// er wordt een uitzondering gegenereerd
throw new \Exception("test");
} catch (\Exception $ex) {
// de opgevangen uitzondering wordt opnieuw gegooid
throw $ex;
} finally {
// we komen inderdaad in de finally terecht
print "finally 1\n";
}
} catch (\Exception $ex2) {
// de oorspronkelijke uitzondering wordt correct opgevangen
print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
// we komen inderdaad in de finally terecht
print "finally 2\n";
}
Opmerkingen
- regel 6: er wordt een uitzondering gegenereerd;
- regel 7: deze wordt opgevangen;
- regel 9: de uitzondering wordt opnieuw gegenereerd. Deze wordt vervolgens doorgegeven aan de try/catch/finally-structuur van het bovenliggende niveau;
- regel 14: deze wordt opnieuw opgevangen;
- regels 10-12: de uitvoering laat zien dat zelfs na de [throw] van regel 9, we wel degelijk in de clausule [finally] van de try/catch/finally terechtkomen;
Resultaten
7.11. Gebruik van een uitzonderingsstapel
Een uitzondering kan een andere uitzondering omvatten, die op haar beurt weer een andere uitzondering kan omvatten, waardoor uiteindelijk een stapel uitzonderingen ontstaat. Hier volgt een voorbeeld [exceptions-08.php]:
Opmerkingen
- regels 4-14: definiëren drie uitzonderingsklassen die zijn afgeleid van de vooraf gedefinieerde uitzondering [RuntimeException];
- regel 17: een instantie van de klasse [Exception3] is ingekapseld in een instantie van de klasse [Exception2], die op haar beurt is ingekapseld in een instantie van de klasse [Exception1]. De constructor die hier wordt gebruikt, is de constructor van de klasse [Exception]:
![]()
Met de derde parameter van de constructor kan een uitzondering worden ingekapseld. Dit kan nuttig zijn in het volgende scenario:
- er wordt een methode M gedefinieerd die een uitzondering van het type [Exception1] kan genereren, en uitsluitend van dit type, bijvoorbeeld omwille van compatibiliteit met een interface;
- maar in methode M kunnen ook andere soorten uitzonderingen optreden. Om een fout door te geven aan de code die methode M aanroept, zullen we deze uitzonderingen dan inkapselen in het type [Exception1], dat we vervolgens zullen afvuren. Zo gaat de informatie in de ingekapselde uitzondering – die de oorspronkelijke oorzaak van de fout was – niet verloren;
- de regels 20-27 laten zien hoe de stapel van interne uitzonderingen binnen een uitzondering wordt beheerd;
Resultaten
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…