Menu

Exceptions PHP : throw et classes d'exception personnalisées

En PHP, on lève une exception avec throw new Exception('message');, et l'appelant la lit avec $e->getMessage() dans un bloc catch. Les classes d'exception natives, écrire des exceptions personnalisées, getCode(), getLine() et getPrevious(), throw comme expression, et les erreurs que PHP lève lui-même comme ValueError et TypeError.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

En PHP, on lève une exception avec throw new Exception('message');. L'exécution s'arrête à cette ligne et saute au bloc catch le plus proche qui accepte le type de l'exception, où $e->getMessage() renvoie le message. Utilisez une classe précise, comme InvalidArgumentException, pour que les appelants puissent distinguer les problèmes.

Une exception est un objet. throw la fait remonter la pile d'appels jusqu'au premier appelant qui a un catch correspondant, et c'est cet appelant qui décide quoi faire ; la fonction qui a trouvé le problème n'a pas à le savoir. Le fonctionnement de l'interception, de finally et de l'interception de plusieurs types est expliqué sur la page try catch.

Ce que contient un objet exception

Chaque exception porte un message, un code entier, le fichier et la ligne où elle a été créée, et une trace de la pile d'appels. Le constructeur prend ($message, $code, $previous), tous facultatifs :

getLine() est la ligne du throw new, pas celle du catch. La trace liste les appels qui y ont mené (#0 /home/index.php(8): findOrder()), et c'est en général la première chose à lire quand une erreur apparaît dans un journal. Convertir l'exception en chaîne (echo $e; ou (string) $e) affiche tout cela au format standard de PHP.

Les classes d'exception natives

PHP fournit une famille de classes d'exception dans sa Standard PHP Library (SPL). Lever celle qui nomme le problème rend votre code plus lisible et permet aux appelants d'intercepter de façon ciblée. Toutes celles-ci étendent Exception :

ClasseÀ lever quand
InvalidArgumentExceptionun argument a une mauvaise forme : un nom vide, une option inconnue
DomainExceptionune valeur est hors de l'ensemble qui a du sens : un prix négatif
OutOfRangeExceptionle code a demandé un index qui ne peut jamais exister (un bug de l'appelant)
OutOfBoundsExceptionune clé ou un index manque dans des données connues seulement à l'exécution
RangeExceptionun résultat calculé sort de la plage valide pendant l'exécution
LengthExceptionquelque chose est trop long ou trop court
RuntimeExceptionun problème détectable seulement à l'exécution : disque plein, délai dépassé
UnexpectedValueExceptionune fonction a renvoyé ou reçu une valeur d'un type inattendu
LogicExceptionle code lui-même est faux, un bug plutôt qu'une mauvaise saisie
JsonExceptionlevée par json_encode()/json_decode() avec JSON_THROW_ON_ERROR

InvalidArgumentException, DomainException, LengthException et OutOfRangeException étendent LogicException ; OutOfBoundsException, RangeException et UnexpectedValueException étendent RuntimeException. Vous pouvez vérifier vous-même les parents de n'importe quelle classe :

TypeError a Error pour parent, pas Exception : elle appartient à la seconde famille, ci-dessous.

Les erreurs que PHP lève lui-même

Depuis PHP 7, et bien plus encore depuis PHP 8, les fonctions et opérateurs de PHP lèvent des sous-classes d'Error pour des problèmes qui étaient autrefois des warnings. Vous les interceptez de la même façon, mais catch (Exception $e) ne les attrapera pas :

DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError et Error simple étendent toutes Error. Chacune signale plus souvent un bug dans le code appelant que des données invalides, c'est pourquoi elles vivent hors de l'arbre Exception. C'est declare(strict_types=1) qui fait de str_repeat(5, 2) une TypeError ; sans lui, PHP convertirait 5 en "5".

Écrire une classe d'exception personnalisée

Une sous-classe d'une ligne suffit souvent : le nom de la classe porte le sens, et les appelants peuvent intercepter exactement ce type. Quand celui qui intercepte a besoin de données (quelle commande, quel montant), ajoutez des propriétés et transmettez le message avec parent::__construct() :

Choisissez le parent avec soin : étendre RuntimeException (plutôt qu'Exception simple) signifie que le code qui intercepte RuntimeException traite aussi la vôtre. Une petite hiérarchie, une exception de base par bibliothèque ou par module avec des sous-classes précises en dessous, permet aux appelants de choisir entre « tout intercepter des paiements » et « intercepter seulement ce cas ».

Chaîner les exceptions avec previous

Quand vous interceptez une exception de bas niveau et en levez une de plus haut niveau, passez l'originale en troisième argument. Rien n'est perdu : getPrevious() remonte jusqu'à la cause première.

L'utilisateur voit « customers.csv could not be imported » ; le journal, en parcourant la chaîne, reçoit aussi « row 2 has 2 columns ». Si une exception chaînée n'est jamais interceptée, l'erreur fatale de PHP affiche toute la chaîne : la cause d'origine d'abord, puis chaque enveloppe sous une ligne Next.

throw comme expression

Depuis PHP 8.0, throw est une expression, il peut donc se placer partout où une valeur est attendue : après ??, dans un ternaire, dans une fonction fléchée. « Obtenir ceci ou échouer » tient alors en une ligne :

Quand ne pas lever d'exception

Les exceptions sont pour les situations que la fonction courante ne peut pas traiter et que le déroulement normal ne prévoit pas. Une recherche qui ne trouve rien, un champ facultatif vide ou un utilisateur non connecté sont des résultats ordinaires : renvoyez null, false ou un tableau vide et laissez l'appelant vérifier, et gardez les exceptions pour les cas où continuer serait une erreur.

Deux autres habitudes gardent le code des exceptions lisible : n'interceptez jamais une exception juste pour l'ignorer (un catch (Exception $e) {} vide cache le prochain vrai bug), et levez des classes précises plutôt qu'un simple Exception, pour qu'un appelant ne soit jamais obligé de tout intercepter pour traiter un seul cas.

Questions fréquentes

Comment lever une exception en PHP ?

Créez un objet exception et levez-le : throw new InvalidArgumentException('Quantity must be positive');. L'exécution s'arrête à cette ligne et saute au bloc catch correspondant le plus proche ; s'il n'y en a pas, le script se termine par une erreur fatale « Uncaught ».

Comment créer une exception personnalisée en PHP ?

Étendez Exception ou l'une de ses sous-classes : class PaymentFailedException extends RuntimeException {}. Cette seule ligne suffit pour la lever et l'intercepter par son propre type. Ajoutez des propriétés et un constructeur quand celui qui l'intercepte a besoin de données supplémentaires, et appelez parent::__construct($message, $code, $previous).

Quelle est la différence entre getMessage et getCode ?

getMessage() renvoie le texte passé en premier argument du constructeur, destiné aux humains et aux journaux. getCode() renvoie l'entier passé en second argument (0 par défaut), utile pour des vérifications automatiques comme associer des erreurs à des codes de statut HTTP.

Quelle exception lever en PHP ?

Utilisez une classe SPL native qui correspond au problème : InvalidArgumentException pour un mauvais argument, DomainException pour une valeur hors de ce qui est permis, RuntimeException pour des échecs visibles seulement à l'exécution (disque plein, délai dépassé), LogicException pour des erreurs de programmation. Pour les erreurs que vos appelants doivent distinguer, créez votre propre sous-classe.

Qu'est-ce que le chaînage d'exceptions en PHP ?

Passer l'exception d'origine en troisième argument du constructeur quand vous en levez une nouvelle : throw new ImportException('Import failed', 0, $e);. La nouvelle exception apporte le contexte, et $e->getPrevious() sur elle renvoie la cause d'origine, si bien qu'aucun détail n'est perdu.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER