Exceptions PHP

Dans cet article, nous allons en apprendre davantage sur les exceptions PHP. Ces concepts sont utilisés dans de nombreuses applications et infrastructures de grande taille, évolutives et orientées objet. Tirez parti de cette fonctionnalité linguistique pour améliorer vos compétences en tant que développeur d'applications Web..


1 Un exemple d'abord

Avant de commencer avec toutes les explications, je voudrais montrer un exemple en premier.

Supposons que vous souhaitiez calculer l'aire d'un cercle en fonction du rayon donné. Cette fonction fera cela:

 fonction circle_area ($ radius) return pi () * $ radius * $ radius; 

C'est très simple, mais il ne vérifie pas si le rayon est un nombre valide. Maintenant, nous allons le faire et lancer une exception si le rayon est un nombre négatif:

 function circle_area ($ radius) // le rayon ne peut pas être négatif si ($ radius < 0)  throw new Exception('Invalid Radius: ' . $radius);  else  return pi() * $radius * $radius;  

Voyons ce qui se passe lorsque nous l'appelons avec un nombre négatif:

 $ rayon = -2; echo "Rayon du cercle: $ rayon => Zone du cercle:". circle_area ($ radius). "\ n"; echo "Une autre ligne";

Le script se bloque avec le message suivant:

 
Erreur fatale: Exception non capturée 'Exception' avec le message 'Rayon non valide: -2' dans C: \ wamp \ www \ test \ test.php: 19 Trace de pile: # 0 C: \ wamp \ www \ test \ test.php (7) : circle_area (-2) # 1 principal jeté dans C: \ wamp \ www \ test \ test.php en ligne 19

Comme il s’agissait d’une erreur fatale, aucune autre exécution de code n’est survenue par la suite. Cependant, vous ne voudrez peut-être pas toujours que vos scripts s'arrêtent chaque fois qu'une exception se produit. Heureusement, vous pouvez les "attraper" et les manipuler.

Cette fois, faisons un tableau de valeurs de rayon:

 $ radius_array = array (2, -2,5, -3); foreach ($ radius_array as $ radius) try echo "Rayon du cercle: $ radius => Zone du cercle:". circle_area ($ radius). "\ n";  catch (Exception $ e) echo 'Exception surprise:', $ e-> getMessage (), "\ n"; 

Maintenant nous obtenons cette sortie:

 Rayon du cercle: 2 => Surface du cercle: 12.566370614359 Exception capturée: Rayon non valide: -2 Rayon du cercle: 5 => Surface du cercle: 78.539816339745 Exception capturée: Rayon non valide: -3

Il n'y a plus d'erreurs et le script continue de s'exécuter. C'est comme ça que tu attrapes des exceptions.


Screencast complet



2 Qu'est-ce qu'une exception?

Des exceptions existent depuis longtemps dans d'autres langages de programmation orientés objet. Il a d'abord été adopté en PHP avec la version 5.

Par définition, une exception est "levée" lorsqu'un événement exceptionnel se produit. Cela pourrait être aussi simple qu'une division par zéro ou tout autre type de situation invalide.

 jeter une nouvelle exception ('Un message d'erreur.');

Cela peut sembler similaire à d’autres erreurs de base que vous avez déjà vues à plusieurs reprises. Mais les exceptions ont un type de mécanisme différent.

Les exceptions sont en réalité des objets et vous avez la possibilité de les "attraper" et d'exécuter certains codes. Ceci est fait en utilisant des blocs 'try-catch':

 try // du code va ici // qui peut lever une exception catch (Exception $ e) // le code ici n'est exécuté que // si une exception s'est produite dans le bloc try ci-dessus

Nous pouvons inclure n'importe quel code dans un bloc "try". Le bloc 'catch' suivant est utilisé pour capturer toute exception qui aurait pu être levée depuis le bloc try. Le bloc catch n'est jamais exécuté s'il n'y avait pas d'exception. De plus, lorsqu'une exception se produit, le script passe immédiatement au bloc catch, sans exécuter de code supplémentaire..

Plus loin dans l'article, nous aurons plus d'exemples qui devraient démontrer la puissance et la flexibilité d'utiliser des exceptions au lieu de simples messages d'erreur..


3 exceptions Bubble Up

Lorsqu'une exception est émise par une fonction ou une méthode de classe, elle est adressée à quiconque a appelé cette fonction ou cette méthode. Et il continue de le faire jusqu'à ce qu'il atteigne le sommet de la pile OU qu'il soit pris. S'il atteint le sommet de la pile et qu'il n'est jamais appelé, vous obtiendrez une erreur fatale..

Par exemple, nous avons ici une fonction qui lève une exception. Nous appelons cette fonction à partir d'une seconde fonction. Et finalement, nous appelons la deuxième fonction du code principal, pour démontrer cet effet bouillonnant:

 function bar () renvoie une nouvelle exception ('Message de bar ().');  fonction foo () bar ();  essayer foo ();  catch (Exception $ e) echo 'Exception capturée:', $ e-> getMessage (), "\ n"; 

Ainsi, lorsque nous appelons foo (), nous essayons d’attraper les exceptions possibles. Bien que foo () n'en jette pas un, mais que bar () le fasse, il bouillonne toujours et reste coincé au sommet. Nous obtenons donc une sortie disant: "Exception capturée: message de bar ()."


4 Exceptions de traçage

Comme les exceptions font des bulles, elles peuvent venir de n’importe où. Pour faciliter notre travail, la classe Exception dispose de méthodes nous permettant de localiser la source de chaque exception..

Voyons un exemple impliquant plusieurs fichiers et plusieurs classes.

Tout d'abord, nous avons une classe User et nous l'enregistrons sous le nom user.php:

 utilisateur de classe nom public $; public $ email; fonction publique save () $ v = new Validator (); $ v-> validate_email ($ this-> email); //… save echo "Utilisateur enregistré."; retourne vrai; 

Il utilise une autre classe nommée Validator, que nous avons insérée dans validator.php:

 class Validator fonction publique validate_email ($ email) if (! filtre_var ($ email, FILTER_VALIDATE_EMAIL)) lance une nouvelle exception ('Email est invalide'); 

À partir de notre code principal, nous allons créer un nouvel objet Utilisateur, définir le nom et les valeurs de courrier électronique. Une fois que nous avons appelé la méthode save (), elle utilisera la classe Validator pour vérifier le format de courrier électronique, ce qui pourrait renvoyer une exception:

 include ('user.php'); include ('validator.php'); $ u = nouvel utilisateur (); $ u-> name = 'foo'; $ u-> email = '$!% # $% # *'; $ u-> save ();

Cependant, nous aimerions intercepter l'exception, il n'y a donc pas de message d'erreur fatale. Et cette fois, nous allons examiner les informations détaillées sur cette exception:

 include ('user.php'); include ('validator.php'); essayez $ u = nouvel utilisateur (); $ u-> name = 'foo'; $ u-> email = '$!% # $% # *'; $ u-> save ();  catch (Exception $ e) echo "Message:". $ e-> getMessage (). "\ n \ n"; echo "Fichier:". $ e-> getFile (). "\ n \ n"; echo "Ligne:". $ e-> getLine (). "\ n \ n"; echo "Trace: \ n". $ e-> getTraceAsString (). "\ n \ n"; 

Le code ci-dessus produit cette sortie:

 Message: L'email est invalide Fichier: C: \ wamp \ www \ test \ validator.php Ligne: 7 Trace: # 0 C: \ wamp \ www \ test \ user.php (11): Validator-> validate_email ('$! % # $% # * ') # 1 C: \ wamp \ www \ test \ test.php (12): Utilisateur-> save () # 2 principal

Donc, sans regarder une seule ligne de code, nous pouvons dire d'où vient l'exception. Nous pouvons voir le nom du fichier, le numéro de ligne, le message d'exception et plus encore. Les données de trace montrent même les lignes de code exactes qui ont été exécutées.

La structure de la classe Exception par défaut est montrée dans le manuel PHP, où vous pouvez voir toutes les méthodes et les données fournies:



5 exceptions prolongées

Puisqu'il s'agit d'un concept orienté objet et que Exception est une classe, nous pouvons en fait l'étendre pour créer nos propres exceptions personnalisées..

Par exemple, vous pouvez ne pas vouloir afficher tous les détails d'une exception à l'utilisateur. Au lieu de cela, vous pouvez afficher un message convivial et consigner le message d'erreur en interne:

 // à utiliser pour les problèmes de base de données, classe DatabaseException extend Exception // vous pouvez ajouter des méthodes personnalisées. public function log () // enregistre cette erreur quelque part //… // à utiliser pour les problèmes de système de fichiers, classe FileException Exception //…

Nous venons de créer deux nouveaux types d'exceptions. Et ils peuvent avoir des méthodes personnalisées.

Lorsque nous interceptons l'exception, nous pouvons afficher un message fixe et appeler les méthodes personnalisées en interne:

 function foo () //… // quelque chose de mal s'est passé avec la base de données, jetez une nouvelle base de données DatabaseException ();  try // met tout votre code ici //… foo ();  catch (FileException $ e) die ("Il semble que nous ayons des problèmes de système de fichiers. Nous sommes désolés pour le désagrément.");  catch (DatabaseException $ e) // en appelant notre nouvelle méthode $ e-> log (); // exit avec un message die ("Il semble que nous ayons des problèmes de base de données. Nous sommes désolés pour le désagrément.");  catch (Exception $ e) echo 'Exception capturée:'. $ e-> getMessage (). "\ n"; 

C'est la première fois que nous examinons un exemple avec plusieurs blocs catch pour un seul bloc try. C'est ainsi que vous pouvez intercepter différents types d'exceptions, afin de pouvoir les gérer différemment.

Dans ce cas, nous attraperons une exception DatabaseException et seul ce bloc catch sera exécuté. Dans ce blog, nous pouvons appeler nos nouvelles méthodes personnalisées et afficher un message simple à l'utilisateur..

Veuillez noter que le bloc catch avec la classe Exception par défaut doit être la dernière, car nos nouvelles classes enfants sont également toujours considérées comme cette classe. Par exemple, "DatabaseException" est également considéré comme "Exception", de sorte qu'il peut être intercepté si l'ordre est inversé..


6 Gestion des exceptions non capturées

Il se peut que vous ne souhaitiez pas toujours rechercher des exceptions dans tout votre code, en regroupant tout dans des blocs try-catch. Cependant, les exceptions non capturées affichent un message d'erreur détaillé à l'utilisateur, ce qui n'est également pas idéal dans un environnement de production..

Il existe en fait un moyen de centraliser le traitement de toutes les exceptions non capturées afin que vous puissiez contrôler la sortie à partir d'un seul emplacement..

Pour cela, nous allons utiliser la fonction set_exception_handler ():

 set_exception_handler ('exception_handler'); function exception_handler ($ e) // message public echo "Une erreur s'est produite. \ n"; // echo de message semi-caché "

Comme vous pouvez le constater, le script a été abandonné après la première exception et n’a pas exécuté la seconde. C'est le comportement attendu des exceptions non capturées.

Si vous voulez que votre script continue à s'exécuter après une exception, vous devez utiliser un bloc try-catch à la place..


7 Construction d’une classe d’exception MySQL

Nous allons terminer ce tutoriel en construisant une classe MySQL Exception personnalisée, dotée de fonctionnalités utiles, et voir comment l'utiliser..

 class MysqlException extended Exception // chemin du fichier journal private $ log_file = 'mysql_errors.txt'; fonction publique __construct () $ code = mysql_errno (); $ message = mysql_error (); // ouvre le fichier journal pour ajouter if ($ fp = fopen ($ this-> log_file, 'a')) // construit le message de log $ log_msg = date ("[Y-m-d H: i: s]"). "Code: $ code -". "Message: $ message \ n"; fwrite ($ fp, $ log_msg); fclose ($ fp);  // appelle le constructeur parent parent :: __ construct ($ message, $ code); 

Vous remarquerez peut-être que nous avons mis à peu près tout le code dans le constructeur. Chaque fois qu'une exception est levée, c'est comme si vous créiez un nouvel objet. C'est pourquoi le constructeur est toujours appelé en premier. À la fin du constructeur, nous nous assurons également d'appeler le constructeur parent..

Cette exception sera levée chaque fois que nous rencontrons une erreur MySQL. Il recherchera ensuite le numéro d'erreur et le message directement de mysql, puis stockera ces informations dans un fichier journal, ainsi que l'horodatage. Dans notre code, nous pouvons intercepter cette exception, afficher un simple message à l'utilisateur et laisser la classe d'exception gérer la journalisation pour nous..

Par exemple, essayons de nous connecter à MySQL sans fournir d’informations d’utilisateur / mot de passe:

 try // tente de se connecter if (! @mysql_connect ()) new new MysqlException;  catch (MysqlException $ e) die ("Il semble que nous ayons des problèmes de base de données. Nous sommes désolés pour le désagrément."); 

Nous devons préfixer l'opérateur de suppression d'erreur (@) avant l'appel à mysql_connect () afin qu'il ne présente pas l'erreur à l'utilisateur. Si la fonction échoue, nous lançons une exception, puis nous l'attrapons. Seul notre message convivial sera montré au navigateur.

La classe MysqlException s’occupe automatiquement de la consignation des erreurs. Lorsque vous ouvrez le fichier journal, vous trouverez cette ligne:

 [2010-05-05 21:41:23] Code: 1045 - Message: Accès refusé pour l'utilisateur 'SYSTEM' @ 'localhost' (avec mot de passe: NO)

Ajoutons plus de code à notre exemple et fournissons également des informations de connexion correctes:

 try // connection devrait fonctionner correctement si (! @mysql_connect ('localhost', 'root', ")) lance une nouvelle MysqlException; // sélectionne une base de données (qui peut ne pas exister) if (! mysql_select_db ('mon_db' )) lance une nouvelle exception MysqlEx; // tente une requête (qui peut comporter une erreur de syntaxe) si (! $ result = mysql_query ("INSERT INTO foo SET bar = '42")) lance une nouvelle erreur MysqlException; catch ( MysqlException $ e) die ("Il semble que nous ayons des problèmes de base de données. Nous sommes désolés pour ce désagrément.");

Si la connexion à la base de données aboutit, mais que la base de données nommée 'my_db' est manquante, vous la trouverez dans les journaux:

 [2010-05-05 21:55:44] Code: 1049 - Message: Base de données inconnue 'my_db'

Si la base de données existe, mais que la requête échoue, à cause d'une erreur de syntaxe par exemple, ceci peut apparaître dans le journal:

 [2010-05-05 21:58:26] Code: 1064 - Message: Votre syntaxe SQL est erronée. Consultez le manuel correspondant à la version de votre serveur MySQL pour connaître la syntaxe à utiliser près de "42" à la ligne 1.

Bonus Construire une classe DB avec PHP Magic

Nous pouvons rendre l'exemple de code ci-dessus encore plus propre en écrivant notre propre classe de base de données, qui gère le lancement des exceptions. Cette fois, je vais utiliser certaines fonctionnalités "magiques" de PHP pour construire cette classe..

À la fin, nous voulons que notre code principal ressemble à ceci:

 try Database :: connect ('localhost', 'root', "); Database :: select_db ('test'); $ resultat = Database :: query (" INSERT INTO foo SET bar = '42 "); catch (MysqlException $ e) die ("Il semble que nous ayons des problèmes de base de données. Nous sommes désolés pour ce désagrément.");

C'est beau et propre. Nous ne vérifions pas les erreurs dans chaque appel de base de données. Cette nouvelle classe de base de données a la responsabilité de générer des exceptions lorsque des erreurs se produisent. Et comme ces exceptions font leur apparition, elles sont capturées par notre bloc de capture à la fin..

Et voici la classe magique Database:

 class Database // tout appel de fonction statique appelle cette fonction statique publique __callStatic ($ name, $ args) // fonction appelée $ function = 'mysql_'. $ name; // la fonction existe-t-elle? if (! function_exists ($ function)) // lève une exception régulière, lève une nouvelle exception ("Fonction mysql invalide: $ function.");  // appelle la fonction mysql $ ret = @call_user_func_array ($ function, $ args); // ils renvoient FALSE en cas d'erreur si ($ ret === FALSE) // lève une exception de base de données et lance une nouvelle erreur MysqlException;  // retourne la valeur renvoyée return $ ret; 

Elle n'a qu'une méthode et est invoquée chaque fois que nous appelons une méthode statique sur cette classe. Vous pouvez en savoir plus sur ce concept ici: Surcharge PHP.

Ainsi, lorsque nous appelons Database :: connect (), ce code appelle automatiquement mysql_connect (), transmet les arguments, vérifie les erreurs, lève des exceptions si nécessaire et renvoie la valeur renvoyée à partir de l'appel de la fonction. Il agit essentiellement comme un homme du milieu et gère le sale boulot.


Conclusion

J'espère que vous avez apprécié ce tutoriel et en avez tiré des leçons. Maintenant, vous devriez avoir une meilleure compréhension de ce sujet. Essayez de voir si vous pouvez utiliser les exceptions PHP dans votre prochain projet. À la prochaine!