Seul celui qui ne fait rien ne fait pas d'erreurs, et nous en sommes un exemple - nous nous asseyons et travaillons sans relâche, lisez le Habr :)
Dans cet article, je vais mener mon histoire sur les erreurs en PHP et comment les limiter.
Erreurs
Variétés de la famille des erreurs
Avant d'apprivoiser les erreurs, je recommanderais d'étudier
chaque espèce et de prêter attention séparément aux représentants les plus éminents.
Pour éviter qu'une seule erreur ne passe inaperçue, vous devez activer le suivi de toutes les erreurs à l'aide de la fonction
error_reporting () et à l'aide de la directive
display_errors pour activer leur affichage:
<?php error_reporting(E_ALL); ini_set('display_errors', 1);
Erreurs fatales
Les types d'erreurs les plus redoutables sont fatals, ils peuvent survenir à la fois pendant la compilation et pendant le travail de l'analyseur ou du script PHP, alors que le script est interrompu.
E_PARSECette erreur apparaît lorsque vous faites une erreur de syntaxe brute et que l'interpréteur PHP ne comprend pas ce que vous en voulez, par exemple, si vous n'avez pas fermé le bouclé ou la parenthèse:
<?php {
Ou ils ont écrit dans une langue incompréhensible:
<?php
Des parenthèses supplémentaires se produisent également, et pas si importantes rondes ou bouclées:
<?php }
Je note un point important - le code du fichier dans lequel vous avez fait l'erreur d'analyse ne sera pas exécuté, donc, si vous essayez d'activer l'affichage des erreurs dans le même fichier où l'erreur d'analyse s'est produite, cela ne fonctionnera pas:
<?php
E_ERREURCette erreur apparaît lorsque PHP a compris ce que vous vouliez, mais cela n'a pas fonctionné pour un certain nombre de raisons. Cette erreur interrompt également l'exécution du script et le code fonctionnera avant que l'erreur n'apparaisse:
Le plug-in est introuvable:
require_once 'not-exists.php';
Une exception a été levée (quel genre de bête, je vous le dirai un peu plus tard), mais n'a pas été traitée:
throw new Exception();
Lorsque vous essayez d'appeler une méthode de classe inexistante:
$stdClass = new stdClass(); $stdClass->notExists();
Manque de mémoire libre (plus que ce qui est prescrit dans la directive
memory_limit ) ou autre chose similaire:
$arr = array(); while (true) { $arr[] = str_pad(' ', 1024); }
Il est très courant lors de la lecture ou du téléchargement de fichiers volumineux, alors soyez prudent avec le problème de la consommation de mémoire
Appel de fonction récursive. Dans cet exemple, il s'est terminé à la 256e itération, car il est ainsi écrit
dans les paramètres de xdebug (oui, cette erreur ne peut apparaître sous cette forme que lorsque l'extension xdebug est activée):
function deep() { deep(); } deep();
Pas fatal
Cette vue n'interrompt pas l'exécution du script, mais c'est généralement le testeur qui les trouve. Ce sont ces erreurs qui causent le plus de problèmes aux développeurs débutants.
E_WARNINGCela se produit souvent lorsque vous connectez un fichier à l'aide de
include
, mais il n'apparaît pas sur le serveur ou vous avez fait une erreur en indiquant le chemin d'accès au fichier:
include_once 'not-exists.php';
Cela se produit si vous utilisez le mauvais type d'arguments lors de l'appel de fonctions:
join('string', 'string');
Il y en a beaucoup, et tout énumérer n'a pas de sens ...
E_NOTICECe sont les erreurs les plus courantes, de plus, il y a des ventilateurs pour désactiver la sortie d'erreur et les riveter toute la journée. Il y a un certain nombre d'erreurs triviales.
Lors de l'accès à une variable non définie:
echo $a;
Lors de l'accès à un élément de tableau inexistant:
$b = []; $b['a'];
Lors de l'accès à une constante inexistante:
echo UNKNOWN_CONSTANT;
Lorsque les types de données ne sont pas convertis:
echo array();
Pour éviter de telles erreurs - soyez prudent, et si l'EDI vous dit quelque chose, ne l'ignorez pas:
E_STRICTCe sont des erreurs qui vous apprendront à écrire du code correctement afin que vous n'ayez pas honte, d'autant plus que l'EDI vous montre immédiatement ces erreurs. Par exemple, si vous avez appelé une méthode non statique comme statique, le code fonctionnera, mais il est en quelque sorte incorrect et des erreurs graves peuvent se produire si la méthode de classe est modifiée à l'avenir, et un appel à
$this
apparaît:
class Strict { public function test() { echo "Test"; } } Strict::test();
Ce type d'erreur est pertinent pour PHP version 5.6, et presque tous ont été supprimés
7 matchs. En savoir plus dans le RFC correspondant . Si quelqu'un sait où ces erreurs sont restées, écrivez dans les commentaires
E_DEPRECATEDPHP jurera donc si vous utilisez des fonctions obsolètes (c'est-à-dire celles qui sont marquées comme obsolètes, et elles ne seront pas dans la prochaine version majeure):
Dans mon éditeur, des fonctions similaires seront barrées:

Personnalisé
Ce type, que le développeur de code lui-même «élève», je ne les ai pas vus depuis longtemps, et je ne vous recommande pas d'en abuser:
E_USER_ERROR
- erreur critiqueE_USER_WARNING
- pas une erreur critiqueE_USER_NOTICE
- messages qui ne sont pas des erreurs
Séparément, il convient de noter
E_USER_DEPRECATED
- ce type est encore utilisé très souvent pour rappeler au programmeur que la méthode ou la fonction est obsolète et qu'il est temps de réécrire le code sans l'utiliser. La fonction
trigger_error () est utilisée pour créer ceci et des erreurs similaires:
function generateToken() { trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
Maintenant que vous vous êtes familiarisé avec la plupart des types et types d'erreurs, il est temps de donner une brève explication sur le fonctionnement de la directive display_errors
:
- si
display_errors = on
, en cas d'erreur le navigateur recevra du html avec le texte et le code d'erreur 200 - si
display_errors = off
, alors pour les erreurs fatales, le code de réponse sera 500 et le résultat ne sera pas retourné à l'utilisateur, pour d'autres erreurs - le code ne fonctionnera pas correctement, mais ne le dira à personne.
Apprivoiser
Il existe 3 fonctions pour travailler avec les erreurs en PHP:
- set_error_handler () - définit un gestionnaire pour les erreurs qui n'interrompent pas le script (c'est-à-dire pour les erreurs non fatales)
- error_get_last () - obtient des informations sur la dernière erreur
- register_shutdown_function () - enregistre un gestionnaire qui sera lancé à la fin du script. Cette fonction ne s'applique pas directement aux gestionnaires d'erreurs, mais elle est souvent utilisée à cette fin.
Maintenant, quelques détails sur la gestion des erreurs à l'aide de
set_error_handler()
, comme fonction, cette fonction prend le nom de la fonction qui sera affectée à la mission de gestion des erreurs et les
types d'erreurs qui seront surveillés. Un gestionnaire d'erreurs peut également être une méthode de classe ou une fonction anonyme, l'essentiel est qu'il accepte la liste d'arguments suivante:
$errno
- le premier argument contient le type d'erreur sous forme d'entier$errstr
- le deuxième argument contient un message d'erreur$errfile
- un troisième argument facultatif contient le nom du fichier dans lequel l'erreur s'est produite$errline
- un quatrième argument facultatif contient le numéro de ligne sur lequel l'erreur s'est produite$errcontext
- le cinquième argument optionnel contient un tableau de toutes les variables existant dans la portée où l'erreur s'est produite
Si le gestionnaire a renvoyé
true
, l'erreur sera considérée comme traitée et le script continuera à s'exécuter, sinon, un gestionnaire standard sera appelé qui enregistre l'erreur et, selon son type, continuera à exécuter le script ou à le terminer. Voici un exemple de gestionnaire:
<?php
Vous ne pourrez pas affecter plus d'une fonction pour gérer les erreurs, bien que j'aimerais vraiment enregistrer mon propre gestionnaire pour chaque type d'erreur, mais non - écrivez un gestionnaire et décrivez toute la logique d'affichage pour chaque type directement dedans
Il y a un problème important avec le gestionnaire qui est écrit ci-dessus - il ne détecte pas les erreurs fatales, et avec de telles erreurs, au lieu du site, les utilisateurs ne verront qu'une page vierge ou, pire encore, un message d'erreur. Afin d'éviter un tel scénario, vous devez utiliser la fonction
register_shutdown_function () et l'utiliser pour enregistrer une fonction qui sera toujours exécutée à la fin du script:
function shutdown() { echo ' '; } register_shutdown_function('shutdown');
Cette fonction fonctionnera toujours!
Mais revenons aux erreurs, pour suivre l'apparition de l'erreur dans le code d'erreur, nous utilisons la fonction
error_get_last () , avec son aide, vous pouvez obtenir des informations sur la dernière erreur détectée, et puisque les erreurs fatales interrompent l'exécution du code, elles joueront toujours le rôle du "dernier":
function shutdown() { $error = error_get_last(); if (
Je voudrais attirer l'attention sur le fait que ce code se produit même pour la gestion des erreurs, et vous pouvez même le rencontrer, mais il a perdu sa pertinence depuis la 7ème version de PHP. Ce qui est venu remplacer je le dirai un peu plus tard.
TâcheComplétez le gestionnaire d'erreur fatale avec la sortie du code source du fichier où l'erreur a été commise, et ajoutez également la coloration syntaxique du code de sortie.
À propos de la gourmandise
Faisons un test simple et découvrons combien de ressources précieuses mange l'erreur la plus triviale:
À la suite de l'exécution de ce script, j'ai obtenu ce résultat:
0.002867 seconds
Ajoutez maintenant l'erreur dans la boucle:
Le résultat est vraisemblablement pire, et d'un ordre de grandeur (même deux ordres de grandeur!):
0.263645 seconds
La conclusion est claire - les erreurs dans le code entraînent une gourmandise excessive des scripts - alors activez l'affichage de toutes les erreurs pendant le développement et les tests de l'application!Les tests ont été effectués sur différentes versions de PHP, et partout la différence est des dizaines de fois, alors que ce soit une autre raison de corriger toutes les erreurs dans le code
Où est le chien enterré
PHP a un symbole spécial «@» - un opérateur de suppression d'erreur, il est utilisé pour ne pas écrire la gestion des erreurs, mais s'appuie sur le comportement correct de PHP, auquel cas
<?php echo @UNKNOWN_CONSTANT;
Dans ce cas, le gestionnaire d'erreur spécifié dans
set_error_handler()
sera toujours appelé, et le fait que la suppression a été appliquée à l'erreur peut être suivi en appelant la fonction
error_reporting()
à l'intérieur du gestionnaire, auquel cas elle renverra
0
.
Si vous supprimez les erreurs de cette manière, cela réduit la charge du processeur par rapport à si vous les masquez simplement (voir le test comparatif ci-dessus), mais dans tous les cas, la suppression des erreurs est mauvaise
TâcheVérifiez comment la suppression des erreurs avec @
affecte l'exemple de boucle précédent.
Exceptions
À l'ère de PHP4, il n'y avait pas d'exceptions, tout était beaucoup plus compliqué, et les développeurs se débattaient avec les erreurs du mieux qu'ils pouvaient, c'était une bataille non pas pour la vie mais pour la mort ... Vous pouvez plonger dans cette histoire fascinante de la confrontation dans l'article Exceptional Code. Partie 1 Dois-je le lire maintenant? Je ne peux pas donner de réponse définitive, je veux juste noter que cela vous aidera à comprendre l’évolution du langage et révélera tout le charme des exceptions.
Les exceptions sont des événements exceptionnels en PHP, contrairement aux erreurs, non seulement elles signalent un problème, mais nécessitent des actions supplémentaires de la part du programmeur pour gérer chaque cas spécifique.
Par exemple, un script doit enregistrer certaines données dans un fichier cache si quelque chose ne va pas (pas d'accès en écriture, pas d'espace disque), une exception du type correspondant est générée et la décision est prise dans le gestionnaire d'exceptions - enregistrer dans un autre emplacement ou informer l'utilisateur du problème.
Une exception est un objet de la classe
Exception
ou de l'un de ses nombreux descendants, elle contient le texte d'erreur, le statut et peut également contenir un lien vers une autre exception qui est devenue la cause première de cela. Le modèle d'exception en PHP est similaire à celui utilisé dans d'autres langages de programmation. Une exception peut être levée (comme on dit «lancer») à l'aide de l'opérateur de
throw
, et vous pouvez attraper («attraper») l'instruction
catch
. Le code qui lève l'exception doit être entouré d'un bloc
try
pour intercepter l'exception. Chaque bloc
try
doit avoir au moins un
catch
correspondant ou
finally
bloc:
try {
Dans quels cas, il vaut la peine d'utiliser des exceptions:
- si, dans le cadre d'une méthode / fonction, plusieurs opérations peuvent échouer
- si votre framework ou bibliothèque déclare leur utilisation
Pour illustrer le premier scénario, prenons un exemple déjà exprimé d'une fonction d'écriture de données dans un fichier - de nombreux facteurs peuvent nous empêcher, et pour dire au code ci-dessus quel était exactement le problème, vous devez créer et lever une exception:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
En conséquence, nous intercepterons ces exceptions comme ceci:
try {
Cet exemple montre un scénario très simple pour gérer les exceptions, lorsque nous avons une exception gérée d'une manière. Mais souvent, diverses exceptions nécessitent une approche différente du traitement, puis vous devez utiliser des codes d'exception et définir la hiérarchie des exceptions dans l'application:
Maintenant, si vous utilisez ces exceptions, vous pouvez obtenir le code suivant:
try {
Il est important de se rappeler que l'exception est avant tout un événement exceptionnel, c'est-à-dire une exception à la règle. Vous n'avez pas besoin de les utiliser pour gérer des erreurs évidentes, par exemple, pour valider l'entrée utilisateur (bien que ce ne soit pas si simple). Dans ce cas, le gestionnaire d'exceptions doit être écrit à l'endroit où il pourra le gérer. Par exemple, le gestionnaire des exceptions causées par l'inaccessibilité d'un fichier à écrire doit être dans la méthode responsable de la sélection du fichier ou de la méthode qui l'appelle, afin qu'il puisse sélectionner un autre fichier ou un répertoire différent.
Alors, que se passera-t-il si vous ne capturez pas l'exception? Vous obtiendrez "Erreur fatale: exception non interceptée ...". Désagréable.
Pour éviter cette situation, vous devez utiliser la fonction
set_exception_handler () et définir le gestionnaire pour les exceptions
levées en dehors du bloc try-catch et qui n'ont pas été traitées. Après avoir appelé un tel gestionnaire, l'exécution du script sera arrêtée:
Je vais également vous parler de la construction en utilisant le bloc
finally
- ce bloc sera exécuté indépendamment du fait qu'une exception ait été levée ou non:
try {
Pour comprendre ce que cela nous donne, je vais donner l'exemple suivant de l'utilisation du bloc
finally
:
try {
C'est-à-dire rappelez-vous - le bloc
finally
sera exécuté même si vous lancez une exception ci-dessus dans le bloc
catch
(en fait, c'est ce qu'il voulait).
Pour un article d'introduction juste, qui a soif de plus de détails, vous les trouverez dans l'article
Code exceptionnel ;)
TâcheÉcrivez votre gestionnaire d'exceptions, avec la sortie du texte du fichier où l'erreur s'est produite, et tout cela avec la coloration syntaxique, n'oubliez pas non plus d'afficher la trace sous une forme lisible. Pour référence, voyez à quel point il a l'air cool sur les
whoops .
PHP7 - tout n'est plus comme avant
Donc, maintenant vous avez appris toutes les informations ci-dessus et maintenant je vais vous charger d'innovations en PHP7, c'est-à-dire
Je vais parler de ce que vous rencontrerez en travaillant sur un projet PHP moderne. Plus tôt, je vous l'ai dit et montré avec des exemples quelle béquille vous devez construire afin de détecter les erreurs critiques, et donc - en PHP7, ils ont décidé de le réparer, mais? comme d'habitude? lié à la compatibilité descendante du code, et reçu, bien qu'une solution universelle, mais il est loin d'être idéal. Et maintenant sur les points concernant les changements:- lorsque des erreurs fatales du type
E_ERROR
ou des erreurs fatales se produisent avec la possibilité de traiter E_RECOVERABLE_ERROR
PHP lève une exception - ces exceptions n'héritent pas de la classe Exception (rappelez-vous, j'ai parlé de compatibilité descendante, c'est tout pour elle)
- ces exceptions héritent de la classe Error
- les classes Exception et Error implémentent l'interface Throwable
- vous ne pouvez pas implémenter l'interface jetable dans votre code
L'interface Throwable
nous répète presque complètement Exception
: interface Throwable { public function getMessage(): string; public function getCode(): int; public function getFile(): string; public function getLine(): int; public function getTrace(): array; public function getTraceAsString(): string; public function getPrevious(): Throwable; public function __toString(): string; }
Est-ce difficile? Maintenant, pour des exemples, prenez ceux qui étaient plus élevés et légèrement modernisés: try {
En conséquence, nous interceptons l'erreur et imprimons: object(ParseError)
Comme vous pouvez le voir, ils ont intercepté l'exception ParseError , qui est le successeur de l'exception Error
qui implémente l'interface Throwable
dans la maison que Jack a construite. Il existe de nombreuses autres exceptions, mais je ne tourmenterai pas - pour plus de clarté, je donnerai une hiérarchie d'exceptions: interface Throwable |- Exception implements Throwable | |- ErrorException extends Exception | |- ... extends Exception | `- ... extends Exception `- Error implements Throwable |- TypeError extends Error |- ParseError extends Error |- ArithmeticError extends Error | `- DivisionByZeroError extends ArithmeticError `- AssertionError extends Error
Et un peu plus en détail:TypeError - pour les erreurs lorsque le type des arguments de fonction ne correspond pas au type transmis: try { (function(int $one, int $two) { return; })('one', 'two'); } catch (TypeError $e) { echo $e->getMessage(); }
ArithmeticError - peut se produire pendant les opérations mathématiques, par exemple, lorsque le résultat du calcul dépasse la limite allouée pour un entier: try { 1 << -1; } catch (ArithmeticError $e) { echo $e->getMessage(); }
DivisionByZeroError - division par zéro erreur: try { 1 / 0; } catch (ArithmeticError $e) { echo $e->getMessage(); }
AssertionError - une bête rare qui apparaît lorsque la condition spécifiée dans assert () n'est pas remplie: ini_set('zend.assertions', 1); ini_set('assert.exception', 1); try { assert(1 === 0); } catch (AssertionError $e) { echo $e->getMessage(); }
A paramètres directive production serveur zend.assertions
et assert.exception
coupé, et à juste titre
Vous trouverez une liste complète des exceptions prédéfinies dans le manuel officiel , dans la même hiérarchie des exceptions SPL .TâcheÉcrivez un gestionnaire d'erreur universel pour PHP7 qui interceptera toutes les exceptions possibles.
Lors de la rédaction de cette section, des éléments de l'article Exceptions et erreurs pouvant être levées en PHP 7 ont été utilisés .
Uniformité
- Il y a des erreurs, des exceptions, mais tout cela peut-il être ratissé d'une manière ou d'une autre?Oui, c'est facile, nous en avons set_error_handler()
un et personne ne nous interdira de lever une exception à l'intérieur de ce gestionnaire:
Mais cette approche avec PHP7 est redondante, maintenant elle peut tout gérer Throwable
: try { } catch (\Throwable $e) {
Débogage
Parfois, pour déboguer du code, vous devez suivre ce qui est arrivé à une variable ou à un objet à un certain stade, à ces fins, il existe une fonction debug_backtrace () et debug_print_backtrace () qui renverra l'historique des appels aux fonctions / méthodes dans l'ordre inverse: <?php function example() { echo '<pre>'; debug_print_backtrace(); echo '</pre>'; } class ExampleClass { public static function method () { example(); } } ExampleClass::method();
À la suite de l'exécution de la fonction debug_print_backtrace()
, une liste des appels qui nous ont amenés à ce point sera affichée:
Vous pouvez vérifier le code pour les erreurs de syntaxe en utilisant la fonction php_check_syntax () ou la commande php -l [ ]
, mais je n'en ai pas vu l'utilisation.Affirmer
Je voudrais également parler d'une bête aussi exotique que assert () en PHP. En fait, cette pièce peut être considérée comme un mimétisme pour la méthodologie de programmation des contrats, puis je vous dirai comment je ne l'ai jamais utilisée :)La fonction a assert()
changé son comportement lors de la transition de la version 5.6 à 7.0, et tout a changé encore plus fort dans la version 7.2, alors lisez attentivement les journaux des modifications et PHP;)
Le premier cas est celui où vous devez écrire TODO directement dans le code, afin de ne pas oublier d'implémenter la fonctionnalité donnée:
À la suite de l'exécution de ce code, nous obtenons E_WARNING
: Warning: assert(): Remove it! failed
PHP7 peut être basculé en mode exception, et au lieu d'une erreur, une exception apparaîtra toujours AssertionError
:
En conséquence, nous nous attendons à une exception AssertionError
.Si nécessaire, vous pouvez lever une exception arbitraire: assert(false, new Exception("Remove it!"));
Je recommanderais d'utiliser des balises @TODO
, les IDE modernes fonctionnent bien avec eux, et vous n'aurez pas besoin de déployer des efforts et des ressources supplémentaires pour travailler avec eux, bien que la tentation de «marquer» avec eux soit grande
Le deuxième cas d'utilisation consiste à créer une sorte de TDD, mais rappelez-vous, ce n'est qu'une similitude. Cependant, si vous essayez dur, vous pouvez obtenir un résultat amusant qui vous aidera à tester votre code:
La troisième option est une sorte de programmation sous contrat, lorsque vous décrivez les règles d'utilisation de votre bibliothèque, mais que vous voulez vous assurer que vous êtes bien compris, et dans ce cas, informez immédiatement le développeur d'une erreur (je ne suis même pas sûr de bien le comprendre, mais un exemple Le code fonctionne assez bien): function setupDb ($settings) {
Si vous êtes intéressé par les contrats, alors spécialement pour vous, j'ai un lien vers le framework PhpDeal .
Ne jamais utiliser assert()
pour vérifier les paramètres d'entrée, car en fait, il assert()
interprète le premier paramètre (se comporte comme eval()
), et cela est lourd d'injection PHP. Et oui, c'est le comportement correct, car si vous désactivez l'assertion, tous les arguments passés seront ignorés, et si vous faites comme dans l'exemple ci-dessus, le code sera exécuté et le résultat booléen de l'exécution sera passé à l'intérieur de l'assertion désactivée. Oh, et cela a changé en PHP 7.2 :)
Si vous avez une expérience d'utilisation en direct assert()
- partagez avec moi, je vous en serai reconnaissant. Et oui, voici une autre lecture intéressante pour vous sur ce sujet - PHP Assertions , avec la même question à la fin :)En conclusion
Je vais écrire les conclusions de cet article pour vous:- Lutter contre les erreurs - elles ne devraient pas figurer dans votre code
- Utilisez les exceptions - travailler avec eux doit être correctement organisé et il y aura du bonheur
- Affirmer - en savoir plus sur eux, et bien
PS
Ceci est une rediffusion d'une série d'articles "PHP pour les débutants":Si vous avez des commentaires sur le contenu de l'article, ou éventuellement sous forme, décrivez l'essentiel dans les commentaires, et nous améliorerons encore ce contenu.Merci à Maxim Slesarenko pour l'aide à la rédaction de l'article.