Vous connaissez tous les fonctions PHP isset et empty ? Mais savez-vous vraiment ce qu'il se cache derrière et pourquoi il ne faut pas les utiliser ?
Pour les deux du fond qui ont dormit pendant les cours de PHP et qui ne savent pas lire une documentation, voici une explication assez brève.
Le isset
permet, comme son nom l'indique, de regarder si une variable ou une entrée du tableau est définie ou non. Mais là où elle est vicieuse, c'est que si vous testez une variable qui a comme contenu null
ou une clé de tableau qui elle aussi contient null
, isset va vous renvoyer false
.
Le empty
quand à lui permet de regarder si une variable est vide ou non. Mais quand je dis vide c'est un vide assez bizarre. En effet, la fonction va retourner true
si elle est égale à :
Comme vous pouvez le voir, empty est bien trop vague.
Imaginons que vous proposez aux utilisateurs de votre site une API pour créer des galeries d'images. S'ils souhaitent en créer une et ajouter des images il va soumettre quelque chose comme cela :
POST /gallery
{
"name": "Foo Gallery",
"images": [
{
"filename": "image1.jpg",
"label": "Foo label"
},
[...]
]
}
POST /gallery
{
"name": "Foo Gallery",
"images": [
{
"filename": "image1.jpg",
"label": "Foo label"
},
[...]
]
}
Parfait, elle est maintenant créée. Si je souhaite la modifier, je peux faire un PUT
. Je vais donc renvoyer mon objet images
et en PHP je vais avoir un code comme celui-ci :
if (isset($_POST['images'])) {
// Traitement
}
if (isset($_POST['images'])) {
// Traitement
}
ou
if (empty($_POST['images'])) {
// Traitement
}
if (empty($_POST['images'])) {
// Traitement
}
Dans le cas d'un PATCH
où je ne renverrai que le nom de ma galerie, cela ne pose pas de problème.
Mais imaginons que je veux supprimer toutes les images de la galerie et dans mes specs je dis qu'il faut me renvoyer un objet images
avec une valeur à null
. Si je laisse le code comme cela, jamais je n'entrerai dans la boucle qui me permet de supprimer toutes les images.
PUT /gallery/{id}
{
"name": "Foo Gallery",
"images": null
}
PUT /gallery/{id}
{
"name": "Foo Gallery",
"images": null
}
L'autre erreur que je fais, est de penser que l'utilisateur va bien m'envoyer un objet images
qui sera un tableau. Et comme disaient les Inconnus :
Il ne faut pas prendre les clients pour des cons, mais il ne faut pas oublier qu'ils le sont.
Et bien là c'est pareil. Qu'est-ce qu'il se passe si j'envoie directement les clés filename
ou label
, ou totalement autre chose ? Et bien mon script va planter. Donc pensez bien à tester ce que vous souhaitez avec les bonnes méthodes array_key_exists, is_array, is_int ...
Comme ces fonctions testent beaucoup de choses, elles font du traitement pour rien. Alors oui, pour un site vitrine où vous allez avoir une centaine de visites par mois cela ne va pas se voir. Mais si vous avez un site à forte affluence comme un gros e-commerce ? Là vous risquez de voir vos performances chuter. Et vous le savez, avoir un site lent n'est pas très bon.
Le test de performance qui suit a été réalisé grâce au site PHP Benchmarks qui vous propose différents benchmarks pour différents frameworks (Symfony, CakePHP, Laravel ...) mais aussi des moteurs de templates (Twig, Smarty ...) et en fonction des différentes versions de PHP. Si vous souhaitez en savoir plus sur le protocole de benchmarks, je vous laisse lire la documentation.
Voici un petit graphique qui vous montre les différentes fonctions, le temps qu'elles prennent à s'exécuter et les versions de PHP :
Comme vous pouvez le voir, le temps de traitement pour un isset
est différent d'un array_key_exists
. Mais on peut voir aussi que PHP a fait de bonnes améliorations sur array_key_exists
qui est maintenant aussi performant que isset
sur PHP 7.3. Quant au empty
, il est plus long de quelques millisecondes que isset
, mais sur un script qui fait beaucoup d'appels à empty
, cela peut devenir assez long. Pensez donc bien à tester avec la bonne fonction.
Voici 2 autres graphes mais avec un nombre d'appels différents :
Notez bien le \
avant le array_key_exists
, il permet de dire à PHP d'aller chercher la fonction dans le namespace global et pas dans le namespace courant. L'ordre de recherche est :
use
Pour conclure, je dirai que peu importe votre code, testez toujours ce que vous souhaitez pour les performances mais aussi pour la lecture du code. Pensez à celui ou celle qui va relire votre code, ou même vous dans quelques mois/années. Il est toujours plus compliqué de lire :
if (false === is_null($myVar)) {
}
if (false === is_null($myVar)) {
}
que
if (is_array($myVar)) {
}
if (is_array($myVar)) {
}
Dans le premier cas je vais rentrer dans le if sans savoir ce que contient $myVar
, alors que dans le second je sais que j'aurai un tableau.
Et pour terminer, une petite synthèse de ce que vous retournent isset
et empty
avec soit en paramètre $myVar
soit $myVar['key']
:
isset | empty | |
---|---|---|
$myVar = "foo"; | true | false |
$myVar = "0"; | true | true |
$myVar = 0; | true | true |
$myVar = 0.0; | true | true |
$myVar = ""; | true | true |
$myVar = null; | false | true |
$myVar = false; | true | true |
$myVar non définie | false | true |
$myVar = []; | false | true |
$myVar = ["key" => "bar"]; | true | false |
$myVar = ["key" => null]; | false | true |