Les exceptions sont une autre façon de traiter le déclenchement des erreurs et leur traitement.

Pour signaler qu'une erreur s'est produite, une méthode lance une exception (ou lève une exception). Le code appelant la méthode doit capturer l'exception et traiter le problème s'il en est capable.

### Principe

Quand une exception est lancée (mot-clé `throw`), l'interpréteur PHP arrête l'exécution normale du script et transmet l'exception de bloc de code appelant en bloc de code appelant.

Si PHP trouve un bloc capturant l'exception (mot-clé `catch`), l'exception est traitée dans le code de ce bloc puis le script continue son exécution juste après le bloc catch.

Si aucun bloc catch n'est rencontré, l'interpréteur PHP affiche le message d'erreur de l'exception et arrête le script.

Une exception est une instance de la classe `Exception`. Cette classe est native dans PHP et se compose des membres suivants :

    class Exception {
    	/* Propriétés */
    	protected string $message ;
    	protected int $code ;
    	protected string $file ;
    	protected int $line ;

    	/* Méthodes */
    	public __construct([$msg = "" [,$num = 0 [, $prev = NULL ]]])
    	final public string getMessage()
    	final public Exception getPrevious()
    	final public mixed getCode()
    	final public string getFile()
    	final public int getLine()
    	final public array getTrace()
    	final public string getTraceAsString()
    	public string __toString()
    	final private void __clone()
    }

#### Lancer une exception

Pour lancer une exception on utilise le mot-clé `throw` suivi de l'instanciation d'une nouvelle `Exception`.

    thow new Exception("Le message d'erreur à afficher");

PHP ne permet pas de lancer d'exception dans un destructeur. Si c'est le cas, le script s'arrête avec une erreur fatale "Exception thrown without a stack frame in Unknown on line 0"

Exemple : lancer une exception
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; public function __construct($p1, $p2, $p3){ $p1 = trim($p1); if ($p1 == '') { throw new Exception('Modèle invalide'); } $this->modele = $p1; $ok = array(4, 6, 12); if (!estEntier($p3) || !in_array($p3, $ok)) { throw new Exception('Nombre de cordes invalide'); } $this->cordes = $p3; $this->setPrix($p2); } public function setPrix($val) { if (!is_numeric($val) || $val < 0) { throw new Exception('Valeur de prix invalide'); } $this->prix = $val; } } // Fin de la classe Guitare htmlDebut('Lancer une exception'); $guitare = new Guitare('', 758, 12); // Essayer les instanciations suivantes : // $guitare = new Guitare('Gretsch Falcon', 'prix', 12); // $guitare = new Guitare('Gretsch Falcon', 758, 24); htmlFin(); ?>

Comme vous le voyez en testant le code, nous avons une erreur qui nous dit que nous avons lancé une exception sans la capturer. Voyons donc comment traiter cette exception.

#### Capturer une exception

Pour capturer une exception on utilise une structure de contrôle `try`, `catch` (et éventuellement `finally`).

capturer une exception

A noter la syntaxe particulière du bloc `catch` où `catch` est pratiquement considéré comme une fonction à laquelle on passe en paramètre une référence sur l'instance de l'`Exception` à traiter.
A l'intérieur de ce bloc, on va pouvoir invoquer diverses méthodes de la classe `Exception` si on veut réaliser des affichages sur les causes de l'erreur.

L'exemple suivant utilise les méthodes getMessage(), getLine(), getFile(), getTrace() et __toString() de la classe prédéfinie Exception.

Exemple : capturer une exception
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; public function __construct($p1, $p2, $p3){ $p1 = trim($p1); if ($p1 == '') { throw new Exception('Modèle invalide'); } $this->modele = $p1; $ok = array(4, 6, 12); if (!estEntier($p3) || !in_array($p3, $ok)) { throw new Exception('Nombre de cordes invalide'); } $this->cordes = $p3; $this->setPrix($p2); } public function setPrix($val) { if (!is_numeric($val) || $val < 0) { throw new Exception('Valeur de prix invalide'); } $this->prix = $val; } } // Fin de la classe Guitare htmlDebut('Lancer une exception'); echo '... Déroulement du script ...'; try { $guitare = new Guitare('', 758, 12); // Essayer les instanciations suivantes : // $guitare = new Guitare('Gretsch Falcon', 'prix', 12); // $guitare = new Guitare('Gretsch Falcon', 758, 24); } catch (Exception $e) { echo '<hr><h4>Exception capturée !</h4>', '<br><b>Message : </b>',$e->getMessage(), '<br><b>Ligne : </b>', $e->getLine(), '<br><b>Fichier : </b>', $e->getFile(), '<br><b>Appels : </b>', infoTableau($e->getTrace()), '<br><b>Texte : </b>', $e->__toString(), '<hr>'; } echo '... Le script continue son exécution ...'; htmlFin(); ?>

### Etendre la classe Exception de PHP

PHP permet d'étendre la classe `Exception` prédéfinie dans le langage. Nous pouvons ainsi écrire nos propres exceptions, adaptées à nos développements particuliers, et ne gérer que ces exceptions "prévisibles" (comme par exemple les mauvaises valeurs passées en paramètres).

Toutes les méthodes de la classe `Exception` ne sont pas redéfinissable ou surchargeables. Seules les méthodes non finales peuvent être réécrites, soit __construct() et __toString().

Dans l'exemple suivant, notre classe `MonException` surchage le constructeur de la classe `Exception` pour accepter 2 paramètres :
- le 1er paramètre peut être soit un numéro, soit un texte. Le numéro donnera accès à un message d'erreur (attribut de notre classe `MonException`). Procéder ainsi permet de stocker les messages d'erreur dans un endroit bien repéré et facilite par exemple les traductions des textes. En acceptant malgré tout un texte comme paramètre, on laisse une ouverture aux utilisateurs de notre classe.
- le 2éme paramètre est un booléen qui indique si l'exception est bloquante ou non. Par défaut il a la valeur `true` et arrête le script après affichage du message d'erreur.
La méthode __toString() est redéfinie pour afficher les messages d'erreur de 2 façons :
- si nous sommes en phase de développement et de debuggage, l'affichage sera détaillé.
- si nous sommes en phase de production, l'affichage sera très réduit.

Il ne faut jamais lancer d'exception dans la classe qui étend `Exception` car on provoquerait alors une boucle infinie. Si c'est le cas, le script s'arrête avec une erreur fatale "Exception thrown without a stack frame in Unknown on line 0"

Exemple : étendre la classe Exception
<?php ob_start(); require('bib_fonctions.php'); class Guitare { private $modele, $prix, $cordes, $remise = 0; public function __construct($p1, $p2, $p3){ $p1 = trim($p1); if ($p1 == '') { throw new MonException(1); } $this->modele = $p1; $ok = array(4, 6, 12); if (!estEntier($p3) || !in_array($p3, $ok)) { throw new MonException(2); } $this->cordes = $p3; $this->setPrix($p2); } public function setPrix($val) { if (!is_numeric($val) || $val < 0) { throw new MonException(3, false); } $this->prix = $val; } } // Fin de la classe Guitare class MonException extends Exception { public static $is_dev = false; protected $stop; protected $textes = array( 'Erreur inconnue', 'Modèle invalide', 'Nombre de cordes invalide', 'Prix invalide'); public function __construct($p1, $p2 = true) { // Pour simplifier, pas de vérif des paramètres $msg = (is_numeric($p1)) ? $this->textes[$p1] : $p1; parent::__construct($msg, 0); $this->stop = $p2; } public function __toString() { if (! self::$is_dev) { if ($this->stop) { ob_end_clean(); exit('Site inaccessible'); } return; } echo '<hr><b>Exception capturée : </b>', $this->getMessage(), str_replace('#', '<br>#', $this->getTraceAsString()); if ($this->stop) exit('<hr>'); } } // fin de la classe MonException htmlDebut('Etendre la classe Exception'); MonException::$is_dev = true; echo '... Déroulement du script ...'; try { $guitare = new Guitare('', 758, 12); // Essayer les instanciations suivantes : // $guitare = new Guitare('Gretsch Falcon', 'prix', 12); // $guitare = new Guitare('Gretsch Falcon', 758, 24); // Essayer aussi en commentant la ligne 64 } catch (MonException $e) { $e->__toString(); } echo '... Le script continue son exécution ...'; htmlFin(); ?>

Le code de la classe `MonException` sera placé dans le fichier `monexception.class.php` et inclus avec require dans le script principal.

### Exercice : exceptions

Reprenez le code de la classe `Livre` ci-dessous avec le mécanisme des exceptions pour gérer les erreurs. Utilisez la classe du fichier `monexception.class.php` ou écrivez votre propre classe.

    // Définition de la classe Livre
    class Livre {
    	const CAT_METHODE = 'Méthode';
    	const CAT_PARTITION = 'Partition';
    	const CAT_BIO = 'Biographie';
    	const CAT_AUTRE = 'Autre';

    	private $titre, $auteur, $pages, $prix, $cat;

    	public function __construct($p1, $p2, $p3, $p4, $p5) {
    		$this->titre = $this->verifChaine($p1, 'titre');
    		$this->auteur = $this->verifChaine($p2, 'auteur');
    		if (is_numeric($p3) && estEntre($p3, 10, 1000)) {
    			$this->pages = $p3;
    		} else {
    			$this->afficheErreur($p3, 'pages');
    		}
    		if (is_numeric($p4) && estEntre($val, 0, 1000)) {
    			$this->prix = $p4;
    		} else {
    			$this->afficheErreur($p4, 'prix');
    		}
    		$oks = array(self::CAT_METHODE, self::CAT_PARTITION,
    					 self::CAT_BIO, self::CAT_AUTRE);
    		if (in_array($p5, $oks)) {
    			$this->cat = $p5;
    		} else {
    			$this->afficheErreur($p5, 'cat');
    		}
    	}
    	private function verifChaine($val, $attr) {
    		$val = trim($val);
    		($val == '') && $this->afficheErreur($val, $attr);
    		return $val;
    	}
    	private function afficheErreur($val, $attr) {
    		exit("<hr>Valeur '<b>$val</b>' invalide pour
    				l'attribut <b>$attr</b>.<hr>");
    	}
    	public function decrire() {
    		echo '<h4>Instance de la classe Livre</h4>',
    				$this->titre, ' - ', $this->auteur, ' - ',
    				$this->pages, ' pages - ',
    				$this->prix, ' &euro; - ', $this->cat, '<hr>';
    	}
    }			// Fin de la classe Livre

Vous devez tester votre code en instanciant des `Livre` avec les informations suivantes qui provoqueront toutes des erreurs :

Titre | Auteur | Nb Pages | Prix | Cat.
| Pas de titre | 70 | 17 | Méthode
Pas d'auteur |  | 160 | 4.90 | Méthode
Mauvais nombre de pages | Mauvais prix | 0 | -21.90 | Partition
Seulement le titre OK |  | inconnu | 30 euros | Magazine

Une solution possible

Exercice : exceptions
<?php ob_start(); require('monexception.class.php'); require('bib_fonctions.php'); // Définition de la classe Livre class Livre { const CAT_METHODE = 'Méthode'; const CAT_PARTITION = 'Partition'; const CAT_BIO = 'Biographie'; const CAT_AUTRE = 'Autre'; private $titre, $auteur, $pages, $prix, $cat; public function __construct($p1, $p2, $p3, $p4, $p5) { $this->titre = $this->verifChaine($p1, 'titre'); $this->auteur = $this->verifChaine($p2, 'auteur'); if (is_numeric($p3) && estEntre($p3, 10, 1000)) { $this->pages = $p3; } else { $this->afficheErreur($p3, 'pages'); } if (is_numeric($p4) && estEntre($val, 0, 1000)) { $this->prix = $p4; } else { $this->afficheErreur($p4, 'prix'); } $oks = array(self::CAT_METHODE, self::CAT_PARTITION, self::CAT_BIO, self::CAT_AUTRE); if (in_array($p5, $oks)) { $this->cat = $p5; } else { $this->afficheErreur($p5, 'cat'); } } private function verifChaine($val, $attr) { $val = trim($val); ($val == '') && $this->afficheErreur($val, $attr); return $val; } private function afficheErreur($val, $attr) { $msg = "<hr>Valeur '<b>$val</b>' invalide pour l'attribut <b>$attr</b>.<hr>"; throw new MonException($msg); } public function decrire() { echo '<h4>Instance de la classe Livre</h4>', $this->titre, ' - ', $this->auteur, ' - ', $this->pages, ' pages - ', $this->prix, ' &euro; - ', $this->cat, '<hr>'; } } // Fin de la classe Livre htmlDebut('Constructeur implicite'); MonException::$is_dev = true; $l1 = new Livre('', 'Pas de titre', 70, 17, 'Méthode'); //$l2 = new Livre('Pas d\'auteur', '', 160, 4.90, 'Méthode'); //$l3 = new Livre('Mauvais nombre de pages', 'Mauvais prix', 0, -21.90, 'Partition'); //$l4 = new Livre('Seulement le titre OK', '', 'inconnu', '30 euros', 'Magazine'); htmlFin(); ?>