set_error_handler()
.
On va ainsi apprendre à gérer les erreurs en manipulant des objets qu’on appelle des exceptions et qui vont être crées et « lancés » dès qu’une erreur définie va survenir.
Présentation des exceptions en PHP 5
La version 5 de PHP a introduit une nouvelle façon de gérer la plupart des erreurs en utilisant de qu’on appelle des exceptions. Cette nouvelle façon de procéder se base sur le PHP orienté objet et sur la classe Exception
.
L’idée ici va être de créer ou de « lancer » un nouvel objet Exception
lorsqu’une erreur spécifique est détectée. Dès qu’une exception est lancée, le script va suspendre son exécution et le PHP va chercher un endroit dans le script où l’exception va être « attrapée ».
Utiliser des exceptions va nous permettre de gérer les erreurs de manière plus fluide et de personnaliser la façon dont un script doit gérer certaines erreurs.
Notez que quasiment tous les langages serveurs utilisent le concept d’exceptions pour prendre en charge les erreurs car c’est la meilleure façon de procéder à ce jour.
Lancer et attraper une exception
En pratique, la gestion d’une erreur via une exception va se faire en trois temps :
- On va définir quand une exception doit être lancée avec une instruction
throw
; - On va créer un bloc
catch
dont le but va être d’attraper l’exception si celle-ci a été lancée et de définir la façon dont doit être gérée l’erreur ; - On va utiliser un bloc
try
dans lequel le code qui peut potentiellement retourner une erreur va être exécuté.
Illustrons immédiatement tout cela avec un exemple concret. Pour cela, imaginons que l’on définisse une fonction qui divise deux nombres non connus à l’avance entre eux. Comme vous le savez, on ne peut pas diviser un nombre par zéro. Si on tente de le faire, une erreur sera renvoyée par le PHP. Nous allons justement lancer une exception pour gérer ce cas particulier.
Commençons déjà par créer notre script sans aucune prise en charge des erreurs :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <form action='cours.php' method='post'> <label for='n1'>Numérateur :</label> <input type='number' id='n1' name='n1'><br><br> <label for='n2'>Dénominateur :</label> <input type='number' id='n2' name='n2'><br> <input type='submit' value='Envoyer'><br><br> </form> <?php function division($x, $y){ echo '<br>Résultat de' .$x. '/' .$y. ' : ' .($x / $y); } //En pratique, il faudrait vérifier les données envoyées if(isset($_POST['n1']) && isset($_POST['n2'])){ division($_POST['n1'],$_POST['n2']); } ?> <p>Un paragraphe</p> </body> </html>
Ici, on définit une fonction division()
dont le rôle est de diviser un nombre par un autre et de renvoyer le résultat. Les nombres seront passés par nos utilisateurs via un formulaire. Évidemment, en pratique, il faudra vérifier les données envoyées avant de les manipuler mais ce n’est pas l’objet de la leçon.
Ici, on se contente simplement de s’assurer que des données ont bien été envoyées et si c’est le cas on exécute division()
. Évidemment, si on renseigne 0 comme dénominateur, une erreur est renvoyée puisqu’on n’a pas le droit de diviser par zéro.
Pour gérer cette erreur, on pourrait tout à fait utiliser un gestionnaire d’erreurs et la fonction set_error_handler()
comme ceci :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <form action='cours.php' method='post'> <label for='n1'>Numérateur :</label> <input type='number' id='n1' name='n1'><br><br> <label for='n2'>Dénominateur :</label> <input type='number' id='n2' name='n2'><br> <input type='submit' value='Envoyer'><br><br> </form> <?php function division($x, $y){ echo '<br>Résultat de' .$x. '/' .$y. ' : ' .($x / $y); } set_error_handler(function($niveau, $message, $fichier, $ligne){ echo 'Erreur : ' .$message. '<br>'; echo 'Niveau de l\'erreur : ' .$niveau. '<br>'; echo 'Erreur dans le fichier : ' .$fichier. '<br>'; echo 'Emplacement de l\'erreur : ' .$ligne. '<br>'; }); //En pratique, il faudrait vérifier les données envoyées if(isset($_POST['n1']) && isset($_POST['n2'])){ division($_POST['n1'],$_POST['n2']); } ?> <p>Un paragraphe</p> </body> </html>
Cela fonctionne bien ici. Cependant, vous devez savoir que dans un script plus complexe, il va être difficile de s’assurer que la fonction set_error_handler()
soit appelée avec les bonnes valeurs et au bon moment. Pour ces raisons, on préfèrera souvent utiliser les exceptions pour gérer les erreurs. Regardez à nouveau le code :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <form action='cours.php' method='post'> <label for='n1'>Numérateur :</label> <input type='number' id='n1' name='n1'><br><br> <label for='n2'>Dénominateur :</label> <input type='number' id='n2' name='n2'><br> <input type='submit' value='Envoyer'><br><br> </form> <?php function division($x, $y){ if($y == 0){ throw new Exception('Division par zéro impossible', 15); } else{ echo '<br>Résultat de' .$x. '/' .$y. ' : ' .($x / $y); } } //En pratique, il faudrait vérifier les données envoyées if(isset($_POST['n1']) && isset($_POST['n2'])){ try{ division($_POST['n1'],$_POST['n2']); } catch(Exception $e){ echo 'Message d\'erreur : ' .$e->getMessage(); echo '<br>'; echo 'Code d\'erreur : ' .$e->getCode(); echo '<br>'; echo $e->getFile(); } } ?> <p>Un paragraphe</p> </body> </html>
Dans l’exemple ci-dessus, on utilise cette fois-ci les exceptions pour prendre en charge les erreurs. L’idée derrière les exceptions va être d’anticiper les situations problématiques (situations qui vont pouvoir causer une erreur) et de lancer une exception si la situation est rencontrée.
C’est ce qu’on fait ici dans le code de notre fonction division()
: on a identifié que le PHP renverrait une erreur dans le cas où un utilisateur tenterait une division par zéro. Dans ce cas là, on va lancer une exception. Pour cela, on utilise la syntaxe throw new Exception
qui lance un objet de la classe Exception
.
La classe Exception
possède un constructeur qui va pouvoir accepter un message d’erreur et un code d’erreur personnalisé de notre choix. Ici, je passe le message « Division par zéro impossible » et je choisis le code « 15 ».
Ce code va être très utile dans le cas d’un « vrai » site si on souhaite effectuer un suivi et un rapport des erreurs survenues puisqu’on va ainsi pouvoir identifier très rapidement et précisément les erreurs.
Dès qu’une exception est lancée, il va falloir l’attraper. On va faire cela dans un deuxième bloc catch
. Le but de ce bloc va déjà être de capturer une exception si une exception a été lancée. Ici, si c’est le cas, on place les informations liées à l’exception dans $e
(le nom $e
est choisi arbitrairement, vous pouvez choisir le nom de votre choix).
Au sein du bloc catch
, nous allons préciser les actions à mener en cas d’exception. Ici, on se contente de renvoyer notre message d’erreur, notre code d’erreur et le fichier dans lequel l’exception a été lancée.
Pour comprendre ce code, vous devez savoir que $e
est un objet de la classe Exception
et que la classe Exception
possède des méthodes dont notamment :
getMessage()
qui va renvoyer le message d’erreur défini lors du lancement de l’exception ;getCode()
qui va renvoyer le code d’erreur défini lors du lancement de l’exception ;getFile()
qui va renvoyer le chemin du fichier depuis lequel l’exception a été lancée ;getLine()
qui va renvoyer la ligne du fichier depuis lequel l’exception a été lancée.
Finalement, on va exécuter le code potentiellement problématique dans un bloc try
(bloc « d’essai »). Si aucune erreur n’est rencontrée, alors aucune exception ne sera lancée et le code s’exécutera normalement. Si une erreur est rencontrée, alors une exception sera lancée et attrapée dans le bloc catch
dans lequel on va décider de la marche à suivre.
Les exceptions et les erreurs en PHP 7
L’une des grandes limites du PHP 5 est qu’il était quasiment impossible de gérer des erreurs fatales. En effet, une erreur fatale n’appelait pas le gestionnaire d’erreurs défini dans set_error_handler()
et il était également impossible de lancer une exception en cas d’erreur fatale.
Ainsi, en PHP 5, dès qu’une erreur fatale était rencontrée, le script s’arrêtait brutalement. Certains développeurs disent qu’il s’agit là d’un comportement normal puisqu’une erreur fatale ne devrait pas pouvoir être gérée d’une autre façon / ignorée. Ce point de vue est parfois vrai mais pas toujours justifié en fonction de l’erreur fatale.
La version 7 du PHP modifié cela et nous offre désormais un moyen de gérer certaines erreurs fatales via le lancement d’exceptions. La difficulté ici est que pour des raisons de rétrocompatibilité du code (cohérence entre différentes versions de PHP qui coexistent), les exceptions lancées par les erreurs fatales ne vont pas être des instances de la classe Exception
mais d’une nouvelle classe créée en PHP 7 pour cela : la classe Error
qui est très similaire dans sa construction à la classe Exception
.
Note : A ce point du cours, vous n’êtes plus tout à fait débutants et il y a donc une notion que vous devriez intégrer et qui va grandement faciliter la compréhension des différents langages de programmation : les langages de programmation ne sont JAMAIS parfaits. Certains langages prennent des directions de développement puis reviennent sur leur pas ou intègrent de nouveaux composants au fil du temps. Cela fait que chaque langage dispose d’un héritage de composants désuets et que parfois il est difficile de maintenir un ensemble qui fasse du sens.
C’est le cas du PHP qui est un langage très performant mais cependant également très loin d’être parfait d’un point de vue sémantique et qui possède encore certains comportements qui ne font tout simplement aucun sens. Vous devez absolument garder cela en tête et essayer d’utiliser les langages au mieux tout en gardant à l’esprit que la perfection n’existe pas !
Retour à nos erreurs. Une nouvelle classe a donc été créée en PHP 7 pour utiliser les exceptions avec certains types d’erreurs. Pour apporter un peu de cohérence dans la gestion des erreurs et tenter une unification, une interface a également été créée : l’interface Throwable
.
Cette interface est en PHP 7 l’interface de base pour tout objet qui peut être jeté ou lancé grâce à la déclaration throw
et va être implémentée à la fois par la classe Error
et par la classe Exception
.
On va ainsi pouvoir utiliser l’interface Throwable
pour attraper à la fois des exceptions issues de la classe Exception
et des exceptions issues de la classe Error
.
Cependant, comme Throwable
est une interface, on ne va pas pouvoir l’instancier directement.
En PHP 7, lorsqu’une erreur fatale et récupérable (erreur fatale de type « recoverable ») survient, une exception de la classe Error
est automatiquement lancée. On va ainsi pouvoir l’attraper dans un bloc catch
comme nos exceptions « classiques » à part que cette fois-ci le bloc catch
devra utiliser la syntaxe catch(Error $e){}
ou éventuellement plus généralement catch(Throwable $t){}
qui va permettre d’attraper les exceptions issues de la classe Error
et celles de la classe Exception
.
Notez qu’il est généralement considéré comme une bonne pratique d’utiliser les classes les plus précises pour attraper les exceptions et les gérer de la meilleure façon.
Regardez l’exemple ci-dessous pour bien comprendre :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <?php try{ bonjour(); } catch(Error $e){ echo $e->getMessage(); } echo '<br><br>'; try{ if(!function_exists('test')){ throw new Error('La fonction n\'est pas définie'); } } catch(Error $e){ echo $e->getMessage(); } ?> <p>Un paragraphe</p> </body> </html>
Ici, on commence par essayer d’appeler une fonction bonjour()
qui n’est pas définie. Vous devez savoir que lorsqu’on tente d’exécuter une fonction non définie, le PHP lève une erreur fatale. En PHP 7, une exception de la classe Error
est également lancée. On capture cette erreur dans le bloc catch
juste en dessous et on affiche les informations relatives à l’erreur avec la méthode getMessage()
de la classe Error
.
On va ensuite gérer une autre erreur du même type en lançant nous-même l’exception cette fois-ci pour personnaliser le message par exemple. Pour cela, on utilise la fonction function_exists()
qui vérifie si une fonction a été définie et renvoie true
si c’est le cas ou false
dans le cas contraire.
Ici, on demande à function_exists()
de vérifier si une fonction test()
a été définie. Ce n’est pas le cas ; notre fonction va donc renvoyer false
et on va lancer une exception issue de la classe Error
en précisant un message personnalisé.
Ici, notez bien qu’on rentre dans le if
lorsque function_exists()
renvoie false
puisqu’on a utilisé l’opérateur !
pour inverser la valeur logique de notre test.
Ensuite, on utilise un bloc catch
pour capturer l’exception et renvoyer le message personnalisé.
Ici, nous ne faisons qu’echo
les informations relatives à l’erreur, ce qui n’est pas très utile en soi. Cependant, vous allez pouvoir définir toute sorte de code dans votre bloc catch
pour dicter le comportement du script en cas d’exception.
Le bloc finally
Depuis PHP 5.5, on va pouvoir spécifier un bloc finally
à la suite des blocs catch
.
L’intérêt principal de ce bloc est que le code contenu dans finally
sera toujours exécuté à la suite des blocs try
et catch
et avant la reprise de l’exécution normale du script, et ceci qu’une exception ait été lancée ou pas.
On va donc vouloir utiliser ce bloc lorsqu’on souhaite absolument qu’un code s’exécute quelle que soit la situation, comme par exemple dans le cas où on voudrait fermer une connexion à une base de données.
La grande différence entre un code dans un bloc finally
et un code dans l’espace global du script (en dehors des blocs cités précédemment) est qu’un code suivant les blocs try
et finally
et en dehors de ceux-ci ne sera pas exécuté dans le cas où une exception a été lancée mais pas attrapée (car dans ce cas une erreur fatale sera levée).
Regardez plutôt l’exemple ci-dessous :
<!DOCTYPE html> <html> <head> <title>Cours PHP & MySQL</title> <meta charset="utf-8"> <link rel="stylesheet" href="cours.css"> </head> <body> <h1>Titre principal</h1> <?php try{ bonjour(); } finally{ echo 'Toujours affiché'; } echo 'Non affiché ici car exception lancée et non capturée'; ?> <p>Un paragraphe</p> </body> </html>