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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

Théorie de la gestion d'erreur en PHP


Sujet :

Langage PHP

  1. #1
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut Théorie de la gestion d'erreur en PHP
    Bonjour à tous,

    Vu que je reçoit régulièrement des question de ce genre par MP (même si je m'étais juré de ne pas répondre aux questions technique, c'est plus fort que moi ~SEGAAA) je pense qu'il est utile que je fasse le point sur la gestion des erreurs et des exceptions en PHP.

    Les remarques qui suivent sont à l'origine d'un MP adressé par BobbyMcGee, si vous avez vous-même des intérrogation sur le sujet, n'hésitez pas à poster en enfilade (plutôt que d'envoyer des MP !)

    Théorie de la gestion d'erreur en PHP

    Je suis habitué à des langages comme Java ou C# où toute erreur remonte sous la forme d'une exception. J'ai lu quelque part que seules les extensions récentes de PHP (type PDO) lèvent des exceptions en cas d'erreur. Est-ce exact ? Comment gérer les autres erreurs ?
    En effet, les exceptions étant apparues avec la version 5 du langage, seules les extensions développées depuis lancent des exceptions. Pour ce qui est de PDO, le mécanisme par défaut est de lever des erreurs (E_WARNING), il faut lui spécifier explicitement l'utilisation des exceptions avec
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    Comme tu le sais, PHP n'est pas un langage compilé. Du coup, les erreurs de syntaxe (et certaines erreurs de runtime) ne peuvent pas être détectées par le compilateur. C'est pourquoi PHP lève des erreurs (E_NOTICE, E_WARNING et la plus grave E_FATAL_ERROR qui indique que la state machine est dans un état inconnu), il est d'ailleurs possible de lever manuellement ces erreurs avec trigger_error (en utilisant les codes E_USER_x). Ces erreurs peuvent être traitées lors du runtime à l'aide des fonctions error_get_last ou encore set_error_handler. Mais ce mécanisme présente le défaut de ne pas permettre la remontée de l'erreur (et donc l'arrêt de l'exécution du bloc en cours). C'est pourquoi la version 5 intègre les exceptions.

    En PHP; il n'existe que 3 façons admises de gérer les cas d'erreurs:
    • en renvoyant un statut d'erreur (par exemple false) mais cela à l'inconvénient de ne pas être très explicite sur l'erreur elle-même
    • en émettant une erreur PHP mais cela à l'inconvénient de ne pas arrêter l'exécution en cours (sauf avec E_USER_ERROR qui est l'équivalent de E_ERROR et qui bloque le script), de plus les erreurs sont loggées dans le log d'erreur d'apache par défaut...
    • en levant une exception


    Ex:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <?php
     
    /**
     * Dans ce cas, on renvoie false en cas d'erreur mais on ne signale pas ce qui ne va pas.
     * @return int, false in case of error
     */
    function square ($a) {
        if (!$a)
            return false;
     
        if (!is_int($a))
            return false;
     
        return pow($a, 2);
    }
     
    /**
     * Dans ce cas, on renvoie false et on signale ce qui s'est mal passé avec une erreur
     * @return int, false in cas of error
     */
    function square ($a) {
        if (!$a) {
            trigger_error("invalid value", E_USER_WARNING);
            return false;
        }
        if (!is_int($a)) {
            trigger_error("invalid type", E_USER_WARNING);
            return false;
        }
     
        return pow($a, 2);
    }
     
    /**
     * Dans ce cas, on ne renvoie rien et on lance une exception
     * @throw UnexpectedValueException if $a is empty
     * @throw InvalidArgumentExceptino if $a is not integer
     * @return int
     */
    function square  ($a) {
        if (!$a)
            throw new UnexpectedValueException("invalid value for a");
     
        if (!is_int($a))
            throw new InvalidArgumentException("invalid type for a");
     
        return pow($a, 2);
    }

    La gestion par erreur + return ressemble de près à la gestion par exception (à ceci près que les erreurs ne remontent pas tant qu'elles ne sont pas catchées comme les exceptions) et dans beaucoup de cas, cette gestion peut suffire. Il existe en revanche des cas ou ce n'est pas possible, ce sont pour ces cas exceptionnels que tous les langages objet du monde implémentent les exceptions. C'est notamment le cas du constructeur, si une erreur était détectée pendant la construction d'un objet, que faudrait il faire vu que les constructeurs ne retournent rien (même pas void) ?

    On peut effectivement toujours s'en sortir avec un paramètre out:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?php
     
    class Foo {
     
        protected $_value;
     
        public function __construct ($a, & $return = null) {
            if (!$a) {
                $return = false;
                trigger_error("invalid value", E_USER_WARNING);
                return;
            }
            if (!is_int($a)) {
                $return = false;
                trigger_error("invalid type", E_USER_WARNING);
                return false;
            }
     
            $this->_value = pow($a, 2);
        }
    }

    Mais c'est outrageusement impropre, ça complexifie inutilement le code de notre constructeur et ça rajoute des paramètres pas bien clairs sur son prototype. Il vaut mieux lever une exception:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    class Foo {
     
        protected $_value;
     
        public function __construct ($a) {
            if (!$a)
                throw new UnexpectedValueException("invalid value for a");
            if (!is_int($a))
                throw new InvalidArgumentException("invalid type for a");
     
            $this->_value = pow($a, 2);
        }
    }

    Pour pouvoir attraper une exception, il faut impérativement qu'elle soit lancée à l'intérieur d'un bloc try / catch. En PHP, il est possible de catcher plusieurs fois:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?php
     
    function foo () {
        bar();
    }
     
    function bar () {
        throw new LogicException("aha");
    }
     
    try {
        foo();
    }
    catch (RuntimException $e) {
        // do something...
    }
    catch (InvalidArgumentException $e) {
        // do something else...
    }
    catch (Exception $e) { // ~pokééémooooon (gotta catch em all)
        // attrape tout (car toute classe d'exception dérive d'Exception)
    }

    On notera également que les blocs try / catch peuvent s'imbriquer théoriquement à l'infini, ce qui fait qu'un catch peut tout à fait effectuer un rethrow, ce qui est utile car depuis PHP 5.3 on peut définir pour une exception, quelle à été l'exception d'origine ($previous).

    Ex:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?php
     
    function foo () {
        try {
            bar();
        }
        catch (Exception $e) {
            throw new Exception("foo exception", 0, $e);
        }
    }
     
    function bar () {
        throw new Exception("bar exception");
    }
     
    try {
        foo();
    }
    catch (Exception $e) {
        var_dump($e->getPrevious());
    }

    Je ne le répéterai jamais assez mais les exceptions sont exceptionnelles ! Elles doivent êtres utilisées quand rien d'autre n'est possible (ou plutôt sémantiquement envisageable - car tout est toujours possible en informatique).

    Gestion des erreurs

    Existe-t-il un moyen de basculer en mode "full exception" afin d'unifier la gestion des erreurs ?
    Oui, c'est possible avec set_error_handler:

    Ex:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    <?php
     
    function handle_error ($errno, $errstr, $errfile, $errline) {
        throw new ErrorException($errstr, 2048, $errno, $errfile, $errline);
    }
     
    set_error_handler('handle_error');

    Mais ce n'est pas toujours souhaitable car comme on l'a vu, les exception on un mécanisme différent des erreurs. De plus, PHP à tendance à lever des erreurs pour presque n'importe quoi (E_DEPRECATED, E_NOTICE, E_STRICT sont des erreurs mineures). C'est donc un mécanisme à utiliser avec des script dits E_STRICT safe qu'on contrôle de bout en bout. Généralement, on se sert plus volontiers de set_error_handler pour du logging.

    Ex:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
     
        ... ma classe de log
     
        public static warning ($message) {
            // écrit dans le fichier de log
        }
     
        ...
     
        public static function handleError ($errno, $errstr, $errfile, $errline) {
            $error = "(PHP Error) $errstr in $errfile on line $errline";
            switch ($errno) {
                case E_WARNING:
                case E_USER_WARNING:
                    self::warning($error);
                    break;
     
                case E_NOTICE:
                case E_USER_NOTICE:
                    self::notice($error);
                    break;
     
                default:
                case E_USER_ERROR:
                    self::error($error);
                    break;
     
                case E_RECOVERABLE_ERROR:
                    throw new ErrorException($errstr, 2048, $errno, $errfile, $errline);
                    break;
            }
        }

    On notera dans mon dernier exemple le cas du E_RECOVERABLE_ERROR, il s'agit d'un cas particulier d'erreur qu'on est capable de "catcher". On rencontre cette erreur par exemple lorsqu'une méthode __toString n'a pas renvoyé une chaîne de caractères comme elle devrait. Convertir cette erreur en exception est plutôt une bonne idée.

    Conclusion

    En aucun cas les exceptions ne se substituent au mécanisme traditionnel de gestion des erreurs de PHP. Aucun développeur PHP ne saurait ignorer l'un des deux mécanismes tant ils sont complémentaires. Les exception, de par leur puissance, sont un moyen fiable et précis de gêrer les erreurs mais il faut savoir ne pas en abuser, et c'est malheureusement quelque chose que je ne peux pas vous apprendre, vous devez le découvrir par la pratique.

    Faites vous même quelques essais pour vous familiariser avec tout ça, n'oubliez pas de rajouter
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    <?php
    ini_set('error_reporting', -1);
    ini_set('display_errors', 1);
    au début de votre script afin que toutes les erreurs soient affichées, vous devriez avoir quelques surprises

    Dans tout les cas, n'oubliez jamais qu'a chaque fois que vous écrivez or die(...) dans un script, je tue un chaton (et je ne veux pas de débat sur l'utilisabilité du or die dans ce thread, vous êtes prévenus ) !

  2. #2
    Modérateur
    Avatar de grunk
    Homme Profil pro
    Lead dév - Architecte
    Inscrit en
    Août 2003
    Messages
    6 691
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Lead dév - Architecte
    Secteur : Industrie

    Informations forums :
    Inscription : Août 2003
    Messages : 6 691
    Points : 20 222
    Points
    20 222
    Par défaut
    Je rajouterais un lien pour compléter cette très bonne réponse :

    http://phperror.net/

    Une libraire qui vous rend presque heureux quand une erreur survient
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  3. #3
    Membre expert Avatar de RunCodePhp
    Profil pro
    Inscrit en
    Janvier 2010
    Messages
    2 962
    Détails du profil
    Informations personnelles :
    Localisation : Réunion

    Informations forums :
    Inscription : Janvier 2010
    Messages : 2 962
    Points : 3 947
    Points
    3 947
    Par défaut
    Excellent tuto, même si ça n'était pas l'intention, je le prends comme tel

    n'oubliez jamais qu'a chaque fois que vous écrivez or die(...) dans un script, je tue un chaton
    Excellent
    +1
    Et 2 chatons

    [coup d'gueule]
    C'est comme les mysql_truc_machin_qui_m'enerve
    Un de ces 4, je commencerais par dire au gars des virer ces or die() ou autre mysql_énervant_car_obsolète, et après on verra pour le coup d'main
    [/coup d'gueule]


    Bien qu'estimant connaitre au moins les bases sur la gestion des erreurs et Exceptions, il y a tout de même des points où même la doc reste (encore) à mes yeux extrêmement flou.
    Ca l'est au niveau du type d'Exception.

    La doc fourni une liste de type d'Exception (du moins, je les nommes comme ça), la doc appel ça : un jeu d'Exceptions standards de SPL.
    La liste : SPL Exceptions

    Le problème c'est que pour certaines je ne sais vraiment pas lequel utiliser.
    Du coup j'ai tendance à renvoyer une simple Exception, donc de cette classe.

    Prenons par exemple :
    LogicException, la doc dit :
    Citation Envoyé par php.net
    Exception qui représente les erreurs dans la logique du programme. Ce type d'exceptions doit faire directement l'objet d'une correction de votre code.
    Et celle ci : DomainException, la doc dit :
    Citation Envoyé par php.net
    Exception lancée si une valeur n'adhère pas à un domaine de données défini et valide.
    Le choix entre lancer une Exception de logique ou d'un domaine, franchement, c'est vague.


    Et puis (toujours pour les Exceptions standards) par moment la doc parle d'erreur détectée à la compilation, donc on suppose que d'autres ne le seraient pas.
    Là aussi je ne vois pas vraiment.
    Certes, c'est évident que cela est lié que je ne sache pas ce que représente cette phase de compilation.


    Si ce n'est pas abusé, il y a t-il moyen d'avoir quelques explications ou/et précisions sur les points évoqués ci-dessus.
    Win XP | WampServer 2.2d | Apache 2.2.21 | Php 5.3.10 | MySQL 5.5.20
    Si debugger, c'est supprimer des bugs, alors programmer ne peut être que les ajouter [Edsger Dijkstra]

  4. #4
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Les erreurs de compilation sont rares mais elles existent, je suis tombé une paire de fois dessus mais franchement faut y aller pour les produire - je saurais d'ailleurs pas comment faire aujourd'hui

    Fatal compile-time errors. This is like an E_ERROR, except it is generated by the Zend Scripting Engine.
    Avec ça on est pas plus avancé sur le sujet. Tout ce qu'on retiendra c'est qu'il s'agit grosso-modo des mêmes erreurs que les FATAL. Si Julien passe dans le coin, il pourra peut être nous en dire un peu plus...

    Concernant les types d'exceptions, j'ai moi même du mal à trouver lesquelles utiliser. Finalement, le mieux est de se créer ses propres exception (ClassNotFoundException, HTTPException, ViewException etc.)

  5. #5
    Membre éprouvé

    Profil pro
    Inscrit en
    Juin 2007
    Messages
    748
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2007
    Messages : 748
    Points : 1 022
    Points
    1 022
    Par défaut
    Merci beaucoup pour ces indications,

    reste a choisir ou on va pouvoir écrire les logs : en bdd, sur un fichier ou autre; tu as peu être des recommandations a ce niveaux.

    Une question en plus : comment écrire les logs erreurs, pour avoir l'essentiel, et pour détecter très rapidement d’où vient la panne ?
    Conception / Dev

  6. #6
    Expert éminent
    Avatar de Benjamin Delespierre
    Profil pro
    Développeur Web
    Inscrit en
    Février 2010
    Messages
    3 929
    Détails du profil
    Informations personnelles :
    Âge : 36
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Février 2010
    Messages : 3 929
    Points : 7 762
    Points
    7 762
    Par défaut
    Pour mes logs, j'ai implémenté le mécanisme Chain-of-resposibitilty qui permet à un message de "traverser" une chaîne de loggers (attachés les uns aux autres). Lors de la notification, un logger détecte si le message lui est destiné (à l'aide d'un masque) et si tel est le cas, l'enregistre sur son média (fichier, base de données, mail ou autre).

    J'ai implémenté cette solution de façon libre dans Axiom:


    Pour initialiser le log, je fais tout simplement:
    Code PHP : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
     * General log
     */
    Axiom::log()->addLogger('axTextLogger', AXIOM_APP_PATH . '/ressource/log/app.log');
     
    /**
     * Error log
     */
    Axiom::log()->addLogger('axTextLogger', AXIOM_APP_PATH . '/ressource/log/error.log', 5);
    (voir https://github.com/bdelespierre/php-.../bootstrap.php)

    Si jamais on a besoin d'un log supplémentaire, on peut tout simplement créer le logger approprié (par exemple SQLLogger) et directement l'attacher au log principal.

    A noter que la classe axLog est capable également de logger les erreurs / exceptions levées par PHP. C'est extrêmement pratique à la fois en développement et en production (n'oubliez pas d'utiliser une rotation en crontab sur vos logs en prod).

    Sur d'autre environnements, j'ai spécialisé logs à l'aide d'une constante définie dans php.ini (qu'on obtiens avec get_cfg_var), ce qui permet de régler la quantité et le détail des informations de log (en réalité, ça détermine quel logger utiliser et quel masque de message attraper tout simplement).

    Selon moi, le plus simple (et le plus rapide à implémenter) c'est le log sur fichier. Si le besoin devient complexe et qu'il faut être capable d'effectuer des recherches dans les logs ou d'en extraire des données, ça devient intéressant de le mettre en base.

    Voici une façon simplissime de créer un log:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    ini_set('error_log', 'error.log');
    ini_set('error_reporting', -1);
    ini_set('display_errors', 0);
     
    1/0;
     
    error_log("foobar");
    Pas forcément besoin d'aller chercher bien loin parfois, ce snippet peut suffire dans bien des cas

Discussions similaires

  1. [SQL-Server] Gestion des erreurs avec PHP
    Par arthuro45 dans le forum PHP & Base de données
    Réponses: 0
    Dernier message: 05/04/2010, 18h21
  2. [php]Gestion des erreurs
    Par petchos dans le forum PostgreSQL
    Réponses: 1
    Dernier message: 15/02/2008, 14h26
  3. [Oracle] [PHP] Gestion des erreurs de connexion
    Par Mimo dans le forum PHP & Base de données
    Réponses: 4
    Dernier message: 17/06/2006, 01h17
  4. [PHP-JS] gestion des erreurs sur liste déroulente
    Par HwRZxLc4 dans le forum Langage
    Réponses: 9
    Dernier message: 28/05/2006, 03h21

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo