PHP: exceptions vs erreurs?

116

Il me manque peut-être quelque part dans le manuel PHP, mais quelle est exactement la différence entre une erreur et une exception? La seule différence que je peux voir est que les erreurs et les exceptions sont gérées différemment. Mais qu'est-ce qui cause une exception et qu'est-ce qui cause une erreur?

Jason Baker
la source

Réponses:

87

Des exceptions sont lancées - elles sont destinées à être capturées. Les erreurs sont généralement irrécupérables. Disons par exemple - vous avez un bloc de code qui insérera une ligne dans une base de données. Il est possible que cet appel échoue (ID en double) - vous voudrez avoir une "Erreur" qui dans ce cas est une "Exception". Lorsque vous insérez ces lignes, vous pouvez faire quelque chose comme ceci

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

L'exécution du programme continuera - parce que vous avez «attrapé» l'exception. Une exception sera traitée comme une erreur sauf si elle est interceptée. Cela vous permettra également de poursuivre l'exécution du programme après son échec.

gnarf
la source
29
Errors are generally unrecoverable<- en fait, ce n'est pas vraiment vrai. E_ERRORet E_PARSEsont les deux erreurs irrécupérables les plus courantes (il y en a quelques autres) mais la grande majorité des erreurs que vous verrez dans le développement sont récupérables ( E_NOTICE, E_WARNINGet al). Malheureusement, la gestion des erreurs de PHP est un désordre complet - toutes sortes de choses déclenchent des erreurs inutilement (la grande majorité des fonctions du système de fichiers, par exemple). En général, les exceptions sont "la manière OOP", mais malheureusement certaines des API natives de PHP utilisent des erreurs au lieu d'exceptions :-(
DaveRandom
1
@DaveRandom E_NOTICE, E_WARNING ne sont pas des "erreurs" par définition, n'est-ce pas? Je les ai toujours considérés comme des «messages» que PHP affichait pour avertir le programmeur que quelque chose ne va pas avec le code qu'il a écrit.
slhsen
2
@slhsen le problème est vraiment une terminologie merdique, toutes les formes de ces messages passent par le "système de gestion des erreurs" en PHP, sémantiquement tous ces événements sont des "erreurs", même si sémantiquement avis / avertissement n'est certainement pas la même chose qu'un " erreur "dans ce contexte. Heureusement, le prochain PHP7 a au moins ouvert la voie au tri de ce désordre en transformant la plupart de ces choses en exceptions capturables (au moyen d'une nouvelle Throwableinterface), donnant un moyen beaucoup plus expressif et absolu de distinguer et de remettre correctement les deux réels problèmes et messages d'
avertissement
"L'exécution du programme continuera" a changé je suppose? Puisque PHP dit "Lorsqu'une exception est levée, le code suivant l'instruction ne sera pas exécuté" ( php.net/manual/en/language.exceptions.php )
Robert Sinclair
1
Je pense que ce que signifiait l'OP concernait davantage la différence entre les descendants de ErrorVS les descendants de Exception.
XedinUnknown
55

Je suis généralement set_error_handlerà une fonction qui prend l'erreur et lève une exception afin que, quoi qu'il arrive, je n'ai que des exceptions à gérer. Plus @file_get_contentsjuste d'essayer / attraper gentil et soigné.

Dans les situations de débogage, j'ai également un gestionnaire d'exceptions qui génère une page similaire à asp.net. Je poste ceci sur la route, mais si demandé, je publierai un exemple de source plus tard.

Éditer:

Ajout comme promis, j'ai coupé et collé une partie de mon code ensemble pour en faire un échantillon. J'ai enregistré le fichier ci-dessous sur mon poste de travail, vous ne pouvez PLUS voir les résultats ici (car le lien est rompu).

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }

    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }

?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }

            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }

            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>
Kris
la source
Ce serait utile. Tout ce qui facilite les temps que je suis obligé de gérer avec PHP aidera. :-)
Jason Baker
Beau code, merci. Je ne sais pas d'où vient la classe X, et quel est son but?
Alec
tout ce qui est en dessous de "set_exception_handler ('global_exception_handler');" est juste une démo, vous n'en aurez pas besoin, c'est juste pour montrer ce qui se passerait dans une situation d'erreur normalement sans exception.
Kris le
Le PHP standard définit ErrorException spécifiquement pour être lancé à partir d'un gestionnaire d'erreurs général. Me permettez-vous de modifier et de mettre à jour votre message?
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan: bien sûr, mais l'exemple de travail ne sera pas synchronisé. De plus, de nos jours, je dirigerais probablement les gens vers github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php de private-void.com à la place.
Kris du
21

La réponse mérite de parler de l'éléphant dans la pièce

Les erreurs sont l'ancienne façon de gérer une condition d'erreur au moment de l'exécution. En général, le code appelle quelque chose comme set_error_handleravant d'exécuter du code. Suivant la tradition des interruptions en langage assembleur. Voici à quoi ressemblerait un code BASIC.

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

Il était difficile de s'assurer que ce set_error_handlerserait appelé avec la bonne valeur. Et pire encore, un appel pourrait être fait à une procédure distincte qui changerait le gestionnaire d'erreurs. De plus, plusieurs fois, les appels étaient entrecoupés d' set_error_handlerappels et de gestionnaires. Il était facile pour le code de devenir rapidement incontrôlable. La gestion des exceptions est venue à la rescousse en formalisant la syntaxe et la sémantique de ce que le bon code faisait vraiment.

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

Aucune fonction distincte ou risque d'appeler le mauvais gestionnaire d'erreurs. Le code est désormais garanti au même endroit. De plus, nous obtenons de meilleurs messages d'erreur.

Auparavant, PHP n'avait que la gestion des erreurs, alors que de nombreux autres langages avaient déjà évolué vers le modèle de gestion des exceptions préférable. Finalement, les fabricants de PHP ont implémenté la gestion des exceptions. Mais susceptibles de prendre en charge l'ancien code, ils ont conservé la gestion des erreurs et ont fourni un moyen de faire ressembler la gestion des erreurs à la gestion des exceptions. Sauf que, il n'y a aucune garantie que certains codes peuvent ne pas réinitialiser le gestionnaire d'erreurs, ce qui était précisément ce que la gestion des exceptions était censée fournir.

Réponse finale

Les erreurs qui ont été codées avant la mise en œuvre de la gestion des exceptions sont probablement des erreurs. Les nouvelles erreurs sont probablement des exceptions. Mais il n'y a pas de conception ou de logique à laquelle sont des erreurs et qui sont des exceptions. Il est simplement basé sur ce qui était disponible au moment où il a été codé et sur la préférence du programmeur qui le codait.

Arturo Hernandez
la source
3
C'est la vraie raison pour laquelle les exceptions et les erreurs coexistent. S'il est conçu à partir de zéro, php ne devrait inclure que l'un ou l'autre.
Tomas Zubiri
1
C'est la meilleure réponse à mon avis, car c'est la plus détaillée et la plus explicative.
Robert Kusznier
8

Une chose à ajouter ici concerne la gestion des exceptions et des erreurs. Pour les besoins du développeur d'applications, les erreurs et les exceptions sont des «mauvaises choses» que vous souhaitez enregistrer pour en savoir plus sur les problèmes rencontrés par votre application - afin que vos clients aient une meilleure expérience à long terme.

Il est donc logique d'écrire un gestionnaire d'erreurs qui fait la même chose que ce que vous faites pour les exceptions.

Alex Weinstein
la source
Merci de fournir le lien!
Mike Moore
@Alex Weinstein: le lien est rompu
Marco Demaio
7

Comme indiqué dans d'autres réponses, définir le gestionnaire d'erreurs sur le lanceur d'exceptions est le meilleur moyen de gérer les erreurs en PHP. J'utilise une configuration un peu plus simple:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

Veuillez noter le error_reporting()chèque à conserver@ opérateur travailler. De plus, il n'est pas nécessaire de définir une exception personnalisée, PHP a une classe intéressante pour cela.

Le grand avantage de lancer des exceptions est que l'exception a une trace de pile qui leur est associée, il est donc facile de trouver où est le problème.

Josef Kufner
la source
5

Re: "mais quelle est exactement la différence entre une erreur et une exception?"

Il y a beaucoup de bonnes réponses sur les différences ici. Je vais juste ajouter quelque chose dont on n'a pas encore parlé: la performance. Plus précisément, il s'agit de la différence entre lancer / gérer des exceptions et gérer un code de retour (succès ou erreur). Habituellement, en php, cela signifie retourner falseou null, mais ils peuvent être plus détaillés, comme avec le téléchargement de fichiers: http://php.net/manual/en/features.file-upload.errors.php Vous pouvez même renvoyer un objet Exception !

J'ai fait quelques performances dans différentes langues / systèmes. En règle générale, la gestion des exceptions est environ 10 000 fois plus lente que la recherche d'un code de retour d'erreur.

Donc, s'il faut absolument, définitivement, terminer son exécution avant même qu'elle ne commence - eh bien, vous n'avez pas de chance car le voyage dans le temps n'existe pas. Sans voyage dans le temps, les codes de retour sont l'option la plus rapide disponible.

Éditer:

PHP est hautement optimisé pour la gestion des exceptions. Les tests du monde réel montrent que lever une exception n'est que 2 à 10 fois plus lent que de renvoyer une valeur.

Evan
la source
3
Bien sûr, mais le nombre de cycles perdus pour lancer des exceptions est plus que compensé par les pouvoirs descriptifs supplémentaires que vous obtenez avec les exceptions. Vous pouvez lever des types d'exceptions spécifiques, voire ajouter des données à l'exception pour contenir les codes d'erreur. Je doute aussi sérieusement de votre réclamation de 10 000 *. Même si vous avez raison sur le décalage horaire, le temps passé à faire le retour et si par rapport à la nouvelle Execption, lancer, attraper dans n'importe quel scénario du monde réel est probablement si minuscule par rapport au code exécuté qu'il s'agit certainement d'une optimisation prématurée. Jetez des exceptions, ils sont plus agréables à gérer 90% du temps.
gnarf
1
1. 10,000x est précis - avec une certaine variance basée sur les options du langage et du compilateur. 2. Vous n'avez pas à retourner null / false. Vous pouvez renvoyer un nombre - jusqu'à MAX_ULONG codes retour directement. Vous pouvez également renvoyer une chaîne d'échec et simplement rechercher une chaîne de réussite ou un int ou une valeur nulle. 3. Dans les scénarios du monde réel, chaque cycle d'horloge compte. Facebook compte 552 millions d'utilisateurs actifs par jour. En supposant que les exceptions ne sont que 2x et que la vérification de l'utilisateur / du pass prend 0,001 s, ce qui signifie une économie de 153 heures de temps de traitement chaque jour. À 10 000 fois, cela permet d'économiser 175 ans. Juste pour vérifier les tentatives de connexion - chaque jour.
evan
@evan: FYI, ici ils ont testé le code avec des exceptions et cela ne semble pas être plus lent: stackoverflow.com/a/445094/260080
Marco Demaio
@MarcoDemaio Cette question ne couvre que le bloc try / catch sans lever d'exception. Un meilleur test serait de renvoyer une valeur dans noexcept () et de lancer une exception dans except (). En outre, il devrait bouillonner à travers plusieurs fonctions. stackoverflow.com/a/104375/505172 indique que la différence de PHP est en fait de 54x. J'ai effectué mon propre test en temps réel et il semble être 2 à 10 fois plus lent. C'est bien mieux que prévu.
evan
@evan: Je ne serais pas inquiet alors, j'utilise des exceptions uniquement pour suivre les erreurs inattendues / irrécupérables, donc même si ce serait 100 fois plus lent, je m'en fiche. Mes soucis étaient de rendre le code plus lent en ajoutant simplement des blocs try / catch.
Marco Demaio
4

Je pense que la réponse que vous recherchez est la suivante;

Les erreurs sont les choses standard auxquelles vous êtes habitué, comme faire écho à une variable $ qui n'existe pas.
Les exceptions sont uniquement à partir de PHP 5 et surviennent lorsqu'il s'agit d'objets.

Pour faire simple:

Les exceptions sont les erreurs que vous obtenez lorsque vous traitez des objets. L'instruction try / catch vous permet cependant de faire quelque chose à leur sujet, et est utilisée un peu comme l'instruction if / else. Essayez de le faire, si le problème n'a pas d'importance, faites-le.

Si vous n'attrapez pas d'exception, cela se transforme en erreur standard.

Les erreurs sont les erreurs fondamentales de php qui stoppent généralement votre script.

Try / catch est souvent utilisé pour établir des connexions de base de données comme PDO, ce qui convient si vous souhaitez rediriger le script ou faire autre chose si la connexion ne fonctionne pas. Mais si vous souhaitez simplement afficher le message d'erreur et arrêter le script, vous n'en avez pas besoin, l'exception non interceptée se transforme en erreur fatale. Vous pouvez également utiliser un paramètre de gestion des erreurs à l'échelle du site.

J'espère que cela pourra aider

Lan
la source
3
Les exceptions peuvent tout aussi bien être utilisées avec du code procédural en PHP.
Tiberiu-Ionuț Stan
2

Dans PHP 7.1 et versions ultérieures, un bloc catch peut spécifier plusieurs exceptions en utilisant le caractère pipe (|). Ceci est utile lorsque différentes exceptions de différentes hiérarchies de classes sont gérées de la même manière.

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}
Jehong Ahn
la source
1

Les exceptions sont lancées intentionnellement par le code en utilisant un jet, des erreurs ... pas tellement.

Les erreurs surviennent à la suite de quelque chose qui n'est généralement pas géré. (Erreurs d'E / S, erreurs TCP / IP, erreurs de référence nulles)

cgp
la source
1
Ce n'est pas nécessairement vrai. Dans de nombreux cas, les erreurs sont vérifiées et les codes de retour sont renvoyés intentionnellement, le cas échéant. En fait, c'est le cas pour tout langage non orienté objet. Les exceptions ne sont que des exceptions à la règle. Dans les deux cas, quelque chose ne va pas, est remarqué et doit être traité. Le téléchargement de fichiers PHP est un exemple de gestion intentionnelle des erreurs via les codes de retour - php.net/manual/en/features.file-upload.errors.php
evan
1

J'ai l'intention de vous donner une discussion des plus inhabituelles sur le contrôle des erreurs.

J'ai construit un très bon gestionnaire d'erreurs dans un langage il y a des années, et bien que certains des noms aient changé, les principes du traitement des erreurs sont les mêmes aujourd'hui. J'avais un système d'exploitation multi-tâches personnalisé et je devais être en mesure de récupérer des erreurs de données à tous les niveaux sans fuites de mémoire, croissance de la pile ou plantages. Ce qui suit est donc ma compréhension de la manière dont les erreurs et les exceptions doivent fonctionner et en quoi elles diffèrent. Je dirai simplement que je ne comprends pas comment les composants internes de try catch fonctionnent, donc je suppose dans une certaine mesure.

La première chose qui se passe sous les couvertures du traitement des erreurs est de passer d'un état de programme à un autre. Comment cela se fait-il? J'y reviendrai.

Historiquement, les erreurs sont plus anciennes et plus simples, et les exceptions sont plus récentes et un peu plus complexes et capables. Les erreurs fonctionnent bien jusqu'à ce que vous ayez besoin de les faire remonter, ce qui équivaut à confier un problème difficile à votre superviseur.

Les erreurs peuvent être des nombres, comme des numéros d'erreur, et parfois avec une ou plusieurs chaînes associées. Par exemple, si une erreur de lecture de fichier se produit, vous pourrez peut-être signaler ce que c'est et éventuellement échouer correctement. (Hay, c'est un pas en avant par rapport au crash comme autrefois.)

Ce qui n'est pas souvent dit à propos des exceptions, c'est que les exceptions sont des objets superposés sur une pile d'exceptions spéciale. C'est comme une pile de retour pour le flux du programme, mais elle contient un état de retour uniquement pour les essais et les captures d'erreur. (J'avais l'habitude de les appeler ePush et ePop, et? Abort était un lancer conditionnel qui ePop et récupérait à ce niveau, tandis que Abort était un dé complet ou une sortie.)

Au bas de la pile se trouvent les informations sur l'appelant initial, l'objet qui connaît l'état lorsque l'essai externe a été lancé, ce qui correspond souvent au démarrage de votre programme. En plus de cela, ou la couche suivante de la pile, avec up étant les enfants et down étant les parents, se trouve l'objet d'exception du prochain bloc try / catch interne.

Si vous faites un essai à l'intérieur d'un essai, vous empilez l'essai intérieur au-dessus de l'essai extérieur. Lorsqu'une erreur se produit dans l'essai interne et que soit le catch interne ne peut pas le gérer, soit l'erreur est renvoyée à l'essai externe, le contrôle est passé au bloc catch externe (objet) pour voir s'il peut gérer l'erreur, c'est-à-dire votre superviseur.

Donc, ce que cette pile d'erreurs fait vraiment est de pouvoir marquer et restaurer le flux du programme et l'état du système, en d'autres termes, elle permet à un programme de ne pas planter la pile de retour et de gâcher les choses pour les autres (données) lorsque les choses tournent mal. Ainsi, il enregistre également l'état de toutes les autres ressources telles que les pools d'allocation de mémoire et peut donc les nettoyer lorsque la capture est terminée. En général, cela peut être une chose très compliquée, et c'est pourquoi la gestion des exceptions est souvent lente. En général, un peu d'état doit entrer dans ces blocs d'exceptions.

Donc, une sorte de bloc try / catch définit un état auquel il est possible de revenir si tout le reste est gâché. C'est comme un parent. Quand nos vies sont gâchées, nous pouvons retomber sur les genoux de nos parents et ils le feront à nouveau.

J'espère que je ne vous ai pas déçu.

Vue elliptique
la source
1

Vous pouvez ajouter ce commentaire

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}
Yolo
la source
0

Une fois set_error_handler () défini, le gestionnaire d'erreur est similaire à celui d'Exception. Voir le code ci-dessous:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
N Zhang
la source