Code PHP bogué : les 10 erreurs les plus courantes commises par les développeurs PHP
Publié: 2022-03-11PHP rend relativement facile la construction d'un système basé sur le Web, ce qui explique en grande partie sa popularité. Mais malgré sa facilité d'utilisation, PHP est devenu un langage assez sophistiqué avec de nombreux frameworks, nuances et subtilités qui peuvent mordre les développeurs, entraînant des heures de débogage époustouflants. Cet article met en évidence dix des erreurs les plus courantes dont les développeurs PHP doivent se méfier.
Erreur courante #1 : Laisser des références de tableau pendantes après les boucles foreach
Vous ne savez pas comment utiliser les boucles foreach en PHP ? L'utilisation de références dans les boucles foreach
peut être utile si vous souhaitez opérer sur chaque élément du tableau sur lequel vous itérez. Par exemple:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
Le problème est que, si vous ne faites pas attention, cela peut aussi avoir des effets secondaires et des conséquences indésirables. Plus précisément, dans l'exemple ci-dessus, après l'exécution du code, $value
restera dans la portée et contiendra une référence au dernier élément du tableau. Les opérations ultérieures impliquant $value
pourraient donc involontairement finir par modifier le dernier élément du tableau.
La principale chose à retenir est que foreach
ne crée pas de portée. Ainsi, $value
dans l'exemple ci-dessus est une référence dans la portée supérieure du script. À chaque itération, foreach
définit la référence pour pointer vers l'élément suivant de $array
. Une fois la boucle terminée, par conséquent, $value
pointe toujours vers le dernier élément de $array
et reste dans la portée.
Voici un exemple du type de bugs évasifs et déroutants auxquels cela peut conduire :
$array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (ie, copy) echo implode(',', $array), "\n";
Le code ci-dessus affichera ce qui suit :
1,2,3 1,2,3 1,2,2
Non, ce n'est pas une faute de frappe. La dernière valeur de la dernière ligne est bien un 2, pas un 3.
Pourquoi?
Après avoir traversé la première boucle foreach
, $array
reste inchangé mais, comme expliqué ci-dessus, $value
est laissé comme une référence pendante au dernier élément de $array
(puisque cette boucle foreach
accédé $value
par référence ).
En conséquence, lorsque nous passons par la deuxième boucle foreach
, des "trucs bizarres" semblent se produire. Plus précisément, puisque $value
est maintenant accessible par value (c'est-à-dire par copy ), foreach
copie chaque élément $array
séquentiel dans $value
à chaque étape de la boucle. En conséquence, voici ce qui se passe à chaque étape de la deuxième boucle foreach
:
- Passe 1 : copie
$array[0]
(c'est-à-dire « 1 ») dans$value
(qui est une référence à$array[2]
), donc$array[2]
est maintenant égal à 1. Donc$array
contient maintenant [1, 2, 1]. - Passe 2 : copie
$array[1]
(c'est-à-dire « 2 ») dans$value
(qui est une référence à$array[2]
), donc$array[2]
est maintenant égal à 2. Donc$array
contient maintenant [1, 2, 2]. - Passe 3 : copie
$array[2]
(qui est maintenant égal à "2") dans$value
(qui est une référence à$array[2]
), donc$array[2]
est toujours égal à 2. Donc$array
contient maintenant [1 , 2, 2].
Pour toujours bénéficier de l'utilisation des références dans les boucles foreach
sans courir le risque de ce genre de problèmes, appelez unset()
sur la variable, immédiatement après la boucle foreach
, pour supprimer la référence ; par exemple:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
Erreur courante n°2 : mauvaise compréhension du comportement isset()
Malgré son nom, isset()
renvoie non seulement false si un élément n'existe pas, mais renvoie également false
pour les valeurs null
.
Ce comportement est plus problématique qu'il n'y paraît au premier abord et est une source courante de problèmes.
Considérer ce qui suit:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
L'auteur de ce code voulait probablement vérifier si keyShouldBeSet
était défini dans $data
. Mais, comme indiqué, isset($data['keyShouldBeSet'])
renverra également false si $data['keyShouldBeSet']
était défini, mais était défini sur null
. La logique ci-dessus est donc erronée.
Voici un autre exemple :
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
Le code ci-dessus suppose que si $_POST['active']
renvoie true
, alors postData
sera nécessairement défini, et donc isset($postData)
renverra true
. Ainsi, à l'inverse, le code ci-dessus suppose que la seule façon pour isset($postData)
renvoyer false
est si $_POST['active']
renvoie également false
.
Pas.
Comme expliqué, isset($postData)
également false
si $postData
était défini sur null
. Il est donc possible que isset($postData)
renvoie false
même si $_POST['active']
renvoyé true
. Encore une fois, la logique ci-dessus est erronée.
Et en passant, en passant, si l'intention dans le code ci-dessus était vraiment de vérifier à nouveau si $_POST['active']
retournait vrai, s'appuyer sur isset()
pour cela était une mauvaise décision de codage dans tous les cas. Au lieu de cela, il aurait été préférable de simplement revérifier $_POST['active']
; c'est à dire:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
Pour les cas, cependant, où il est important de vérifier si une variable a été réellement définie (c'est-à-dire, pour faire la distinction entre une variable qui n'a pas été définie et une variable qui a été définie sur null
), la méthode array_key_exists()
est beaucoup plus robuste Solution.
Par exemple, nous pourrions réécrire le premier des deux exemples ci-dessus comme suit :
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
De plus, en combinant array_key_exists()
avec get_defined_vars()
, nous pouvons vérifier de manière fiable si une variable dans la portée actuelle a été définie ou non :
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Erreur courante n° 3 : confusion entre le retour par référence et le retour par valeur
Considérez cet extrait de code :
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Si vous exécutez le code ci-dessus, vous obtiendrez ce qui suit :
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Qu'est-ce qui ne va pas?
Le problème est que le code ci-dessus confond les tableaux de retour par référence avec les tableaux de retour par valeur. À moins que vous ne disiez explicitement à PHP de renvoyer un tableau par référence (c'est-à-dire en utilisant &
), PHP renverra par défaut le tableau « par valeur ». Cela signifie qu'une copie du tableau sera renvoyée et que, par conséquent, la fonction appelée et l'appelant n'accéderont pas à la même instance du tableau.
Ainsi, l'appel ci-dessus à getValues()
renvoie une copie du tableau $values
plutôt qu'une référence à celui-ci. Dans cet esprit, revoyons les deux lignes clés de l'exemple ci-dessus :
// getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];
Une solution possible consisterait à enregistrer la première copie du tableau $values
renvoyé par getValues()
, puis à opérer ultérieurement sur cette copie ; par exemple:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
Ce code fonctionnera correctement (c'est-à-dire qu'il affichera test
sans générer de message "index indéfini"), mais selon ce que vous essayez d'accomplir, cette approche peut ou non être adéquate. En particulier, le code ci-dessus ne modifiera pas le tableau $values
d'origine. Donc, si vous voulez que vos modifications (telles que l'ajout d'un élément 'test') affectent le tableau d'origine, vous devrez plutôt modifier la fonction getValues()
pour renvoyer une référence au tableau $values
lui-même. Cela se fait en ajoutant un &
avant le nom de la fonction, indiquant ainsi qu'elle doit renvoyer une référence ; c'est à dire:
class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
La sortie de ceci sera test
, comme prévu.
Mais pour rendre les choses plus confuses, considérez plutôt l'extrait de code suivant :
class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Si vous avez deviné que cela entraînerait la même erreur "index indéfini" que notre exemple array
précédent, vous vous êtes trompé. En fait, ce code fonctionnera très bien. La raison en est que, contrairement aux tableaux, PHP passe toujours les objets par référence . ( ArrayObject
est un objet SPL, qui imite complètement l'utilisation des tableaux, mais fonctionne comme un objet.)
Comme le montrent ces exemples, il n'est pas toujours tout à fait évident en PHP de savoir s'il s'agit d'une copie ou d'une référence. Il est donc essentiel de comprendre ces comportements par défaut (c'est-à-dire que les variables et les tableaux sont passés par valeur ; les objets sont passés par référence) et aussi de vérifier attentivement la documentation de l'API pour la fonction que vous appelez pour voir si elle renvoie une valeur, un copie d'un tableau, une référence à un tableau ou une référence à un objet.
Cela dit, il est important de noter que la pratique consistant à renvoyer une référence à un tableau ou à un ArrayObject
est généralement à éviter, car elle offre à l'appelant la possibilité de modifier les données privées de l'instance. Cela « va à l'encontre » de l'encapsulation. Au lieu de cela, il est préférable d'utiliser des "getters" et des "setters" à l'ancienne, par exemple :
class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'
Cette approche donne à l'appelant la possibilité de définir ou d'obtenir n'importe quelle valeur dans le tableau sans fournir un accès public au tableau $values
autrement privé lui-même.
Erreur courante n° 4 : effectuer des requêtes en boucle
Il n'est pas rare de tomber sur quelque chose comme ça si votre PHP ne fonctionne pas :
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Bien qu'il n'y ait absolument rien de mal ici, mais si vous suivez la logique du code, vous constaterez peut-être que l'appel innocent ci-dessus à $valueRepository->findByValue()
aboutit finalement à une requête quelconque, telle que :
$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);
Par conséquent, chaque itération de la boucle ci-dessus entraînerait une requête distincte dans la base de données. Ainsi, si, par exemple, vous fournissez un tableau de 1 000 valeurs à la boucle, cela générerait 1 000 requêtes distinctes à la ressource ! Si un tel script est appelé dans plusieurs threads, cela pourrait potentiellement immobiliser le système.
Il est donc crucial de reconnaître quand les requêtes sont effectuées par votre code et, dans la mesure du possible, de rassembler les valeurs, puis d'exécuter une requête pour récupérer tous les résultats.
Un exemple d'un endroit assez courant pour rencontrer des requêtes effectuées de manière inefficace (c'est-à-dire dans une boucle) est lorsqu'un formulaire est publié avec une liste de valeurs (ID, par exemple). Ensuite, pour récupérer les données d'enregistrement complètes pour chacun des ID, le code parcourt le tableau et effectue une requête SQL distincte pour chaque ID. Cela ressemblera souvent à ceci :
$data = []; foreach ($ids as $id) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }
Mais la même chose peut être accomplie beaucoup plus efficacement dans une seule requête SQL comme suit :
$data = []; if (count($ids)) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Il est donc crucial de savoir quand des requêtes sont faites, directement ou indirectement, par votre code. Dans la mesure du possible, rassemblez les valeurs, puis exécutez une requête pour récupérer tous les résultats. Pourtant, la prudence doit également être exercée là-bas, ce qui nous amène à notre prochaine erreur courante en PHP…
Erreur courante n° 5 : faux-semblants et inefficacités de l'utilisation de la mémoire
Bien que la récupération de plusieurs enregistrements à la fois soit nettement plus efficace que l'exécution d'une seule requête pour chaque ligne à récupérer, une telle approche peut potentiellement conduire à une condition de "mémoire insuffisante" dans libmysqlclient
lors de l'utilisation de l'extension mysql
de PHP.
Pour démontrer, jetons un coup d'œil à une boîte de test avec des ressources limitées (512 Mo de RAM), MySQL et php-cli
.
Nous allons amorcer une table de base de données comme celle-ci :
// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }
OK, vérifions maintenant l'utilisation des ressources :
// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";
Sortir:

Before: 224704 Limit 1: 224704 Limit 10000: 224704
Frais. Il semble que la requête soit gérée en toute sécurité en interne en termes de ressources.
Juste pour être sûr, cependant, augmentons la limite une fois de plus et fixons-la à 100 000. Oh-oh. Quand on fait ça, on obtient :
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
Qu'est-il arrivé?
Le problème ici est le fonctionnement du module mysql
de PHP. C'est vraiment juste un proxy pour libmysqlclient
, qui fait le sale boulot. Lorsqu'une partie des données est sélectionnée, elle va directement en mémoire. Étant donné que cette mémoire n'est pas gérée par le gestionnaire de PHP, memory_get_peak_usage()
n'affichera aucune augmentation de l'utilisation des ressources lorsque nous augmenterons la limite de notre requête. Cela conduit à des problèmes comme celui démontré ci-dessus où nous sommes amenés à la complaisance en pensant que notre gestion de la mémoire est correcte. Mais en réalité, notre gestion de la mémoire est sérieusement défectueuse et nous pouvons rencontrer des problèmes comme celui présenté ci-dessus.
Vous pouvez au moins éviter le headfake ci-dessus (bien qu'il n'améliorera pas en soi votre utilisation de la mémoire) en utilisant à la place le module mysqlnd
. mysqlnd
est compilé en tant qu'extension PHP native et utilise le gestionnaire de mémoire de PHP.
Par conséquent, si nous exécutons le test ci-dessus en utilisant mysqlnd
plutôt que mysql
, nous obtenons une image beaucoup plus réaliste de notre utilisation de la mémoire :
Before: 232048 Limit 1: 324952 Limit 10000: 32572912
Et c'est encore pire que ça, soit dit en passant. Selon la documentation PHP, mysql
utilise deux fois plus de ressources que mysqlnd
pour stocker des données, de sorte que le script original utilisant mysql
utilisait encore plus de mémoire que ce qui est indiqué ici (environ deux fois plus).
Pour éviter de tels problèmes, envisagez de limiter la taille de vos requêtes et d'utiliser une boucle avec un petit nombre d'itérations ; par exemple:
$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }
Lorsque nous considérons à la fois cette erreur PHP et l'erreur n ° 4 ci-dessus, nous réalisons qu'il existe un équilibre sain que votre code doit idéalement atteindre entre, d'une part, le fait que vos requêtes soient trop granulaires et répétitives, par rapport à chacune de vos les requêtes individuelles sont trop volumineuses. Comme c'est le cas pour la plupart des choses dans la vie, l'équilibre est nécessaire ; l'un ou l'autre extrême n'est pas bon et peut causer des problèmes avec PHP qui ne fonctionne pas correctement.
Erreur courante #6 : Ignorer les problèmes Unicode/UTF-8
Dans un certain sens, c'est vraiment plus un problème dans PHP lui-même que quelque chose que vous rencontreriez lors du débogage de PHP, mais cela n'a jamais été traité de manière adéquate. Le noyau de PHP 6 devait être rendu compatible Unicode, mais cela a été suspendu lorsque le développement de PHP 6 a été suspendu en 2010.
Mais cela ne dispense en aucun cas le développeur de manipuler correctement l'UTF-8 et d'éviter l'hypothèse erronée selon laquelle toutes les chaînes seront nécessairement du "vieux ASCII". Le code qui ne parvient pas à gérer correctement les chaînes non ASCII est connu pour introduire des heisenbugs noueux dans votre code. Même de simples appels strlen($_POST['name'])
pourraient causer des problèmes si quelqu'un avec un nom de famille comme "Schrodinger" essayait de s'inscrire sur votre système.
Voici une petite liste de contrôle pour éviter de tels problèmes dans votre code :
- Si vous ne savez pas grand-chose sur Unicode et UTF-8, vous devriez au moins apprendre les bases. Il y a une excellente introduction ici.
- Assurez-vous de toujours utiliser les fonctions
mb_*
au lieu des anciennes fonctions de chaîne (assurez-vous que l'extension "multibyte" est incluse dans votre build PHP). - Assurez-vous que votre base de données et vos tables sont configurées pour utiliser Unicode (de nombreuses versions de MySQL utilisent toujours
latin1
par défaut). - N'oubliez pas que
json_encode()
convertit les symboles non ASCII (par exemple, "Schrodinger" devient "Schr\u00f6dinger") maisserialize()
ne le fait pas . - Assurez-vous que vos fichiers de code PHP sont également encodés en UTF-8 pour éviter les collisions lors de la concaténation de chaînes avec des constantes de chaîne codées en dur ou configurées.
Une ressource particulièrement précieuse à cet égard est l'article UTF-8 Primer for PHP and MySQL de Francisco Claria sur ce blog.
Erreur courante #7 : Supposer $_POST
contiendra toujours vos données POST
Malgré son nom, le tableau $_POST
ne contiendra pas toujours vos données POST et peut être facilement trouvé vide. Pour comprendre cela, regardons un exemple. Supposons que nous fassions une demande au serveur avec un appel jQuery.ajax()
comme suit :
// js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
(Incidemment, notez le contentType: 'application/json'
ici. Nous envoyons les données au format JSON, ce qui est très populaire pour les API. C'est la valeur par défaut, par exemple, pour la publication dans le service AngularJS $http
.)
Du côté serveur de notre exemple, nous vidons simplement le tableau $_POST
:
// php var_dump($_POST);
Étonnamment, le résultat sera :
array(0) { }
Pourquoi? Qu'est-il arrivé à notre chaîne JSON {a: 'a', b: 'b'}
?
La réponse est que PHP n'analyse automatiquement une charge utile POST que lorsqu'elle a un type de contenu application/x-www-form-urlencoded
ou multipart/form-data
. Les raisons en sont historiques - ces deux types de contenu étaient essentiellement les seuls utilisés il y a des années lorsque le $_POST
de PHP a été implémenté. Ainsi, avec tout autre type de contenu (même ceux qui sont très populaires aujourd'hui, comme application/json
), PHP ne charge pas automatiquement la charge utile POST.
Étant donné que $_POST
est un superglobal, si nous le remplaçons une fois (de préférence au début de notre script), la valeur modifiée (c'est-à-dire, y compris la charge utile POST) sera alors référençable dans tout notre code. Ceci est important car $_POST
est couramment utilisé par les frameworks PHP et presque tous les scripts personnalisés pour extraire et transformer les données de requête.
Ainsi, par exemple, lors du traitement d'une charge utile POST avec un type de contenu application/json
, nous devons analyser manuellement le contenu de la requête (c'est-à-dire décoder les données JSON) et remplacer la variable $_POST
, comme suit :
// php $_POST = json_decode(file_get_contents('php://input'), true);
Ensuite, lorsque nous vidons le tableau $_POST
, nous voyons qu'il inclut correctement la charge utile POST ; par exemple:
array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }
Erreur courante #8 : Penser que PHP prend en charge un type de données caractère
Regardez cet exemple de code et essayez de deviner ce qu'il affichera :
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . "\n"; }
Si vous avez répondu de 'a' à 'z', vous serez peut-être surpris de savoir que vous vous êtes trompé.
Oui, il imprimera 'a' à 'z', mais il imprimera également 'aa' à 'yz'. Voyons pourquoi.
En PHP, il n'y a pas de type de données char
; seule string
est disponible. Dans cet esprit, l'incrémentation de la string
z
en PHP donne aa
:
php> $c = 'z'; echo ++$c . "\n"; aa
Pourtant, pour compliquer davantage les choses, aa
est lexicographiquement inférieur à z
:
php> var_export((boolean)('aa' < 'z')) . "\n"; true
C'est pourquoi l'exemple de code présenté ci-dessus imprime les lettres a
à z
, mais aussi aa
à yz
. Il s'arrête lorsqu'il atteint za
, qui est la première valeur qu'il rencontre et qu'il est "supérieur à" z
:
php> var_export((boolean)('za' < 'z')) . "\n"; false
Cela étant, voici une façon de boucler correctement les valeurs 'a' à 'z' en PHP :
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . "\n"; }
Ou bien:
$letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . "\n"; }
Erreur courante n° 9 : Ignorer les normes de codage
Bien que le fait d'ignorer les normes de codage n'entraîne pas directement la nécessité de déboguer le code PHP, c'est probablement l'une des choses les plus importantes à discuter ici.
Ignorer les normes de codage peut causer toute une série de problèmes sur un projet. Au mieux, cela se traduit par un code incohérent (puisque chaque développeur "fait son propre truc"). Mais au pire, il produit du code PHP qui ne fonctionne pas ou peut être difficile (parfois presque impossible) à naviguer, ce qui le rend extrêmement difficile à déboguer, améliorer, maintenir. Et cela signifie une productivité réduite pour votre équipe, y compris beaucoup d'efforts inutiles (ou du moins inutiles).
Heureusement pour les développeurs PHP, il existe la PHP Standards Recommendation (PSR), composée des cinq standards suivants :
- PSR-0 : norme de chargement automatique
- PSR-1 : norme de codage de base
- PSR-2 : Guide de style de codage
- PSR-3 : Interface de l'enregistreur
- PSR-4 : chargeur automatique
PSR a été créé à l'origine sur la base des contributions des mainteneurs des plates-formes les plus reconnues du marché. Zend, Drupal, Symfony, Joomla et d'autres ont contribué à ces normes et les suivent maintenant. Même PEAR, qui a tenté d'être une norme pendant des années auparavant, participe maintenant au PSR.
Dans un certain sens, peu importe votre norme de codage, tant que vous êtes d'accord sur une norme et que vous vous y tenez, mais suivre le PSR est généralement une bonne idée, sauf si vous avez une raison impérieuse pour votre projet de faire autrement. . De plus en plus d'équipes et de projets se conforment au PSR. Tt est définitivement reconnu à ce stade comme "le" standard par la majorité des développeurs PHP, donc son utilisation aidera à s'assurer que les nouveaux développeurs sont familiers et à l'aise avec votre standard de codage lorsqu'ils rejoignent votre équipe.
Erreur courante n° 10 : utiliser à mauvais escient empty()
Certains développeurs PHP aiment utiliser empty()
pour les vérifications booléennes pour à peu près tout. Il y a des cas, cependant, où cela peut prêter à confusion.
Revenons d'abord aux tableaux et aux instances ArrayObject
(qui imitent les tableaux). Compte tenu de leur similitude, il est facile de supposer que les tableaux et les instances ArrayObject
se comporteront de manière identique. Cela s'avère cependant être une hypothèse dangereuse. Par exemple, en PHP 5.0 :
// PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?
Et pour aggraver les choses, les résultats auraient été différents avant PHP 5.0 :
// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
Cette approche est malheureusement assez populaire. Par exemple, c'est ainsi que Zend\Db\TableGateway
de Zend Framework 2 renvoie les données lors de l'appel de current()
sur le TableGateway::select()
comme le suggère la doc. Le développeur peut facilement être victime de cette erreur avec de telles données.
Pour éviter ces problèmes, la meilleure approche pour vérifier les structures de tableau vides consiste à utiliser count()
:
// Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)
Et accessoirement, puisque PHP convertit 0
en false
, count()
peut également être utilisé dans les if ()
pour vérifier les tableaux vides. Il convient également de noter qu'en PHP, count()
est une complexité constante (opération O(1)
) sur les tableaux, ce qui rend encore plus clair que c'est le bon choix.
Un autre exemple où empty()
peut être dangereux est de le combiner avec la fonction de classe magique __get()
. Définissons deux classes et avons une propriété de test
dans les deux.
Définissons d'abord une classe Regular
qui inclut test
en tant que propriété normale :
class Regular { public $test = 'value'; }
Définissons ensuite une classe Magic
qui utilise l'opérateur magique __get()
pour accéder à sa propriété de test
:
class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
OK, voyons maintenant ce qui se passe lorsque nous tentons d'accéder à la propriété test
de chacune de ces classes :
$regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"
Très bien jusqu'à présent.
Mais voyons maintenant ce qui se passe lorsque nous appelons empty()
sur chacun d'eux :
var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
Pouah. Donc, si nous nous appuyons sur empty()
, nous pouvons être induits en erreur en pensant que la propriété test
de $magic
est vide, alors qu'en réalité, elle est définie sur 'value'
.
Malheureusement, si une classe utilise la fonction magique __get()
pour récupérer la valeur d'une propriété, il n'existe aucun moyen infaillible de vérifier si cette valeur de propriété est vide ou non. En dehors de la portée de la classe, vous ne pouvez vraiment vérifier que si une valeur null
sera renvoyée, et cela ne signifie pas nécessairement que la clé correspondante n'est pas définie, car elle aurait pu être définie sur null
.
En revanche, si nous tentons de référencer une propriété inexistante d'une instance de classe Regular
, nous recevrons un avis semblable à celui-ci :
Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0
Donc, le point principal ici est que la méthode empty()
doit être utilisée avec précaution car elle peut se prêter à des résultats déroutants - voire potentiellement trompeurs - si l'on ne fait pas attention.
Emballer
La facilité d'utilisation de PHP peut endormir les développeurs dans un faux sentiment de confort, les laissant vulnérables à un long débogage PHP en raison de certaines nuances et particularités du langage. Cela peut entraîner le dysfonctionnement de PHP et des problèmes tels que ceux décrits ici.
Le langage PHP a considérablement évolué au cours de ses 20 ans d'histoire. Se familiariser avec ses subtilités est un effort louable, car cela contribuera à garantir que le logiciel que vous produisez est plus évolutif, robuste et maintenable.