IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Exceptions et PHP5

Exceptions et PHP5


précédentsommairesuivant

III. Exceptions ou erreurs ?

Dans ce paragraphe, nous allons comparer les 2 systèmes (exceptions/erreurs). En fait, nous allons nous concentrer sur les exceptions qui est le sujet principal, car on remarquera vite que les inconvénients de l'un sont les avantages de l'autre et réciproquement.

III-A. Avantages des exceptions

III-A-1. Lisibilité

Comme je l'ai déjà écrit plusieurs fois, les exceptions ne sont pas identiques aux erreurs dans le sens ou elles représentent un fonctionnement normal, mais exceptionnel. Nous allons maintenant voir un exemple dans lequel les exceptions nous aident grandement et montrent clairement que le mécanisme de gestion d'erreurs n'a rien à voir là-dedans. Considérons une page de traitement de formulaire d'inscription à un site. Nous avons le pseudo, le mot de passe et la confirmation ainsi que l'adresse mail qui arrivent du formulaire. Il faudra vérifier que le pseudo n'existe pas, que le mot de passe et la confirmation sont identiques et que le mail est correct.

On pourrait y arriver avec une avalanche de if, mais ça deviendrait vite suffisamment compliqué pour qu'une erreur s'y glisse. Par exemple :

Page d'inscription utilisant une avalanche de if
Sélectionnez

$result = mysql_query("
    SELECT 1 FROM membres 
    WHERE login='".mysql_real_escape_string($_POST['login'])."'");
 
if (mysql_num_rows($result) == 0)
{
    // le pseudo n'est pas pris, on continue
    if ($_POST['pass'] == $_POST['confirmation'])
    {
        // Le mot de passe et la confirmation coïncident
        if (isValide($_POST['mail']))
        {
            // Le mail est valide, on insère le mebre
            insere_membre($_POST['login'], $_POST['pass'], $_POST['mail']);
        }
        else
        {
            affiche_formulaire('mail_invalide');
        }    
    }
    // Le mot de passe et la confirmation ne coïncident pas
    else
    {
        affiche_formulaire('error_mdp');
    }
}
// Le pseudo est pris, on affiche le formulaire
else
{
    affiche_formulaire('error_login');
}

Imaginez si on doit effectuer une vérification sur 200 champs de fomulaire. C'est sincèrement ingérable à cause de la profondeur des if.

Grâce aux exceptions, on ne s'enfonce plus dans une telle profondeur de if, et on n'appelle plus qu'une seule fois la fonction affiche_formulaire. Ainsi, le code précédent devient :

Page d'inscription utilisant les exceptions
Sélectionnez

try
{
    $result = mysql_query("
        SELECT 1 FROM membres 
        WHERE login='".mysql_real_escape_string($_POST['login'])."'");
 
    if (mysql_num_rows($result) > 0)
    {
        throw new Exception('error_login');
    }
 
    if ($_POST['pass'] != $_POST['confirmation'])
    {
        throw new Exception('error_mdp');
    }
 
    if ( ! isValide($_POST['mail']))
    {
        throw new Exception('mail_invalide');
    }
}
catch(Exception $myException)
{
    affiche_formulaire($myException->getMessage());
}

III-A-2. Fiabilité

Les exceptions permettent de ne pas stopper net un système ayant généré une erreur. Ceci permet d'effectuer un traitement adapté au problème rencontré, voire parfois de le réparer. Ainsi, certains langages comme l'ADA ont intégré entièrement les exceptions dans leur langage pour gérer les erreurs liées au système. C'est pourquoi l'ADA est très utilisé en milieu militaire et aérospatial. En effet, même dans ces systèmes coutânt des milliards, il y a des bugs, des erreurs... Il vaut donc mieux être sûr que même en cas d'erreur, le système ne va pas simplement s'arréter. Java, bien que possédant une gestion des erreurs, est très orienté exception, ce qui a contribué (entre autre) à son succès dans le monde industriel. Une classe Java annonce quelles exceptions elle lance. Ainsi, un code source Java ne traitant pas ces exceptions n'est simplement pas compilé. Tous les autres langages utilisent soit les erreurs uniquement, soit un répartition équilibrée entre exception et erreur.
Evidemment, pour augmenter réellement la fiabilité des systèmes, il est important de bien réfléchir à la cohérence des classes d'exception et de bien les capturer. Sinon, le système ne correspondra qu'à une gestion des erreurs traditionnelles.

III-B. Inconvénients des Exceptions

III-B-1. Implémentation "partielle"

Dans PHP5, le mécanisme d'exception est totalement opérationnel, mais ne constitue qu'un apport pour le développeur et non pour le langage. C'est à dire que PHP5 n'utilise pas les exceptions. L'ensemble des fonctions de PHP génèrent toujours des erreurs PHP lorsqu'elles rencontrent un problème, seules quelques extensions ont adopté le modèle des exceptions. Cette faible présence des exceptions dans PHP5 s'explique par :

  • La programmation objet en PHP n'a de réel sens que depuis PHP5 or, pour utiliser au mieux les exceptions, il est nécessaire pour un langage de possèder un bon modèle objet.
  • Lorsqu'une exception est soulevée, le contexte local contenu dans un bloc try est sauvegardé, ceci peut être amplifié par l'imbrication de bloc try. L'abus de l'utilisation des exceptions peut donc diminuer considérablement les performances.
  • Par choix. La plupart des langages choisissent de ne pas supprimer la gestion des erreurs au profit des exceptions pour des raisons de performances et de style de programmation (les exceptions sont équivalentes pour certains programmateurs à l'affreux goto, de plus les exceptions augmentent considérablement le nombre de lignes de code). D'autres langages, comme l'ADA, n'utilisent que les exceptions.

III-B-2. Boucle infinie

Les exceptions permettent un traitement personnalisé d'une erreur. Imaginons un système d'exception unique pour tout un script, que le développeur utilise pour fiabiliser l'accès à un fichier, la connexion à une BDD... Dans cette classe d'exception, il décide de logguer toutes les exceptions soulevées afin de garder une trace. Pour cela, il utilise une BDD. Si la première connexion à la base de donnée ne se fait pas, on soulève une exception, qui tentera de se connecter à la base de donnée, ce qui soulèvera une exception... On obtient des comportements plutôt imprévus, pouvant aller jusqu'à la boucle infinie si le script implémente la fonction set_exception_handler. Il est donc très important d'éviter ce genre de scénario. La meilleure méthode pour éviter ceci est de bien distinguer les différentes classes d'exception dont le script a besoin. En créant ici une classe d'Exception spécifique à la base de données, on aurait forcement compris que l'on ne peut pas utiliser la base de données, vu que l'appel même de l'exception signifie que la connexion à la base de données a échoué. On utilisera alors un fichier. De même, on logguera les exceptions soulevées lors d'un accès fichier dans la BDD et non dans un fichier.

En réalité, PHP empêche ceci. Si il détecte une exception lancée à l'intérieur du traitement d'une autre exception (que ce soit avec catch ou set_exception_handler), il stoppe l'éxecution et affichera un message d'erreur. Ainsi, le code suivant :

Exception lancée dans le code de traitement d'une autre exception
Sélectionnez

<?php
 
class MyException extends Exception
{
    public function __construct($msg=null, $code=0)
    {
        parent::__construct($msg, $code);
    }
 
    // 3 ) La fonction addLog va essayer de se connecter à MySQL, or cette fonction est 
    //  justement appelée car une précédente tentative de connexion a échouée.
    public function addLog()
    {
        // La tentative échoue encore
        if ( ! @mysql_connect($server, $user, $pass) )
        {
            // 4) On lance une exception non capturée (on pourait la capturer, 
            //  l'erreur serait identique)
            throw new MyException('Impossible de se connecter à MySQL dans la classe MyException');
        }
    }
}
 
// 5 ) On attrape l'exception lancée dans la méthode addLog 
//      et on ressaie d'insérer un log...
//    On voit ici la boucle infinie qui se crée entre l'appel de 
//      cette fonction exception_handler et la méthode addLog.
function exception_handler($myException)
{
    $myException->addLog();
}
 
set_exception_handler('exception_handler');
 
// 1) Le commencement, on ouvre une connexion à MySQL, 
        qui bien sûr ne va pas fonctionner
try
{
    if ( ! @mysql_connect($server, $user, $pass) )
    {
        // On lance donc l'exception
        throw new MyException('Impossible de se connecter à Mysql dans le script');
    }
}
// 2) On capture l'exception et on applique le traitement, 
//      c'est à dire insérer un log
catch(MyException $e)
{
    $e->addLog();
}
 
 
?>

Affichera l'erreur suivante.

Erreur générée
Sélectionnez

Fatal error: Exception thrown without a stack frame in Unknown on line 0

Pour résumer : il faut bien être conscient de la cause d'une exception, de façon à ne pas appeler de nouveau dans son traitement la portion de code responsable de sa levée. Le mieux est d'établir une hiérarchie des classes d'exception. Par exemple, si on lance une exception dans le script, le traitement pourra tenter d'écrire dans la base de données, s'il n'y parvient pas, il lance une exception qui tentera d'écrire dans un fichier. Si ce traitement est aussi un échec, une nouvelle exception tentera simplement d'afficher à l'ecran, si rien ne fonctionne, on lance une erreur simple, ou on ne fait rien du tout. Le meilleur moyen d'éviter cette erreur consiste à limiter les opérations à risque dans le code de traitement des exceptions.


précédentsommairesuivant

Copyright © 13/09/2006 Guillaume Affringue. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.