228 Chapitre 5. Organisation du développement La classe Exception ne demande qu’à être étendue dans des sous-classes person- nalisant la gestion des exceptions et la description des erreurs rencontrées. Voici à titre d’exemple une sous-classe SQLException destinée à gérer plus précisément les erreurs survenant au cours d’un accès à un SGBD. Exemple 5.6 exemples/SQLException.php : Extension de la classe Exception pour les exceptions SQL <?php /∗∗ ∗ Sous−classe de la classe exception , spécialisée pour ∗ les erreurs soulevées par un SGBD ∗ / class SQLException extends Exception { // Propriétés private $sgbd ; // nom du SGBD utilisé private $code_erreur ; // code d’erreur du SGBD // Constructeur function SQLException ($message , $sgbd , $code_erreur=0) { // Appel du constructeur de la classe parente parent :: __construct($message); // Affectation aux propriétés de la sous−classe $this−>sgbd = $sgbd ; $this−>code_erreur = $code_erreur ; } // Méthode renvoyant le SGBD qui a levé l ’erreur public function getSGBD() { return $this−>sgbd ; } // Méthode renvoyant le code d’erreur du SGBD public function getCodeErreur () { return $this−>code_erreur ; } } ?> On peut alors lancer explicitement une exception instance de SQLException et intercepter spécifiquement ce type d’exception. Rappelons encore une fois que toute instance d’une sous-classe est aussi instance de toutes les classes parentes, et donc qu’un objet de la classe SQLException est aussi un objet de la classe Exception,ce qui permet de le faire entrer sans problème dans le moule de gestion des exceptions PHP 5. 5.2 Gestion des erreurs 229 Le fragment de code ci-dessous montre comment exploiter cette gestion des exceptions personnalisées. // Bloc d’interception des exceptions try { // Connexion $bd = mysql_connect ( (SERVEUR, NOM, PASSE ) ; if (!$bd) // Erreur survenue? On lance l ’ exception throw new SQLException (" Erreur de connexion" , "MySQL") ; } catch (SQLException $e) // Interception d ’une erreur SQL { trigger_error("Erreur survenue dans " . $e−>getSGBD() . ":".$e−>getMessage () , E_USER_ERROR) ; } catch (Exception) // Interception de n’importe quelle erreur { trigger_error ("Erreur : " . $e−>getMessage () , E_USER_ERROR) ; } On a utilisé plusieurs blocs catch, en interceptant les erreurs les plus précises en premier. PHP exécutera le premier bloc catch spécifiant une classe dont l’exception est instance. L’utilisation des exceptions implique leur surveillance et leur interception par une construction try et catch. Si, quand le script se termine, PHP constate que certaines exceptions n’ont pas été interceptées, il transformera ces exceptions en erreurs standards, avec affichage ou placement dans le fichier des erreurs selon la politique choisie. Le message produit est cependant assez peu sympathique. Voici par exemple ce que l’on obtient si on oublie d’intercepter les exceptions soulevées par la classe d’accès aux bases de données BD. Fatal error: Uncaught exception ’Exception’ with message ’Erreur de connexion au SGBD’ in BD.class.php:23 On peut remplacer ce comportement un peu brutal par un gestionnaire d’excep- tion personnalisé, comme le montrera la prochaine section. L’introduction des exceptions depuis PHP 5 fait de ce dernier –au moins pour cet aspect – un langage aussi puissant et pratique que C++ ou Java, auxquels il emprunte d’ailleurs très exactement le principe et la syntaxe de cette gestion d’erreurs. Les exceptions offrent un mécanisme natif pour décrire, créer et gérer des erreurs de toutes sortes, sans imposer une gestion « manuelle » basée sur des échanges de codes d’erreur au moment des appels de fonctions, suivi du test systématique de ces codes. La gestion des exceptions est d’une grande souplesse : on peut spécialiser les diffé- rents types d’exception, choisir à chaque instant celle qu’on veut traiter, « relancer » 230 Chapitre 5. Organisation du développement lesautresparunthrow, séparer clairement les parties relevant de la gestion des erreurs de celles relevant du code de l’application. Attention cependant : le lancer d’une exception interrompt le script jusqu’au catch le plus proche, ce qui n’est pas forcément souhaitable pour toutes les erreurs détectées. Par exemple, quand on teste les données saisies dans un formulaire, on préfère en général afficher d’un coup toutes les anomalies détectées pour permettre à l’utilisateur de les corriger en une seule fois. Ce n’est pas possible avec des exceptions. 5.2.4 Gestionnaires d’erreurs et d’exceptions PHP permet la mise en place de gestionnaires d’erreurs et d’exceptions personnalisés grâce aux fonction set_error_handler() et set_exception_handler(). Toutes deux prennent en argument une fonction qui implante la gestion personnalisée. Commençons par la gestion des erreurs. La fonction gestionnaire doit prendre en entrée 5 paramètres : le niveau d’erreur, le message, le nom du script, le numéro de ligne et enfin le contexte (un tableau qui contiendra les variables existantes au moment où la fonction est appelée). Quand une erreur est déclenchée, par l’interpréteur PHP ou par le développeur vialafonctiontrigger_error(), PHP appelle la fonction gestionnaire d’erreurs en lui passant les valeurs appropriées pour les paramètres. L’exemple ci-dessous montre une fonction de gestion d’erreur. Exemple 5.7 webscope/lib/GestionErreurs.php : Un gestionnaire d’erreurs PHP <?php // Définition d’un gestionnaire d’erreurs. // Elle affiche le message en français. function GestionErreurs ($niveau_erreur , $message , $script , $no_ligne , $contexte=array ()) { // Regardons le niveau de l ’ erreur switch ($niveau_erreur) { // Les erreurs suivantes ne doivent pas être transmises ici ! case E_ERROR: case E_PARSE : case E_CORE_ERROR : case E_CORE_WARNING : case E_COMPILE_ERROR: case E_COMPILE_WARNING : echo "Cela ne doit jamais arriver !! " ; exit ; case E_WARNING : $typeErreur = "Avertissement PHP"; break ; case E_NOTICE : $typeErreur = "Remarque PHP"; 5.2 Gestion des erreurs 231 break ; case E_STRICT : $typeErreur = "Syntaxe obsolète PHP 5"; break ; case E_USER_ERROR : $typeErreur = "Avertissement de l ’ application " ; break ; case E_USER_WARNING : $typeErreur = "Avertissement de l ’ application " ; break ; case E_USER_NOTICE: $typeErreur = "Remarque PHP"; break ; default : $typeErreur = "Erreur inconnue" ; } // Maintenant on affiche en rouge echo "<font color=’red’><b>$typeErreur </b> : " . $message . "<br/>Ligne $no_ligne du script $script </font><br/>"; // Erreur utilisateur? On stoppe le script. if ($niveau_erreur == E_USER_ERROR) exit ; } ?> On peut noter que les niveaux d’erreur E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING sont traités de manière rapide : en principe PHP gèrera toujours ce type d’erreur lui-même, sans faire appel au gestionnaire d’erreur ; on ne devrait donc pas les rencontrer ici. Pour les autres niveaux d’erreur on met en place une gestion personnalisée, consistant ici simplement à afficher les informations en rouge. On peut faire exac- tement ce que l’on veut : écrire dans un fichier de log (journalisation), envoyer un e-mail à l’administrateur, ou toute combinaison appropriée de ces solutions. Une possibilité par exemple est d’une part d’afficher un message neutre et poli à l’utilisateur du site l’informant que l’application est provisoirement indisponible et que l’équipe d’ingénieurs s’active à tout réparer, d’autre part d’envoyer un e-mail à cette dernière pour la prévenir du problème. Le gestionnaire d’erreurs est mis en place grâce à l’appel suivant : // Gestionnaire personnalisé d’ erreurs . Voir GestionErreurs.php. set_error_handler("GestionErreurs"); 232 Chapitre 5. Organisation du développement Soulignons que ceci vient remplacer la gestion normale des erreurs PHP, et qu’il est donc de sa responsabilité d’agir en fonction du niveau détecté. Notre gestionnaire interrompt donc le script avec une instruction exit quand une erreur de niveau E_USER_ERROR est rencontrée. Si l’on souhaite dans un script abandonner, tempo- rairement ou définitivement, la gestion personnalisée des erreurs, on peut revenir au gestionnaire par défaut avec la fonction restore_error_handler(). Cela peut être utile par exemple quand on inclut des scripts PHP pas entièrement compatibles PHP 5 et pour lesquels l’interpréteur engendre des messages d’avertissement. Le gestionnaire d’exception est basé sur le même principe que le gestionnaire d’erreur : on définit une fonction personnalisée qui prend en entrée un objet instance de la classe Exception (et donc de n’importe laquelle de ses sous-classes). Pour faire simple, on peut transformer l’exception en erreur en appelant le gestionnaire d’erreurs défini précédemment. Exemple 5.8 webscope/lib/GestionExceptions.php : Un gestionnaire d’exceptions PHP <?php // Définition d’un gestionnaire d’exceptions. On fait // simplement appel au gestionnaire d ’ erreurs function GestionExceptions ($exception) { // On transforme donc l ’exception en erreur GestionErreurs (E_USER_ERROR, $exception−>getMessage() , $exception−>getFile() , $exception−>getLine()); } ?> On peut alors mettre en œuvre le gestionnaire d’exceptions grâce à l’appel suivant : set_exception_handler("GestionExceptions"); La fonction GestionExceptions() sera appelée pour toute exception lancée dans un script qui n’est pas interceptée par un bloc catch. Une solution possible est donc de ne pas utiliser du tout les try et les catch et de se reposer entièrement sur le gestionnaire d’exceptions. C’est d’ailleurs la solution adoptée pour notre site. Attention à ne pas entrer dans une boucle sans fin en utilisant un gestionnaire d’erreurs qui lance une exception, laquelle à son tour se transforme en erreur et ainsi de suite. Une fois ces gestionnaires en place, il suffit de les modifier selon les besoins pour obtenir une politique de gestion des erreurs flexible et évolutive. . instance de toutes les classes parentes, et donc qu’un objet de la classe SQLException est aussi un objet de la classe Exception,ce qui permet de le faire entrer sans problème dans le moule de gestion. Méthode renvoyant le code d’erreur du SGBD public function getCodeErreur () { return $this−>code_erreur ; } } ?> On peut alors lancer explicitement une exception instance de SQLException et intercepter. ne demande qu’à être étendue dans des sous-classes person- nalisant la gestion des exceptions et la description des erreurs rencontrées. Voici à titre d’exemple une sous-classe SQLException destinée