Les exceptions - 1ère partie

Rechercher

Les exceptions - 1ère partie

  • Par Emacs
  • 11 commentaires
  • 24218 lectures
  • RSS -  Atom

Le mécanisme des exceptions a été introduit à PHP dans sa version 5 en complément de son nouveau modèle orienté objet. Au même titre qu'en Java, C++, Action Script 3 ou bien Visual Basic (pour ne citer que ces langages de programmation), les exceptions permettent de simplifier, personnaliser et d'organiser la gestion des « erreurs » dans un programme informatique. Ici le mot « erreurs » ne signifie pas « bug », qui est un comportement anormal de l'application développée, mais plutôt « cas exceptionnel » à traiter différemment dans le déroulement du programme. Etudions donc comment fonctionnent les exceptions.

Nous introduirons dans un premier temps la classe native Exception de PHP 5. Puis nous étudierons comment lancer et attraper des exceptions dans un programme. A partir de là, nous serons capables d'étendre le modèle Exception pour développer des exceptions dérivées et de types différents. Enfin, nous aborderons la notion de gestion évènementielle des exceptions au moyen du handler d'exception natif de php.

La classe native Exception

Comme nous l'avons déjà expliqué dans les précédents tutoriels du chapitre de programmation orientée objet, PHP dispose depuis sa version 5 d'un modèle objet semblable à celui de Java. Pour agrémenter ce nouveau moteur, l'équipe de développement de PHP a intégré en natif une classe Exception. Cette classe très particulière permet au développeur de simplifier le traitement des cas exceptionnels susceptibles d'apparaître pendant l'exécution d'un programme en générant des objets de type Exception. En fin de compte, retenez qu'une exception n'est rien de plus qu'un objet (instance d'une classe). Le listing ci-après présente le code de la classe Exception.

Code source de la classe native Exception (extrait de la documentation officielle de PHP)
<?php
class Exception
{
protected $message = 'exception inconnu'; // message de l'exception
protected $code = 0; // code de l'exception défini par l'utilisateur
protected $file; // nom du fichier source de l'exception
protected $line; // ligne de la source de l'exception
function __construct(string $message=NULL, int code=0);
final function getMessage(); // message de l'exception
final function getCode(); // code de l'exception
final function getFile(); // nom du fichier source
final function getLine(); // ligne du fichier source
final function getTrace(); // un tableau de backtrace()
final function getTraceAsString(); // chaîne formattée de trace
/* Remplacable */
function __toString(); // chaîne formatée pour l'affichage
}
?>

Nous identifions ici très clairement 4 parties distinctes qui composent cette classe Exception. Le premier bloc de code déclare les 4 données membres (attributs) de la classe en visibilité protégée. Respectivement, ces attributs enregistrent le message d'erreur, son code, le fichier source concerné et la ligne à laquelle l'erreur a été générée dans le programme.

Puis vient la déclaration du constructeur de la classe qui prend 2 paramètres facultatifs. Le premier est le message d'erreur et le second le code d'erreur.

La troisième partie du corps de la classe concerne la déclaration de 6 méthodes accesseur qu'il est impossible de redéfinir par héritage. Cette restriction est représentée par le mot-clé final. Ces méthodes permettent respectivement de récupérer le message d'erreur, son code d'erreur, le fichier source concerné, la ligne de l'erreur dans le fichier, et la pile des appels.

Enfin, nous découvrons la déclaration de la méthode magique __toString() qui pourra être rédéfinie ensuite par héritage. Cette méthode particulière, lorsqu'elle est surchargée, permet de spécifier de quelle manière on décide de représenter l'état en cours de l'objet sous forme de chaine de caractères.

Générer, lancer et attraper des exceptions à travers le programme

Générer une exception

La création d'une exception est réalisée par l'appel au constructeur de la classe native Exception. Le code ci-dessous illustre cette étape.

<?php
// Création de l'objet Exception
$e = new Exception('Une erreur s\'est produite');
// Affiche le message d'erreur
echo $e->getMessage();
?>

Remarquez la simplicité. La première ligne créer l'objet de type Exception ($e) et assigne automatiquement le message d'erreur dans le constructeur. La seconde ligne de code affiche le message d'erreur enregistré sur la sortie standard.

Note [1] : en développement informatique, les programmeurs ont pris l'habitude de nommer une exception uniquement avec la lettre miniscule "e". C'est à la fois une convention de nommage et une bonne pratique très largement répandue. Toutefois, aucune règle n'oblige les développeurs à l'adopter.

Note [2] : la classe native Exception est chargée automatiquement par PHP, c'est pourquoi il n'est pas nécessaire d'avoir recours à un quelconque import avant de pouvoir l'utiliser.

Lancer une exception à travers le programme

Notre code précédent nous a permis de générer des exceptions. En l'état, ce script ne nous sert strictement à rien puisqu'une exception n'est utile que si elle est créée lorsqu'un évènement exceptionnel se déroule pendant l'exécution du programme. Par exemple : une requête SQL qui échoue, un fichier impossible à ouvrir, une valeur d'un formulaire inattendue pour un champ...

Lorsqu'un tel évènement se produit, c'est que quelque chose d'inhabituel s'est passé. Par conséquent, la poursuite de l'exécution du programme doit être interrompue et signaler l'incident. C'est là qu'interviennent réllement les exceptions. Pour réaliser cette opération, le programme doit automatiquement « lancer » (retenez le vocabulaire de la POO) une exception. Le lancement d'une exception provoque immédiatement l'interruption du déroulement normal du programme

Le lancement d'une exception à travers le programme est réalisée grâce au mot-clé « throw ».

Exemple de lancement d'une Exception à travers le programme
<?php
$password = 'Toto';
if('Emacs' !== $password) {
throw new Exception('Votre password est incorrect !');
}
// Cette ligne ne sera jamais exécutée
// car une exception est lancée pour interrompre
// l'exécution normale du programme
echo 'Bonjour Emacs';
?>

L'utilisation du mot-clé throw permet de stopper l'exécution du programme et rediriger l'exception à travers ce dernier. La commande echo() quant à elle ne sera jamais exécutée. Le mot de passe $password n'étant pas égal à la chaine 'Emacs', l'exception est automatiquement générée puis lancée.

Note [1] : remarquez que l'exception généréese fait à la volée. Du fait qu'elle est automatiquement renvoyée auprogramme, nul besoin de stocker l'objet créé dans une variable.

Note [2] : les exceptions peuvent également être lancées depuis l'intérieur d'une classe.

Cet exemple est encore loin d'être exploitable en l'état. En effet, l'exception qui est lancée au programme est ici perdue pour toujours. Comme si l'on avait oublié de stocker dans une variable le résultat retourné par une fonction. Nous avons déclaré au tout début de ce cours que les exceptions devaient permettre de traiter différemment les cas exceptionnels survenant au cours d'un programme. Pour cela, il est nécessaire de pouvoir « intercepter / attraper » l'exception générée pour appliquer le traitement adéquat. C'est là qu'intervient le bloc try / catch.

Intercepter / attraper une exception générée

Comme en Java, AS 3, VB, C++... PHP dispose d'une structure conditionnelle capable d'intercepter les exceptions en plein vol afin de permettre d'appliquer des traitements particuliers. Il s'agit donc des blocs try { } catch() { }. Cette structure particulière se présente de la manière suivante :

Structure conditionnelle try { } catch() { }
<?php
try {
// Liste d'actions à appeller
// Ces actions peuvent potentiellement
// lancer des exceptions à travers le programme
}
catch(Exception $e)
{
// Bloc des actions spéciales lorsqu'une
// exception $e de type Exception est levée
}
?>

Le bloc try « essaie » d'exécuter le script entre les deux premières accollades. Si une exception est lancée dans ce bloc, elle est immédiatement « attraper » dans le bloc catch() et les traitements particuliers sont exécutés à la place.

Concrètement cela donne le listing ci-après avec notre exemple précédent :

Exemple d'interception d'une exception
<?php
try {
$password = 'Toto';
if('Emacs' !== $password) {
throw new Exception('Votre password est incorrect !');
}
echo 'Bonjour Emacs';
}
catch(Exception $e)
{
echo 'L\'erreur suivante a été générée : '."\n";
echo $e->getMessage();
}
?>

En exécutant ce code, nous découvrons que c'est le code du bloc catch() qui a été exécuté car l'exception lancée dans le bloc try { } a été interceptée en plein vol.

Note [1] : lorsqu'une exception est levée, elle remonte dans le programme et est interceptée par le premier bloc try {} catch() {} englobant qu'elle rencontre. Si aucun bloc try {} catch() {} n'entoure l'exception, alors l'objet sera perdu et l'exécution du programme interrompue.

Note [2] : nous verrons plus loin dans ce tutoriel que nous pouvons appliquer plusieurs bloc catch() sous le bloc try {} afin d'identifier chaque type d'exception potentiellement générée.

Bien sûr, il est possible d'imbriquer les blocs try { } catch() { } comme il est naturellement possible d'imbriquer les blocs if(). Notre exemple peut-être amélioré de la manière suivante :

Exemple d'utilisation de bloc try { } catch() { } imbriqués
<?php
$login = 'Titi';
$password = 'Toto';
try {
if('Hello' !== $login) {
throw new Exception('Votre login est incorrect !');
}
try {
if('Emacs' !== $password) {
throw new Exception('Votre password est incorrect !');
}
echo 'Bonjour Emacs';
}
catch(Exception $e)
{
echo 'L\'erreur suivante a été générée : '."\n";
echo $e->getMessage();
}
}
catch(Exception $e)
{
echo 'L\'erreur suivante a été générée : '."\n";
echo $e->getMessage();
}
?>

Dans cet exemple, la valeur du login est d'abord testée. Comme la valeur du login est incorrecte, une exception est lancée à travers le programme, et c'est le bloc catch() le plus bas qui l'intercepte et exécute le traitement associé. Si maintenant nous remplissons correctement la variable $login, alors le premier bloc try est passé puis l'exception du second bloc try est lancée. Cette dernière est alors interceptée dans le bloc catch() associé pour exécuter le traitement adéquat de l'erreur.

Cet exemple n'est cependant pas très pertinent car il ne profite pas véritablement de toute la puissance des exceptions. La seconde partie de ce cours montre pas à pas comment concevoir des exceptions typées et personnalisées, et comment profiter de celles-ci pour simplifier et optimiser le code.

Introduction à la seconde partie de ce tutoriel

Dans la suite de ce cours, nous étudierons comment rendre l'utilisation des exceptions pertinentes en créant nos propres classes d'exception personnalisées. Nous aurons recours au concept de l'héritage, pilier majeur dans la méthodologie de l'approche orientée objet. Enfin, nous présenterons un aspect pratique de PHP qui permet d'intercepter toutes les exceptions à volée et d'exécuter automatiquement une fonction de callback (de rappel) pour simplifier et centraliser leur traitement.



Les commentaires

1. Par BLANCHON Vincent le 17/06/2008 14:06

"[...]'objet sera perdu mais et l'exécution du programme interrompue.[...]" un "mais" qui traine ... Très sympa les cours il en faudrait un sur les méthodes magiques

2. Par Emacs le 17/06/2008 19:30

Encore merci à toi pour ces corrections

3. Par Emacs le 17/06/2008 23:03

Concernant les méthodes magiques, c'est prévu mais pour le moment je n'ai plus le temps d'écrire pour Apprendre-PHP.com car je travaille sur un projet très intéressant de PHP dont je parlerai très prochainement dans une actualité. Wait and see

4. Par Valentin le 27/10/2008 23:33

Bonjour,

Je parcours plusieurs des tutos proposés sur ce site et souhaite simplement vous félicitez pour la clarté de vos explications et exemples.
Les exceptions étaient pour moi un morceau difficile, tout est maintenant beaucoup plus clair!

Bonne continuation

5. Par saturn1 le 14/12/2008 18:45

C'est intéressant.
Mais concrètement cela sert à la même chose que if/else ^ _ ^

6. Par Emacs le 14/12/2008 19:41

Non pas du tout ! Relis bien le tutoriel pour comprendre ce que sont véritablement les exceptions.

7. Par forever le 16/05/2009 23:47

je suis un pro du c# et je trouve que php n'est pas très au point à part le fait d'être un peu facile, néanmoins ce tuto m'a beaucoup facilité le transfert des compétences
merci pour ce très bon tuto

8. Par Emacs le 17/05/2009 01:05

@forever : PHP est un langage très professionnel bien qu'il ne soit pas encore parfait. Quand tu affirmes qu'il n'est pas au point, j'ai du mal à saisir ce que tu insinues. En quoi n'est-il pas au point ? Performances ? Modèle objet ? API ? syntaxe ?

PHP est un langage tout aussi professionnel que Java ou autres depuis sa version 5 qui a introduit un modèle objet presque complet. Quand on pense que PHP n'est pas suffisamment professionnel ou "au point" comme tu le cites, c'est aussi que l'on ne connaît pas suffisamment le langage et ses subtilités. Je ne connais pas du tout C#, je ne peux donc juger de son niveau et de la réponse qu'il fournit aux besoins des développeurs. Mais aujourd'hui, PHP est un très bon langage performant, mais il toutefois très difficile de trouver de très bons développeurs PHP car PHP jouit encore de sa mauvaise image de langage accessible à tous, contrairement au Java et autres dérivés du C.

Ces langages sont en effet accessibles à des développeurs ayant déjà été formés à la programmation au cours de leur formation post bac. C'est plutôt rare des développeurs Java, C++, C#... qui sont autodidactes et qui n'ont jamais eu de formation en programmation.

Au contraire, avec PHP, on trouve très facilement des jeunes d'une quinzaine d'années qui s'y mettent car la syntaxe leur est accessible, bien qu'ils n'aient pas les concepts fondamentaux de programmation et d'algorithmique qui vont avec.

A l'opposé, on trouve aussi quelques rares excellents développeurs PHP qui ont plusieurs années d'expériences avec le langage PHP (et d'autres bien sûr). Ils en connaissent les spécificités, les astuces, les librairies qui ont fait leurs preuves (PHPDoc, PHPUnit, Propel, Doctrine, phpUnderControl...), ainsi que les frameworks professionnels (Zend Framework, Symfony et autres).

9. Par forever le 18/05/2009 12:13

@Emacs : je parlais de la POO dans le PHP; j'ai toujours l'impression de travailler avec un langage procédural comme le vb6, le soucis de compatibilité ascendant limite trop ce PHP à typage faible, et c'est peut être ça qui a fait sa popularité.
Si on travaille avec le ASP.net on est dans un environement très bien organisé avec des namespace et ce qui va avec, ou même JAVA avec ses classes et librairies à importer , mais le PHP me rappel trop le C avec ses tas de fonctions natives éparpillées.
tout de même il faut avouer que la programmation WEB devraient être plus accessible, et aussi avancé pour répondre au besoin d'entreprise .. ce qui est la nouvelle tendance avec le PHP5 et le 6 en cours de développement afin de palier ce défaut.
merci pour tes explications, et encore merci pour ce merveilleux cours ; il me reste plus que quelques pages pour me lancer dans des testes plus avancés et ce qui dit reste l'avis d'un débuteur en PHP !

10. Par eilijah le 20/06/2009 09:39

@forever : Rien ne t'empeche de programmer en typage fort avec php, par exemple :
<?php
class Foo {}
function useFoo(Foo $obj) { /* ... */ }
$a = new Foo();
useFoo($a); // OK...
$b = new StdClass();
useFoo($b);
// Fatal error: Argument 1 must be an instance of Foo
?>


@Emacs : "Comme elle est fausse, l'exception est générée est le bloc catch()"
Y'a pas une faute qui te dérange là?

Excelents tuto par ailleurs

11. Par Emacs le 20/06/2009 15:35

@elijah : merci c'est corrigé