We schrijven mooie code en in de meeste gevallen loopt de flow hiervan zoals verwacht. Er zijn echter situaties dat we uitzonderingen op deze flow willen maken. Bijvoorbeeld als we te maken hebben met input, waarover we geen invloed hebben. In die gevallen willen we niet de flow breken, maar een Exception geven.  

Een Exception is een uitzondering. Er gebeurd iets dat we niet hebben verwacht, dus gooien we een exception. De uitvoer van het programma wordt dan gestopt, tot de uitzondering is afgehandeld.

In het volgende voorbeeld hebben we een class voor een bankrekening. Hier staat een bepaald saldo op. We schrijven geld af te schrijven met de methode schrijfAf

class BankRekening {

   public $saldo = 0;

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

   public function schrijfAf( $bedrag ) {
      $nieuwSaldo = $this->saldo - $bedrag;
      if ( $nieuwSaldo => 0 ) {  
         $this->saldo = $nieuwSaldo;
      } 
   } 
}

Het afschrijven mag niet leiden tot een negatief saldo. Er wordt dus niets gedaan als het saldo negatief wordt. We moeten extra logica  toevoegen om de gebruiker hiervan op de hoogte te stellen.. 

<?php
$bankRekening = new BankRekening( 10 );

$bedrag = 20;
if( $bankRekening->saldo - $bedrag < 0 ) {
   echo 'Je hebt te weinig saldo';
} 
else {
   $bankRekening->schrijfAf( $bedrag ); 
   echo 'Afgeschreven!';
}

In dit (extreme) geval is extra logica buiten de class geschreven. Het functioneert, maar we zouden het eleganter kunnen maken met een Exception.

Exception geven

Een uitzondering moet gegooid worden. We doen dat door throw new Exception( 'bericht' ); te doen. Het mag ook een subclass van exception zijn. Bijvoorbeeld als er een eigen Exception geschreven is. Hoe specifieker hoe beter.

In ons voorbeeld van de schrijfAf methode kunnen we een Exception gooien als het saldo te laag is. 

   public function schrijfAf( $bedrag ) {
      $nieuwSaldo = $this->saldo - $bedrag;
      if ( $nieuwSaldo < 0 ) { 
         throw new Exception( 'Saldo is te laag' ); 
      } 

      $this->saldo = $nieuwSaldo;
   } 

We gooien nu de uitzondering als het saldo te laag is, maar er gebeurd nog niets mee. Behalve dan dat de verdere uitvoering van de code wordt gestopt, omdat er een Exception is die niet is afgevangen. 

Try en catch

Om eventuele fouten af te vangen, moeten we in de code aangeven dat iets uitgevoerd moet worden. We doen dit door try en catch. We  schrijven de uit te voeren code in een try block en vangen een eventuele opgegooide uitzondering. De uitvoering van de code gaat nu gewoon door, omdat de Exception is afgehandeld.

Hoe zit dit eruit in ons voorbeeld:

<?php
$bankRekening = new BankRekening( 10 );

try {
   $bedrag = 20;
   $bankRekening->schrijfAf( $bedrag ); 

   echo 'Afgeschreven!';
}
catch( Exception $e ) {
   echo $e->getMessage();
}  

Het is ook mogelijk om meerdere catch blokken te hebben. Dit is handig als we een subclass van Exception gebruiken.

Stel dat schrijfAf een BankRekeningException gooit in plaats van een Exception. Dan kunnen we ervoor kiezen om niets te doen met iets van een Exception zelf.

<?php
$bankRekening = new BankRekening( 10 );

try {
   $bedrag = 20;
   $bankRekening->schrijfAf( $bedrag ); 

   echo 'Afgeschreven!';
}
catch( BankRekeningException $e ) {
   echo $e->getMessage();
}
catch( Exception $e ) {
   // Schrijf de fout weg naar een logbestand.
}   

Door het gebruik van specifiekere uitzonderingen en het afvangen hiervan hebben we een betere controle op wat de gebruiker te zien krijgt. In het bovenstaande voorbeeld krijgt de gebruiker geen foutmelding te zien als er bijvoorbeeld iets mis is met de database.

Finally

Naast de try en catch is er ook nog finally. De inhoud hiervan wordt altijd uitgevoerd. In het  volgende voorbeeld wordt altijd het saldo getoond.

<?php
$bankRekening = new BankRekening( 10 );

try {
   $bedrag = 20;
   $bankRekening->schrijfAf( $bedrag ); 

   echo 'Afgeschreven!';
}
catch( BankRekeningException $e ) {
   echo $e->getMessage();
}
finally {
   echo 'Je saldo is op dit moment: ' . $bankRekening->saldo
}