Gestion personnalisée des exceptions PHP
La version 5 de PHP apporte une gestion des exceptions similaires à ce qu'offrent d'autres langages de programmation. On peut ainsi lancer une exception (throw) qui sera éventuellement attrapée (catch) et traitée. On peut ainsi entourer du code sensible de structures try et catch, avec pour chaque structure try une structure catch correspondante.
L'exception n'a rien à voir avec une simple erreur. Lorsqu'une exception est lancée, le flot du programme est interrompu jusqu'au premier bloc catch, ou s'il n'y en a pas, jusqu'au gestionnaire d'exceptions. Ce qui veut dire qu'il nous appartient de gérer l'exception, et de l'attraper si on prévoit une solution de rechange à notre code de test.
Ce billet s'intéressant essentiellement à la personnalisation des exceptions PHP, pour de plus amples informations sur les exceptions je vous invite à consulter la documentation PHP sur les exceptions, et surtout l'excellent billet de Fabrice Lezoray : PHP 5 et la gestion des exceptions.
Quand à moi, je vais vous présenter la classe WdException qui permet de lancer des exceptions tout en profitant des fonctionnalités de la classe WdDebug, et notamment des rapports d'incidents. Je vous invite également à découvrir le framework WdCore, duquel est issue la classe.
Présentation de la classe WdException
La classe WdException peut être utilisée pour lancer des exceptions. Elle étend la classe Exception et présente les fonctionnalités suivantes :
- Le message de l'exception est formaté et localisé (traduit) grâce à la classe WdLocale.
- Le message de l'exception est formaté de manière similaire aux erreurs personnalisées par la classe WdDebug.
- Les messages issus de la journalisation de la class WdDebug peuvent être ajoutés au message de l'exception.
- Un rapport d'incident peut être émit.
La classe est aussi simple à mettre en ouvre que la classe WdDebug :
<?php
define('WDDEBUG_SILENT', true);
define('WDDEBUG_REPORT_ADDRESS', 'me@mycompany.com');
define('WDEXCEPTION_WITH_LOG', true);
require_once 'wdexception.php';
set_exception_handler(array('WdDebug', 'exceptionHandler');
?>
Voyons ce que cela donne avec un petit exemple.
Un exemple d'exception, mais pas exceptionnel
L'exemple suivant présente une fonction test() qui ne supporte pas que son paramètre $a soit différent de 1. Cela l'ennui tellement qu'elle ne se prive pas de lancer une exception si cela se produit :
<?php
function test($a)
{
if ($a != 1)
{
throw new WdException('$a must be 1, %value given instead', array('%value' => $a));
}
}
wd_log('A little message before the big crash');
test(13);
?>
On notera, juste avant l'appel à la fonction test(), un appel à la fonction wd_log(). Cette fonction est une des fonctions de journalisation non intrusives proposées par la classe WdDebug. Les messages loggés par cette fonction sont ajoutés au message d'exception si la constante WDEXCEPTION_WITH_LOG est définie comme vraie.
Voyons maintenant le résultat de ce test :
Exception with the following message:
$a must be 1, 13 given instead
in G:\Weirdog\www\wd\_test\debug.php at line 19
Stack trace:
#00 — /debug.php(25): test(13)
Log:
A little message before the big crash
À noter que si la constante WDDEBUG_REPORT_ADDRESS est définie, le message de l'exception nous parviendra par E-Mail. De quoi réagir bien vite avant que cela ne pose trop de problèmes.
Attraper le bon type d'exception
Un dernier petit mot sur les blocs try et catch, parce que ce n'est pas très clair dans la documentation. Lorsque l'on crée un bloc catch, le type d'objet définit le type d'exception qui peut être attrapé par le bloc. Ceci est très important, et pratique, puisque l'on peut sélectionner le type d'exception à attraper, mais cela peut également être problématique si on ne fait pas attention à ce que l'on fait :
<?php
try
{
test(13);
}
catch (OtherException $e)
{
echo 'je suis complètement ignoré !';
}
catch (Exception $e)
{
echo 'ça tombe sur moi par erreur';
}
catch (WdException $e)
{
echo 'ça devrait tomber sur moi';
}
?>
Ce qui nous donne :
ça tombe sur moi par erreur
En arrangeant l'ordre des blocs on peut récupérer l'exception qui nous intéresse :
<?php
try
{
test(13);
}
catch (OtherException $e)
{
echo 'je suis complètement ignoré !';
}
catch (WdException $e)
{
echo 'ça devrait tomber sur moi';
}
catch (Exception $e)
{
echo 'ça tombe sur moi par erreur';
}
?>
Ce qui nous donne :
ça devrait tomber sur moi
Aller plus loin dans la personnalisation
Le gestionnaire d'exception proposé par la classe WdDebug ne se fatigue pas trop dans la présentation du message. Un petit coup de echo et le tour est joué, mais on peut en faire un peu plus. Voici par exemple le gestionnaire d'exception de WdPublisher, qui utilise un patron HTML pour présenter l'exception :
<?php
class WdPCore
{
static public function exceptionHandler($exception)
{
$html = file_get_contents('exception.html', true);
$html = str_replace('{publisher.url}', WDPUBLISHER_URL, $html);
$html = str_replace('{exception.body}', $exception, $html);
echo $html;
exit;
}
}
?>
On se retrouve ainsi avec une page HTML des plus classe pour présenter notre exception :
En vous souhaitant du courage
Bon, j'espère que les classes WdDebug et WdException vous rendrons bien service et vous serons aussi utile qu'à moi.
Débuggez bien !