La gestion des erreurs est un sujet important en programmation. Il y a souvent des erreurs et il faut savoir vivre avec. Mais comment faire ça bien ? 🤔
Si vous vous souvenez de notre routeur, il contient beaucoup de if
. On fait des tests et on affiche des erreurs à chaque fois qu'il y a un problème :
<?php
if (test) {
// C'est bon, on fait des choses
// ...
if (encoreUnTest) {
// C'est bon, on continue
} else {
echo 'Erreur';
}
} else {
echo 'Autre erreur';
}
Et alors, ça marche, non ?
Oui, mais comme toujours, ce n'est pas parce que ça marche que c'est pratique à la longue. Les développeurs ont en particulier du mal à gérer comme ça les erreurs qui ont lieu à l'intérieur des fonctions.
Que se passe-t-il s'il y a une erreur dans le contrôleur ou dans le modèle ? Va-t-on les laisser se charger d'afficher des erreurs ? Ça ne devrait normalement pas être à eux de le faire. Ils devraient remonter qu'il y a une erreur et laisser une partie spécialisée du code traiter l'erreur.
Tirez parti des exceptions
Les exceptions sont un moyen en programmation de gérer les erreurs. Vous en avez peut-être déjà vu dans du code PHP, ça ressemble à ça :
<?php
try {
// Essayer de faire quelque chose
} catch (Exception $e) {
// Si une erreur se produit, on arrive ici
}
En premier lieu, l'ordinateur essaie d'exécuter les instructions qui se trouvent dans le bloc try
("essayer" en anglais). Deux possibilités :
soit il ne se passe aucune erreur dans le bloc
try
: dans ce cas, on saute le bloccatch
et on passe à la suite du code ;soit une erreur se produit dans le bloc
try
: on arrête ce qu'on faisait et on va directement dans lecatch
(pour "attraper" l'erreur).
C'est par exemple ce qu'on fait ici pour se connecter à la base de données :
<?php
// Code avant
try {
$database = new PDO('mysql:host=localhost;dbname=blog;charset=utf8', 'blog', 'password');
} catch(Exception $e) {
die('Erreur : '.$e->getMessage());
}
// Code après
On essaie de se connecter à la base de données dans le bloc try
:
si tout va bien, on continue (on va dans le "Code après") ;
si, en revanche, il y a un souci lors de la connexion (à l'intérieur du
new PDO
), alors on récupère l'erreur dans le bloccatch
et ledie
est exécuté.
Pour générer une erreur, il faut "jeter une exception", ou "lancer une exception" (oui, on dit ça 😂 ). Dès qu'il y a une erreur quelque part dans votre code, dans une fonction par exemple, vous utiliserez cette ligne :
<?php
throw new Exception('Message d\'erreur à transmettre');
On va utiliser ce mécanisme dans notre code !
Ajoutez la gestion des exceptions dans le routeur
Je vous propose d'entourer tout notre routeur par un bloctry
/catch
comme ceci :
<?php
// index.php
require_once('src/controllers/add_comment.php');
require_once('src/controllers/homepage.php');
require_once('src/controllers/post.php');
try {
if (isset($_GET['action']) && $_GET['action'] !== '') {
if ($_GET['action'] === 'post') {
if (isset($_GET['id']) && $_GET['id'] > 0) {
$identifier = $_GET['id'];
post($identifier);
} else {
throw new Exception('Aucun identifiant de billet envoyé');
}
} elseif ($_GET['action'] === 'addComment') {
if (isset($_GET['id']) && $_GET['id'] > 0) {
$identifier = $_GET['id'];
addComment($identifier, $_POST);
} else {
throw new Exception('Aucun identifiant de billet envoyé');
}
} else {
throw new Exception("La page que vous recherchez n'existe pas.");
}
} else {
homepage();
}
} catch (Exception $e) { // S'il y a eu une erreur, alors...
echo 'Erreur : '.$e->getMessage();
}
Comme vous pouvez le voir, à l'endroit où les erreurs se produisent j'ai mis des throw new Exception
. Cela arrête le bloc try
et amène directement l'ordinateur au bloc catch
.
Ici, notre bloc catch
se contente de récupérer le message d'erreur qu'on a transmis et de l'afficher.
Remontez les exceptions
Pour l'instant, vous vous dites sûrement que ça n'est pas fou fou. Ok, les exceptions sont faites pour gérer les erreurs, mais on a surtout compliqué le code du routeur avec un nouveau bloc.
C'est parce que vous n'avez pas encore vu à quel point les exceptions peuvent être pratiques ! Quand il se passe une erreur à l'intérieur d'une fonction située dans le bloc try
, celle-ci est "remontée" jusqu'au bloc catch
.
Par exemple, notre routeur appelle la fonction du contrôleuraddComment
, on est d'accord ? Que se passe-t-il quand il y a une erreur dans le contrôleur ? Pour l'instant, on fait ça :
<?php
// src/controllers/add_comment.php
require_once('src/model/comment.php');
function addComment(string $post, array $input)
{
$author = null;
$comment = null;
if (!empty($input['author']) && !empty($input['comment'])) {
$author = $input['author'];
$comment = $input['comment'];
} else {
die('Les données du formulaire sont invalides.');
}
$success = createComment($post, $author, $comment);
if (!$success) {
die('Impossible d\'ajouter le commentaire !');
} else {
header('Location: index.php?action=post&id=' . $post);
}
}
Notre contrôleur arrête tout et affiche ses erreurs avec desdie
. Il y a moyen de faire plus propre : jetons ici des exceptions, le code s'y arrêtera, et les erreurs seront remontées jusque dans le routeur qui contenait le bloc try
!
Voilà comment on peux mieux gérer les erreurs, en ajoutant desthrow
:
<?php
// src/controllers/add_comment.php
require_once('src/model/comment.php');
function addComment(string $post, array $input)
{
$author = null;
$comment = null;
if (!empty($input['author']) && !empty($input['comment'])) {
$author = $input['author'];
$comment = $input['comment'];
} else {
throw new Exception('Les données du formulaire sont invalides.');
}
$success = createComment($post, $author, $comment);
if (!$success) {
throw new Exception('Impossible d\'ajouter le commentaire !');
} else {
header('Location: index.php?action=post&id=' . $post);
}
}
Pratique, non ? Ce principe de "remontée" de l'erreur jusqu'à l'endroit du code qui contenait le bloc try
est vraiment un gros avantage des exceptions.
Comme ce n'est pas forcément évident à voir comme ça, je vous ai résumé le concept dans un schéma (j'adore les schémas, je vous l'ai déjà dit ?) :
![Lorsqu'une erreur survient dans une sous-fonction, elle est remontée jusqu'au bloc catch.](https://user.oc-static.com/upload/2022/05/09/1652105899235_P2C7-1.png)
<?php
function dbConnect()
{
$database = new PDO('mysql:host=localhost;dbname=blog;charset=utf8', 'blog', 'password');
return $database;
}
Pour compléter mon explication, je vous propose de regarder ce screencast qui détaille, de manière très visuelle, le fonctionnement des exceptions en PHP.
Exercez-vous
Pour l'instant, notre bloccatch
affiche une erreur avec un simpleecho
. Si nous voulons faire quelque chose de plus joli, nous pouvons appeler une vuetemplates/error.php
qui affiche joliment le message d'erreur.
Il faudrait faire quelque chose dans ce goût-là :
<?php
// index.php
require_once('src/controllers/add_comment.php');
require_once('src/controllers/homepage.php');
require_once('src/controllers/post.php');
try {
// ...
} catch (Exception $e) {
$errorMessage = $e->getMessage();
require('templates/error.php');
}
Bon, là je vous laisse travailler la vue vous-même, je pense que vous avez compris le concept !
En résumé
Les exceptions sont un mécanisme qui permet de gérer les remontées d'erreurs en PHP. Elles s'utilisent avec les trois mots clés :
try
,catch
etthrow
.try
etcatch
permettent de créer des zones de contrôles sur les exceptions.throw
permet de lancer une exception. Elle interrompra le flux classique d'exécution du code, jusqu'à être gérée par une zone de contrôle d'exception.Les zones de contrôle d'exception peuvent s'imbriquer à l'infini.
Waouh, ce sont vraiment des outils puissants ! Et pour le moment, nous n'avons abordé que leur surface. Les exceptions sont des "Objets" et ça offre des possibilités encore plus extraordinaires pour les utiliser. On va d'ailleurs faire le grand pas vers ces fameux "Objets" dans la prochaine partie du cours.
Mais pour l'instant, on doit s'assurer que vous maîtrisez tout ce qu'on a vu jusqu'ici. C'est l'heure du quiz !