Menu

PHP try catch : gérer les exceptions, finally et Throwable

En PHP, le code qui peut échouer va dans un bloc try { }, et catch (Exception $e) { } s'exécute s'il lève une exception, avec la raison dans $e->getMessage(). Intercepter plusieurs types d'exceptions, finally, pourquoi catch (Exception) rate TypeError et DivisionByZeroError (interceptez Throwable), relancer, et un gestionnaire global.

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

En PHP, vous placez le code qui peut échouer dans try { ... } et traitez l'échec dans catch (Exception $e) { ... }. Si quoi que ce soit dans le bloc try lève une exception, PHP saute directement au catch correspondant, et $e->getMessage() vous dit ce qui n'a pas marché. Si rien n'est levé, le bloc catch est sauté.

Remplacez divide(10, 0) par divide(10, 5) et relancez : pas d'exception, donc not printed s'affiche et le bloc catch est sauté. Sans try, l'exception terminerait le script avec une erreur fatale « Uncaught ». Comment lever vos propres exceptions et écrire des classes d'exception est expliqué sur la page des exceptions.

La syntaxe de try catch

La forme complète a trois parties. Seul try est obligatoire, plus au moins un catch ou un finally :

try {
    // code that may throw
} catch (SomeException $e) {
    // runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
    // runs for either of these types
} finally {
    // always runs, thrown or not
}

Le type dans catch décide de ce qu'il intercepte : la classe elle-même et toutes les classes qui l'étendent. Depuis PHP 8.0, la variable est facultative quand vous n'en avez pas besoin : catch (JsonException) { ... }.

Intercepter Exception, Error ou Throwable

Depuis PHP 7, les échecs que PHP détecte lui-même (appel d'une méthode inexistante, mauvais type d'argument, division par zéro) lèvent une Error, pas une Exception, et PHP 8 a transformé beaucoup d'anciens warnings en TypeError et ValueError. Les deux implémentent l'interface Throwable, mais aucune n'étend l'autre, donc catch (Exception $e) n'intercepte pas une Error :

Throwable
├── Exception                 (problems a program expects and handles)
│   ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error                     (engine errors, usually a bug in the code)
    ├── TypeError
    ├── ValueError
    ├── ArithmeticError
    │   └── DivisionByZeroError
    └── UnhandledMatchError, ...

Exécutez ceci pour voir quel catch traite chaque problème :

Alors, laquelle intercepter ?

  • catch (SomeSpecificException $e) quand vous savez ce qui peut mal tourner et quoi faire. C'est le cas de la plupart du code.
  • catch (Exception $e) pour « tout ce qu'une bibliothèque ou mon code a levé volontairement ».
  • catch (Throwable $e) au niveau le plus extérieur (un gestionnaire de requêtes, un exécuteur de tâches) pour journaliser chaque échec, erreurs de PHP comprises, et afficher un message correct.

Intercepter plusieurs types d'exceptions

Écrivez plusieurs blocs catch pour traiter différents types de façons différentes. PHP les essaie de haut en bas et utilise la première correspondance, donc un type précis doit venir avant son parent, sinon le parent interceptera tout en premier. Quand plusieurs types reçoivent le même traitement, combinez-les dans un seul catch avec | :

Les trois classes étendent Exception, donc un seul catch (Exception $e) les intercepterait aussi, mais le code ne pourrait plus répondre 404 pour l'une et 400 pour les autres.

finally : du code qui s'exécute toujours

Un bloc finally s'exécute après try et catch quelle que soit leur façon de se terminer : normalement, avec une exception interceptée, avec une exception que personne n'a interceptée, ou avec un return. C'est l'endroit pour libérer ce que le bloc try a acquis : fermer un fichier, libérer un verrou, arrêter un chronomètre.

Pour good.txt, le fichier est fermé avant l'affichage de saved, car finally s'exécute avant que la fonction ne renvoie réellement sa valeur. Pour bad.txt, il n'y a pas de catch dans process() : finally ferme le fichier, puis l'exception remonte jusqu'au catch de l'appelant.

Évitez return dans finally : il remplace la valeur renvoyée par le bloc try et jette en silence une exception qui était en train de remonter :

function f(): string
{
    try {
        return 'from try';
    } finally {
        return 'from finally';
    }
}

function g(): string
{
    try {
        throw new RuntimeException('lost');
    } finally {
        return 'no exception reaches the caller';
    }
}

echo f(); // from finally
echo g(); // no exception reaches the caller

Relancer ou envelopper une exception

Parfois, un bloc catch ne peut traiter qu'une partie d'un problème : le journaliser, nettoyer, puis le laisser continuer. throw $e; relance la même exception. Plus souvent, vous voulez ajouter du contexte tout en gardant la cause d'origine, et c'est à cela que sert le troisième argument du constructeur, $previous :

L'appelant traite une seule ConfigException et n'a pas besoin de savoir que la configuration est en JSON, alors que getPrevious() garde le détail de bas niveau pour le journal.

Exceptions non interceptées et gestionnaire global

Une exception qu'aucun catch ne traite termine le script avec une erreur fatale qui indique la classe, le message, le fichier et la ligne, ainsi qu'une trace de la pile d'appels :

<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
    if ($cents <= 0) {
        throw new DomainException("Amount must be positive, got $cents");
    }
}

charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";

PHP affiche first charge ok puis :

PHP Fatal error:  Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
  thrown in /home/index.php on line 6

Sur un site en ligne, ce message ne doit jamais atteindre les visiteurs. set_exception_handler() enregistre une fonction qui reçoit toute exception que personne n'a interceptée, un seul endroit pour la journaliser et afficher une page polie :

Le gestionnaire s'exécute, puis le script s'arrête ; il n'y a aucun moyen de reprendre après le throw. L'affichage même du texte d'erreur de PHP sur la page est contrôlé par display_errors, présenté dans l'affichage des erreurs.

Les warnings ne sont pas des exceptions

try/catch ne voit que ce qui est levé. Beaucoup d'anciennes fonctions PHP ne lèvent rien : elles renvoient false ou null et, au plus, affichent un warning. json_decode() est un cas typique, et l'entourer de try ne change rien tant que vous ne lui demandez pas de lever une exception :

Pour les fonctions sans option de ce genre, vérifiez la valeur de retour (if ($handle === false)), ou vérifiez d'abord la condition préalable (file_exists(), isset()). Un set_error_handler() qui lève une ErrorException transforme chaque warning en vraie exception si vous voulez une seule règle pour tout.

Recette : valider une saisie de formulaire avec des exceptions

Un modèle pratique : les fonctions de validation lèvent des exceptions, la page les intercepte et affiche le message à côté du formulaire. Exécutez-le, puis tapez un âge dans le formulaire et cliquez sur Send : le même script s'exécute à nouveau avec $_POST rempli.

Essayez un champ vide, twelve, 9 et 30. Chaque règle qui échoue lève une exception avec son propre message, et un seul catch les transforme toutes en la même ligne rouge. La page sur les formulaires va plus loin avec plusieurs champs et des erreurs à côté de chacun.

Questions fréquentes

Comment fonctionne try catch en PHP ?

PHP exécute le code dans try { }. Si quelque chose y lève une exception, PHP saute le reste du bloc try et exécute le premier bloc catch dont le type correspond, avec l'objet levé dans la variable : catch (Exception $e) { echo $e->getMessage(); }. Si rien n'est levé, les blocs catch sont sautés.

Comment intercepter toutes les exceptions et erreurs en PHP ?

Interceptez Throwable : catch (Throwable $e). catch (Exception $e) n'intercepte que les exceptions, pas les erreurs du moteur comme TypeError, ValueError ou DivisionByZeroError, qui étendent Error. Exception et Error implémentent toutes deux Throwable.

Comment intercepter plusieurs exceptions dans un même bloc catch en PHP ?

Séparez les types par une barre verticale : catch (InvalidArgumentException | RangeException $e). Vous pouvez aussi écrire plusieurs blocs catch à la suite ; PHP utilise le premier qui correspond, placez donc les types les plus précis en premier.

finally s'exécute-t-il s'il y a un return dans try ?

Oui. finally s'exécute après try et catch, que le code ait renvoyé une valeur, levé une exception ou terminé normalement, y compris quand try contient return. Si finally renvoie lui-même une valeur, elle remplace celle de try, évitez donc return dans finally.

try catch intercepte-t-il les warnings en PHP ?

Non. Les warnings et les notices (comme lire une clé de tableau manquante) ne sont pas des exceptions, donc catch ne les voit jamais. Vérifiez la condition avant l'appel, utilisez le mode exception de la fonction quand il existe (json_decode(..., flags: JSON_THROW_ON_ERROR)), ou convertissez les warnings avec set_error_handler() et ErrorException.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER