Meilleur moyen de tester l'existence d'une variable en PHP; isset () est clairement cassé


187

À partir de la isset()documentation :

isset() will return FALSE if testing a variable that has been set to NULL.

Fondamentalement, isset()ne vérifie pas si la variable est définie, mais si elle est définie sur autre chose NULL.

Compte tenu de cela, quelle est la meilleure façon de vérifier réellement l'existence d'une variable? J'ai essayé quelque chose comme:

if(isset($v) || @is_null($v))

(le @est nécessaire pour éviter l'avertissement quand $vn'est pas défini) mais is_null()a un problème similaire à isset(): il retourne TRUEsur des variables non définies ! Il apparaît également que:

@($v === NULL)

fonctionne exactement comme @is_null($v), donc c'est aussi.

Comment sommes-nous censés vérifier de manière fiable l'existence d'une variable en PHP?


Edit: il y a clairement une différence en PHP entre les variables qui ne sont pas définies et les variables qui sont définies sur NULL:

<?php
$a = array('b' => NULL);
var_dump($a);

PHP montre que cela $a['b']existe et a une NULLvaleur. Si vous ajoutez:

var_dump(isset($a['b']));
var_dump(isset($a['c']));

vous pouvez voir l'ambiguïté dont je parle avec la isset()fonction. Voici le résultat de ces trois éléments var_dump()s:

array(1) {
  ["b"]=>
  NULL
}
bool(false)
bool(false)

Modifier davantage: deux choses.

Premièrement, un cas d'utilisation. Un tableau en cours de conversion en données d'une UPDATEinstruction SQL , où les clés du tableau sont les colonnes de la table et les valeurs du tableau sont les valeurs à appliquer à chaque colonne. Toutes les colonnes de la table peuvent contenir une NULLvaleur, signifiée en passant une NULLvaleur dans le tableau. Vous avez besoin d' un moyen de différencier entre une clé de tableau qui n'existe pas et la valeur d'un tableau définie sur NULL; c'est la différence entre ne pas mettre à jour la valeur de la colonne et mettre à jour la valeur de la colonne surNULL .

En second lieu , la réponse de Zoredache , array_key_exists()fonctionne correctement, pour mon cas d' utilisation ci - dessus et pour toutes les variables globales:

<?php
$a = NULL;
var_dump(array_key_exists('a', $GLOBALS));
var_dump(array_key_exists('b', $GLOBALS));

les sorties:

bool(true)
bool(false)

Étant donné que cela gère correctement à peu près partout, je peux voir qu'il y a une ambiguïté entre des variables qui n'existent pas et des variables qui sont définies sur NULL, j'appelle array_key_exists()le moyen officiel le plus simple en PHP pour vraiment vérifier l'existence d'une variable .

(Le seul autre cas auquel je peux penser concerne les propriétés de classe, pour lesquelles il existe property_exists(), qui, selon sa documentation , fonctionne de la même manière array_key_exists()en ce sens qu'il distingue correctement entre ne pas être défini et être défini sur NULL.)


Vous ne pouvez pas vérifier - mais pourquoi en avez-vous besoin?
trop de php

12
NULL a une signification très spécifique en PHP, et c'est un concept entièrement distinct de savoir si une variable est définie ou non.
chazomaticus

33
Il existe des raisons de faire la différence entre nul et inexistant. Par exemple, vous créez un objet pour représenter une ligne dans une table de base de données. Pour chaque colonne de la ligne, vous créez une variable privée, accessible uniquement via la méthode getter de l'objet. Supposons qu'une valeur de colonne soit nulle. Maintenant, comment cette méthode getter sait-elle s'il n'y a pas de telle colonne dans la table, ou si cet objet a juste une valeur nulle? Heureusement, dans mon cas, la variable privée est en fait une entrée dans un tableau privé, donc je peux utiliser array_key_exists, mais c'est un vrai problème.
Nathan Long

1
Il a été supprimé des nouvelles versions de PHP, oui. Malheureusement, il n'est pas passé de chaque déploiement de PHP. De plus, il semble être un détail sémantique inutile de se demander s'il s'agit d'éléments de tableau ou de variables. Quelles que soient les normes auxquelles vous pensez que le code devrait adhérer, il est utile de savoir comment contourner une incohérence dans le langage PHP.
chazomaticus

2
@chazomaticus Mais les variables et les éléments de tableau sont des choses fondamentalement différentes ; ce n'est pas parce que vous pouvez faire certaines des mêmes choses avec eux qu'ils sont ou devraient être interchangeables à 100%. Il n'y a pas "d'incohérence dans le langage PHP" ici, juste quelque chose que vous n'aimez / ne comprenez pas. En ce qui concerne register_globals, j'ai encore du mal à penser à une situation où même cela nécessiterait une telle distinction, car tout ce qui est enregistré à partir de la requête HTTP serait toujours une chaîne, non null.
IMSoP

Réponses:


97

Si la variable que vous vérifiez est dans la portée globale, vous pouvez faire:

array_key_exists('v', $GLOBALS) 

3
Ah ha! MAINTENANT, vous parlez! Comment feriez-vous cela pour, par exemple, les propriétés de classe?
chazomaticus

22
En variante, si la vérification doit également fonctionner pour les variables de portée locale, on peut faire un $defined_vars = get_defined_vars();puis tester via array_key_exists('v', $defined_vars);.
Henrik Opel

1
Cela me semble un peu moche, mais dans le cas où vous vérifiez réellement un élément de tableau, cela a beaucoup plus de sens: isset($foo[$bar])devientarray_key_exists($bar, $foo)
Arild

property_existssemble prometteur, sauf pour ceci:> La fonction property_exists () ne peut pas détecter les propriétés qui sont magiquement accessibles en utilisant la méthode magique __get.
alexw le

@alexw Les variables "créées" via __get n'existent en effet pas. __get est un code arbitraire utilisé comme solution de secours pour les variables inexistantes, qui peut renvoyer tout ce qu'il veut, que des données pertinentes aient été ou non stockées.
Brilliand

46

Tenter de donner un aperçu des différentes discussions et réponses:

Il n'y a pas de réponse unique à la question qui puisse remplacer tous les moyens qui issetpeuvent être utilisés. Certains cas d'utilisation sont traités par d'autres fonctions, tandis que d'autres ne résistent pas à l'examen ou ont une valeur douteuse au-delà du code golf. Loin d 'être «cassés» ou «incohérents», d' autres cas d 'utilisation démontrent pourquoi issetla réaction de nullest au comportement logique.

Cas d'utilisation réels (avec solutions)

1. Touches de tableau

Les tableaux peuvent être traités comme des collections de variables, avec unsetet en les issettraitant comme s'ils l'étaient. Cependant, puisqu'elles peuvent être itérées, comptées, etc., une valeur manquante n'est pas la même que celle dont la valeur est null.

La réponse dans ce cas, est d' utiliser array_key_exists()au lieu deisset() .

Puisque ceci prend le tableau à vérifier en tant qu'argument de fonction, PHP lèvera toujours des "avis" si le tableau lui-même n'existe pas. Dans certains cas, on peut valablement faire valoir que chaque dimension aurait dû être initialisée en premier, de sorte que l'avis fait son travail. Pour les autres cas, une fonction "récursive" array_key_exists, qui vérifie tour à tour chaque dimension du tableau, éviterait cela, mais serait fondamentalement la même que @array_key_exists. C'est aussi quelque peu tangentiel à la gestion des nullvaleurs.

2. Propriétés de l'objet

Dans la théorie traditionnelle de la «programmation orientée objet», l'encapsulation et le polymorphisme sont des propriétés clés des objets; dans une mise en œuvre des propriétés de la POO base de classe comme PHP de, les encapsulées sont déclarées dans le cadre du niveau d'accès définition de la classe, et compte tenu de ( public, protectedou private).

Cependant, PHP vous permet également d'ajouter dynamiquement des propriétés à un objet, comme vous le feriez pour des clés d'un tableau, et certaines personnes utilisent des objets sans classe (techniquement, des instances du intégré stdClass, qui n'a pas de méthodes ou de fonctionnalités privées) dans un façon de tableaux associatifs. Cela conduit à des situations où une fonction peut vouloir savoir si une propriété particulière a été ajoutée à l'objet qui lui est donné.

Comme avec les clés du tableau, une solution pour le contrôle des propriétés d'objet est inclus dans la langue, appelée, assez raisonnablement,property_exists .

Cas d'utilisation non justifiables, avec discussion

3. register_globalset autres pollutions de l'espace de noms mondial

La register_globalsfonctionnalité ajoutait des variables à la portée globale dont les noms étaient déterminés par des aspects de la requête HTTP (paramètres GET et POST et cookies). Cela peut conduire à un code bogué et non sécurisé, c'est pourquoi il a été désactivé par défaut depuis PHP 4.2, publié en août 2000 et complètement supprimé dans PHP 5.4, publié en mars 2012 . Cependant, il est possible que certains systèmes fonctionnent toujours avec cette fonctionnalité activée ou émulée. Il est également possible de «polluer» l'espace de noms global par d'autres moyens, en utilisant le globalmot - clé ou le $GLOBALStableau.

Premièrement, il register_globalsest peu probable qu'il produise de manière inattendue une nullvariable, car les valeurs GET, POST et cookie seront toujours des chaînes (avec ''toujours un retour truede isset), et les variables de la session devraient être entièrement sous le contrôle du programmeur.

Deuxièmement, la pollution d'une variable avec la valeur nulln'est un problème que si cela écrase une initialisation précédente. "Écraser" une variable non initialisée avec nullne serait problématique que si du code ailleurs faisait la distinction entre les deux états, donc à elle seule, cette possibilité est un argument contre une telle distinction.

4. get_defined_varsetcompact

Quelques fonctions rarement utilisées en PHP, telles que get_defined_varset compact, vous permettent de traiter les noms de variables comme s'il s'agissait de clés dans un tableau. Pour les variables globales, le tableau super-global$GLOBALS permet un accès similaire, et est plus courant. Ces méthodes d'accès se comporteront différemment si une variable n'est pas définie dans la portée correspondante.

Une fois que vous avez décidé de traiter un ensemble de variables comme un tableau en utilisant l'un de ces mécanismes, vous pouvez effectuer toutes les mêmes opérations sur celui-ci que sur n'importe quel tableau normal. Par conséquent, voir 1.

La fonctionnalité qui n'existait que pour prédire comment ces fonctions sont sur le point de se comporter (par exemple "y aura-t-il une clé 'foo' dans le tableau renvoyé par get_defined_vars?") Est superflue, car vous pouvez simplement exécuter la fonction et découvrir sans effets négatifs.

4a. Variables variables ( $$foo)

Bien que ce ne soit pas tout à fait la même chose que les fonctions qui transforment un ensemble de variables en un tableau associatif, la plupart des cas utilisant des "variables variables" ("assigner à une variable nommée en fonction de cette autre variable") peuvent et doivent être modifiés pour utiliser un tableau associatif à la place .

Un nom de variable, fondamentalement, est l'étiquette donnée à une valeur par le programmeur; si vous le déterminez au moment de l'exécution, ce n'est pas vraiment une étiquette mais une clé dans un magasin clé-valeur. Plus concrètement, en n'utilisant pas de tableau, vous perdez la capacité de compter, d'itérer, etc. il peut également devenir impossible d'avoir une variable «en dehors» du magasin de valeurs-clés, car elle pourrait être écrasée par $$foo.

Une fois changé pour utiliser un tableau associatif, le code sera prêt pour la solution 1. L'accès indirect aux propriétés des objets (par exemple $foo->$property_name) peut être adressé avec la solution 2.

5. issetest tellement plus facile à taper quearray_key_exists

Je ne suis pas sûr que ce soit vraiment pertinent, mais oui, les noms de fonctions de PHP peuvent parfois être assez longs et incohérents. Apparemment, les versions préhistoriques de PHP utilisaient la longueur d'un nom de fonction comme clé de hachage, de sorte que Rasmus a délibérément composé des noms de fonction de htmlspecialcharsmanière à ce qu'ils aient un nombre inhabituel de caractères ...

Pourtant, au moins, nous n'écrivons pas Java, hein? ;)

6. Les variables non initialisées ont un type

La page de manuel sur les principes de base des variables comprend cette déclaration:

Les variables non initialisées ont une valeur par défaut de leur type en fonction du contexte dans lequel elles sont utilisées

Je ne sais pas s'il existe dans le Zend Engine une notion de "type non initialisé mais connu" ou si cela lit trop dans l'instruction.

Ce qui est clair, c'est que cela ne fait aucune différence pratique à leur comportement, puisque les comportements décrits sur cette page pour les variables non initialisées sont identiques au comportement d'une variable dont la valeur est null. Pour choisir un exemple, les deux $aet $bdans ce code finiront par être l'entier 42:

unset($a);
$a += 42;

$b = null;
$b += 42;

(Le premier soulèvera une notification concernant une variable non déclarée, dans le but de vous faire écrire un meilleur code, mais cela ne fera aucune différence sur la façon dont le code s'exécute réellement.)

99. Détecter si une fonction a été exécutée

(Garder celui-ci en dernier, car il est beaucoup plus long que les autres. Peut-être que je le modifierai plus tard ...)

Considérez le code suivant:

$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
    if ( some_test($thing, $test_value) ) {
        $result = some_function($thing);
    }
}
if ( isset($result) ) {
    echo 'The test passed at least once!';
}

Si some_functionpeut revenir null, il est possible que le echone soit pas atteint même s'il some_testest retourné true. L'intention du programmeur était de détecter quand il $resultn'avait jamais été défini, mais PHP ne leur permet pas de le faire.

Cependant, il existe d'autres problèmes avec cette approche, qui deviennent évidents si vous ajoutez une boucle externe:

foreach ( $list_of_tests as $test_value ) {
    // something's missing here...
    foreach ( $list_of_things as $thing ) {
        if ( some_test($thing, $test_value) ) {
            $result = some_function($thing);
        }
    }
    if ( isset($result) ) {
        echo 'The test passed at least once!';
    }
}

Comme il $resultn'est jamais initialisé explicitement, il prendra une valeur lorsque le tout premier test réussit, ce qui rend impossible de dire si les tests suivants ont réussi ou non. C'est en fait un bogue extrêmement courant lorsque les variables ne sont pas initialisées correctement.

Pour résoudre ce problème, nous devons faire quelque chose sur la ligne où j'ai commenté qu'il manque quelque chose. La solution la plus évidente est de définir $resultune "valeur terminale" qui some_functionne peut jamais revenir; si tel est le cas null, le reste du code fonctionnera correctement. S'il n'y a pas de candidat naturel pour une valeur terminale parce some_functionque son type de retour est extrêmement imprévisible (ce qui est probablement un mauvais signe en soi), alors une valeur booléenne supplémentaire, par exemple $found, pourrait être utilisée à la place.

Première expérience de pensée: la very_nullconstante

PHP pourrait théoriquement fournir une constante spéciale - ainsi que null- à utiliser comme valeur terminale ici; vraisemblablement, il serait illégal de le renvoyer à partir d'une fonction, ou il y serait contraint null, et la même chose s'appliquerait probablement à sa transmission en tant qu'argument de fonction. Cela simplifierait légèrement ce cas très spécifique, mais dès que vous décidiez de re-factoriser le code - par exemple, de mettre la boucle interne dans une fonction séparée - cela deviendrait inutile. Si la constante pouvait être transmise entre les fonctions, vous ne pouviez pas garantir qu'elle some_functionne la renverrait pas, elle ne serait donc plus utile comme valeur de terminal universelle.

L'argument pour détecter les variables non initialisées dans ce cas se résume à l'argument de cette constante spéciale: si vous remplacez le commentaire par unset($result)et que vous le traitez différemment $result = null, vous introduisez une "valeur" car elle $resultne peut pas être transmise, et ne peut être détecté par des fonctions intégrées spécifiques.

Deuxième expérience de pensée: compteur d'affectation

Une autre façon de penser à ce que le dernier ifdemande est "quelque chose a-t-il été assigné $result?" Plutôt que de le considérer comme une valeur spéciale de $result, vous pourriez peut-être considérer cela comme des "métadonnées" sur la variable, un peu comme la "variable tainting" de Perl. Ainsi , plutôt que issetvous pourriez l' appeler has_been_assigned_to, et plutôt que unset, reset_assignment_state.

Mais si oui, pourquoi s'arrêter à un booléen? Que faire si vous voulez savoir combien de fois le test a réussi; vous pouvez simplement étendre vos métadonnées à un entier et avoir get_assignment_countet reset_assignment_count...

De toute évidence, l'ajout d'une telle fonctionnalité aurait un compromis entre la complexité et les performances du langage, il faudrait donc le peser soigneusement par rapport à son utilité attendue. Comme pour une very_nullconstante, elle ne serait utile que dans des circonstances très restreintes, et résiste de la même manière au réaffacturage.

La question qui semble évidente est de savoir pourquoi le moteur d'exécution PHP devrait supposer à l'avance que vous voulez garder une trace de ces choses, plutôt que de vous laisser le faire explicitement, en utilisant du code normal.


Concernant les classes et les propriétés, malheureusement property_exists () ne fonctionne pas lorsque la propriété est array, par exemple: Class {public $ property = array ()}. Lève une erreur.
Andrew

1
@Andrew semble bien fonctionner pour moi: 3v4l.org/TnAY5 Voulez- vous donner un exemple complet?
IMSoP

ouais cela semble fonctionner correctement, quelque chose ne va pas avec ma configuration. Désolé pour la fausse alarme :)
Andrew

20

Parfois, je me perds un peu en essayant de déterminer quelle opération de comparaison utiliser dans une situation donnée. isset()s'applique uniquement aux valeurs non initialisées ou explicitement nulles. Passer / attribuer null est un excellent moyen de s'assurer qu'une comparaison logique fonctionne comme prévu.

Pourtant, il est un peu difficile de penser alors voici une matrice simple comparant comment différentes valeurs seront évaluées par différentes opérations:

|           | ===null | is_null | isset | empty | if/else | ternary | count>0 |
| -----     | -----   | -----   | ----- | ----- | -----   | -----   | -----   |
| $a;       | true    | true    |       | true  |         |         |         |
| null      | true    | true    |       | true  |         |         |         |
| []        |         |         | true  | true  |         |         |         |
| 0         |         |         | true  | true  |         |         | true    |
| ""        |         |         | true  | true  |         |         | true    |
| 1         |         |         | true  |       | true    | true    | true    |
| -1        |         |         | true  |       | true    | true    | true    |
| " "       |         |         | true  |       | true    | true    | true    |
| "str"     |         |         | true  |       | true    | true    | true    |
| [0,1]     |         |         | true  |       | true    | true    | true    |
| new Class |         |         | true  |       | true    | true    | true    |

Pour s'adapter à la table, j'ai compressé un peu les étiquettes:

  • $a; fait référence à une variable déclarée mais non attribuée
  • tout le reste de la première colonne fait référence à une valeur assignée, comme:
    • $a = null;
    • $a = [];
    • $a = 0;
  • les colonnes font référence à des opérations de comparaison, comme:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false

Tous les résultats sont booléens, trueimprimés et falseomis.

Vous pouvez exécuter les tests vous-même, vérifiez ceci:
https://gist.github.com/mfdj/8165967


Peut-être hors de portée de cette question, mais vous voudrez peut-être ajouter "0"au tableau, pour l'exhaustivité et la clarté de l' emptyopération
Rik Schaaf

17

Vous pouvez utiliser la construction de langage compact pour tester l'existence d'une variable nulle. Les variables qui n'existent pas ne s'afficheront pas dans le résultat, tandis que les valeurs nulles s'afficheront.

$x = null;
$y = 'y';

$r = compact('x', 'y', 'z');
print_r($r);

// Output:
// Array ( 
//  [x] => 
//  [y] => y 
// ) 

Dans le cas de votre exemple:

if (compact('v')) {
   // True if $v exists, even when null. 
   // False on var $v; without assignment and when $v does not exist.
}

Bien sûr, pour les variables de portée globale, vous pouvez également utiliser array_key_exists ().

D'ailleurs, personnellement, j'éviterais des situations comme la peste où il y a une différence sémantique entre une variable inexistante et la variable ayant une valeur nulle. PHP et la plupart des autres langages ne le pensent tout simplement pas.


3
PHP ne le fait pas, mais je ne dirais pas que la plupart des autres langages ne le font pas. La plupart des langages qui déclarent des variables lèveront une erreur si une variable n'a pas été déclarée, mais vous pouvez les définir sur NULL. Sémantiquement, cela NULLdevrait signifier «aucune ressource», mais ne pas définir une variable est une erreur de programmeur.
M Miller

1
@MMiller Bien sûr, mais écrire du code qui suit un chemin dans le cas de "aucune ressource" et un chemin différent dans le cas d'une "erreur de programmeur" est assez absurde. Si vous souhaitez détecter des variables non déclarées lors du débogage, utilisez un outil d'analyse statique, comme vous le feriez pour trouver des erreurs potentielles dans n'importe quelle langue.
IMSoP

@MMiller, Cool, comment as-tu pensé à ça.
Pacerier

1
@MMiller Mais cela ne fonctionne pas comme une réfutation, car la déclaration dans la réponse concerne explicitement "une variable qui n'existe pas", et votre contre-exemple concerne une propriété d'objet / clé de hachage qui n'existe pas . La distinction entre ces cas n'est pas seulement accessoire.
IMSoP

1
@MMiller - c'est en effet un meilleur exemple. Pourtant, après plus de 20 ans de programmation dans des langages stricts, les situations où j'avais besoin d'une distinction entre undefinedet nullsont si rares que cela ne me manque pas. À mon humble avis, l'utilisation principale undefinedest "erreur de programmeur dans un langage non strict". Dans un langage strict, si j'ai besoin d'un état distinct pour client did not state a value, alors je déclare une valeur appropriée à la situation et la teste. Dans le pire des cas, vous devez ajouter une variable d'indicateur distincte. Mais faire cela rarement est mieux que de devoir TOUJOURS faire face à DEUX états différents sans valeur !!
ToolmakerSteve

15

Expliquer NULL, penser logiquement

Je suppose que la réponse évidente à tout cela est ... N'initialisez pas vos variables comme NULL, initialisez-les comme quelque chose de pertinent par rapport à ce qu'elles sont censées devenir.

Traitez NULL correctement

NULL doit être traité comme une "valeur inexistante", qui est la signification de NULL. La variable ne peut pas être classée comme existante en PHP car on ne lui a pas dit quel type d'entité elle essaie d'être. Il peut aussi ne pas exister, donc PHP dit simplement "Très bien, ce n'est pas parce que ça ne sert à rien de toute façon et NULL est ma façon de dire cela".

Une dispute

Discutons maintenant. "Mais NULL, c'est comme dire 0 ou FALSE ou ''.

Wrong, 0-FALSE- '' sont toujours classés comme des valeurs vides, mais ils SONT spécifiés comme un type de valeur ou une réponse prédéterminée à une question. FALSE est la réponse à oui ou non '', est la réponse au titre que quelqu'un a soumis, et 0 est la réponse à la quantité ou au temps, etc. Ils SONT définis comme un type de réponse / résultat qui les rend valides comme étant définis.

NULL est juste aucune réponse quoi que ce soit, il ne nous dit pas oui ou non et il ne nous dit pas l'heure et il ne nous dit pas qu'une chaîne vide a été soumise. C'est la logique de base pour comprendre NULL.

Résumé

Il ne s'agit pas de créer des fonctions farfelues pour contourner le problème, il s'agit simplement de changer la façon dont votre cerveau regarde NULL. Si c'est NULL, supposez que ce n'est rien. Si vous prédéfinissez des variables, prédéfinissez-les sur 0, FALSE ou "" selon le type d'utilisation que vous prévoyez pour elles.

N'hésitez pas à citer ceci. C'est hors de ma tête logique :)


5
Très bonne réponse. Tant de fois, je vois des gens se déchaîner sur la façon dont ils détestent telle ou telle caractéristique d'une langue. Mais ils semblent supposer que "si cela ne le fait pas à MA manière, alors c'est cassé." Oui, il y a de mauvaises décisions de conception. Mais il y a aussi des développeurs très proches d'esprit!
curtisdf

23
Il y a une énorme différence entre la variable non définie et la variable === null. L'un n'existe pas, l'autre a la valeur nulle. Les arguments selon lesquels null signifie aucune valeur ne sont tout simplement pas vrais. Null EST UNE VALEUR de type null. C'est une valeur parfaitement valide et il n'y a aucune raison pour que php la traite comme une valeur inexistante, ce qu'il fait malheureusement. Ce serait OK, si les variables non existantes étaient nulles et que chaque variable existante n'était pas nulle et que l'attribution de null dans la variable la désarmerait. Mais il existe BEAUCOUP de situations dans lesquelles les fonctions renvoient null comme valeur réelle. Ensuite, nous sommes foutus, car il n'y a pas de moyen sanglant de le tester.
enrey

2
Je sais que nous ne sommes "pas censés" vérifier l'existence des variables en php, bon sang, il n'y a même pas de véritable moyen de le vérifier. Je ne vais pas écrire de code qui en dépend, car ce n'est pas possible en php. C'est une limitation de php. Il y a clairement une différence entre les variables non définies et nulles, mais php ne fournit aucun moyen de les distinguer. Pourtant, beaucoup de méta-fonctionnalités en dépendent en interne: la lecture de var inexistante produit un avis, isset($a['x'])vous dira false si elle xest nulle, mais elle apparaîtra dans count($a).. compactfonctionnera sur toutes les variables d'ensemble, y compris nulls, et ainsi de suite.
enrey

3
Cette réponse est imparfaite d'une manière majeure: dans la programmation OO, null est le choix logique pour signifier "aucun objet". Par exemple, dans des circonstances non exceptionnelles lorsqu'une fonction peut renvoyer un objet ou aucun objet, null est le choix évident. Techniquement en PHP, false ou toute autre valeur considérée comme false dans un contexte booléen pourrait être utilisée, mais vous perdez alors une certaine pureté sémantique. Ainsi, null est une valeur parfaitement raisonnable pour initialiser une variable qui devrait éventuellement contenir un objet, car elle est pertinente par rapport à ce qu'elle est censée devenir.
chazomaticus

3
Tant que PHP renvoie des erreurs pour des variables non définies, mais pas pour null, il y a une différence. Si null et undefined étaient vraiment le même concept, alors PHP devrait supposer que les variables par défaut non définies / non déclarées sont nulles et ne jamais lancer d'erreur, mais personne ne le veut car c'est un cauchemar de développement. Null et undefined peuvent ne pas être vraiment différents dans le contexte de la sémantique des valeurs, mais ils sont très différents lorsqu'il s'agit d'écrire du code clair et déboguable.
Chris Middleton

9

L'existence des propriétés de l'objet peut être vérifiée par property_exists

Exemple d'un test unitaire:

function testPropertiesExist()
{
    $sl =& $this->system_log;
    $props = array('log_id',
                   'type',
                   'message',
                   'username',
                   'ip_address',
                   'date_added');

    foreach($props as $prop) {
        $this->assertTrue(property_exists($sl, $prop),
                           "Property <{$prop}> exists");
    }
}

4

En complément de la discussion de greatbigmassive sur ce que signifie NULL , considérez ce que signifie réellement «l'existence d'une variable».

Dans de nombreux langages, vous devez déclarer explicitement chaque variable avant de l'utiliser ; cela peut déterminer son type, mais plus important encore, il déclare sa portée . Une variable "existe" partout dans sa portée, et nulle part en dehors - que ce soit une fonction entière, ou un seul "bloc".

Dans son champ d'application, une variable attribue une signification à une étiquette que vous, le programmeur, avez choisie. En dehors de sa portée, cette étiquette n'a pas de sens (si vous utilisez la même étiquette dans une portée différente n'est fondamentalement pas pertinente).

En PHP, les variables n'ont pas besoin d'être déclarées - elles prennent vie dès que vous en avez besoin. Lorsque vous écrivez dans une variable pour la première fois, PHP alloue une entrée en mémoire pour cette variable. Si vous lisez une variable qui n'a pas actuellement d'entrée, PHP considère que cette variable a la valeur NULL.

Cependant, les détecteurs automatiques de qualité de code vous avertiront généralement si vous utilisez une variable sans "l'initialiser" au préalable. Premièrement, cela aide à détecter les fautes de frappe, telles que l'attribution à $thingIdmais la lecture à partir de $thing_id; mais deuxièmement, cela vous oblige à considérer la portée sur laquelle cette variable a un sens, tout comme le ferait une déclaration.

Tout code qui se soucie de savoir si une variable "existe" fait partie de la portée de cette variable - qu'elle ait été initialisée ou non, vous, en tant que programmeur, avez donné une signification à cette étiquette à ce point du code. Puisque vous l'utilisez, il doit dans un certain sens "exister", et s'il existe, il doit avoir une valeur implicite; en PHP, cette valeur implicite est null.

En raison du fonctionnement de PHP, il est possible d'écrire du code qui traite l'espace de noms des variables existantes non pas comme une portée d'étiquettes auxquelles vous avez donné un sens, mais comme une sorte de magasin clé-valeur. Vous pouvez, par exemple, exécuter du code comme ceci: $var = $_GET['var_name']; $$var = $_GET['var_value'];. Ce n'est pas parce que vous le pouvez que c'est une bonne idée.

Il s'avère que PHP a une bien meilleure façon de représenter les magasins clé-valeur, appelés tableaux associatifs. Et bien que les valeurs d'un tableau puissent être traitées comme des variables, vous pouvez également effectuer des opérations sur le tableau dans son ensemble. Si vous avez un tableau associatif, vous pouvez tester s'il contient une clé à l'aide de array_key_exists().

Vous pouvez également utiliser des objets de la même manière, en définissant dynamiquement les propriétés, auquel cas vous pouvez les utiliser property_exists()exactement de la même manière. Bien sûr, si vous définissez une classe, vous pouvez déclarer les propriétés qu'il a - vous pouvez même choisir entre public, privateetprotected la portée.

Bien qu'il existe une différence technique entre une variable (par opposition à une clé de tableau ou à une propriété d'objet) qui n'a pas été initialisée (ou qui a été explicitement unset()) et celle dont la valeur est null, tout code qui considère cette différence comme significative utilise des variables d'une manière qu'elles ne sont pas censées être utilisées.


1
Très bons points, mais pas exactement une réponse à la question.
chazomaticus

1
A la question explicite "Comment sommes-nous censés vérifier de manière fiable l'existence d'une variable en PHP?" ma réponse est "vous n'êtes pas, et voici pourquoi". Cette réponse et celle de greatbigmassive répondent également à la question implicite "pourquoi isset()se comporte-t-il ainsi?".
IMSoP

"Si vous lisez à partir d'une variable qui n'a actuellement pas d'entrée, PHP considère que cette variable a la valeur NULL." C'est faux. Une variable indéfinie est simplement indéfinie. Il peut retourner null lorsque vous essayez d'y accéder, mais ce n'est pas pertinent.
Hugo Zink

@HugoZink Sans rapport avec quoi? Tout test que vous effectuez sur la valeur d'une variable non définie vous indiquera que la valeur est null. La question de savoir si cette valeur existe avant que vous la regardiez est une question pour les philosophes, mais en ce qui concerne tout comportement observable, la valeur est cohérente null.
IMSoP

3

issetvérifie si la variable est définie et, le cas échéant, si sa valeur n'est pas NULL. Cette dernière partie n'entre (à mon avis) pas dans le cadre de cette fonction. Il n'existe pas de solution de contournement décente pour déterminer si une variable est NULL car elle n'est pas définie ou parce qu'elle est explicitement définie sur NULL .

Voici une solution possible:

$e1 = error_get_last();
$isNULL = is_null(@$x);
$e2 = error_get_last();
$isNOTSET = $e1 != $e2;
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

Une autre solution de contournement consiste à sonder la sortie de get_defined_vars():

$vars = get_defined_vars();
$isNOTSET = !array_key_exists("x", $vars);
$isNULL = $isNOTSET ? true : is_null($x);
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

2

Je ne suis pas d'accord avec votre raisonnement sur NULL , et dire que vous devez changer votre état d'esprit à propos de NULL est juste bizarre.

Je pense que isset () n'a pas été conçu correctement, isset () devrait vous dire si la variable a été définie et il ne devrait pas être concerné par la valeur réelle de la variable.

Que faire si vous vérifiez les valeurs renvoyées par une base de données et que l'une des colonnes a une valeur NULL, vous voulez toujours savoir si elle existe même si la valeur est NULL ... non, ne faites pas confiance à isset () ici.

également

$a = array ('test' => 1, 'hello' => NULL);

var_dump(isset($a['test']));   // TRUE
var_dump(isset($a['foo']));    // FALSE
var_dump(isset($a['hello']));  // FALSE

isset () aurait dû être conçu pour fonctionner comme ceci:

if(isset($var) && $var===NULL){....

de cette façon, nous laissons au programmeur le soin de vérifier les types et de ne pas laisser à isset () le soin de supposer que ce n'est pas là parce que la valeur est NULL - c'est juste une conception stupide


Votre exemple ne vérifie pas l'existence d'une variable, mais d'une clé de tableau. Une solution à cela existe, sous la forme de array_key_exists. Vous ne devriez jamais être dans une situation où vous ne savez pas au moment de l'exécution si une variable réelle existe.
IMSoP

@chazomaticus Eh bien, vous ne devriez jamais être dans une situation où register_globals est activé, donc je maintiens cette déclaration.
IMSoP

Oh, je suis d'accord. Pourtant, tout le monde ne peut pas contrôler où son code est déployé. Il est utile d'avoir des informations pour chaque situation, que ce soit la façon dont les choses «devraient» être ou non.
chazomaticus

@chazomaticus Si votre problème est register_globals, alors votre réponse n'est pas un changement de isset(). Le manuel PHP mentionne "c'est généralement une bonne pratique de programmation d'initialiser d'abord les variables", ce qui résout register_globalsau moment de la conception plutôt qu'à l'exécution. Il y a aussi une entrée de FAQ donnant une unregister_globals()fonction pour le traiter au moment de l'exécution.
IMSoP

2

Je vais ajouter un petit deux cents à cela. L'une des raisons pour lesquelles ce problème est déroutant est que ce scénario semble renvoyer le même résultat avec un rapport d'erreur non complet:

$a = null;
var_dump($a); // NULL
var_dump($b); // NULL

Vous pouvez supposer à partir de ce résultat que la différence entre $a = nullet ne pas définir $bdu tout n'est rien.

Rapport d'erreur de démarrage:

NULL

Notice: Undefined variable: b in xxx on line n
NULL

Remarque: il a renvoyé une erreur de variable non définie, mais la valeur de sortie de var_dumpest toujours NULL.

PHP a évidemment une capacité interne à faire la distinction entre une variable nulle et une variable non définie. Il me semble qu'il devrait y avoir une fonction intégrée pour vérifier cela.

Je pense que la réponse acceptée est bonne pour la plupart, mais si j'allais l'implémenter, j'écrirais un wrapper pour cela. Comme mentionné précédemment dans cette réponse , je dois admettre que je n'ai pas réellement rencontré de situation où cela a posé un problème. Il me semble que je me retrouve presque toujours dans un scénario où mes variables sont soit définies et définies, soit elles ne le sont pas (indéfinies, non définies, nulles, vides, etc.). Cela ne veut pas dire qu'une situation comme celle-ci ne se produira pas à l'avenir, mais comme cela semble être un problème assez unique, je ne suis pas surpris que les développeurs PHP n'aient pas pris la peine de mettre cela en place.


L'avertissement sur les variables non définies indique au programmeur qu'il a fait quelque chose de mal dans le code. En dehors du débogage (pour lequel il existe des outils en dehors du langage), il ne devrait jamais être nécessaire qu'un programme détecte un tel état, car le programmeur doit toujours savoir quelles variables il déclare.
IMSoP

1

Si j'exécute ce qui suit:

echo '<?php echo $foo; ?>' | php

J'obtiens une erreur:

PHP Notice:  Undefined variable: foo in /home/altern8/- on line 1

Si j'exécute ce qui suit:

echo '<?php if ( isset($foo) ) { echo $foo; } ?>' | php

Je ne reçois pas l'erreur.

Si j'ai une variable à définir, je fais généralement quelque chose comme ce qui suit.

$foo = isset($foo) ? $foo : null;

ou

if ( ! isset($foo) ) $foo = null;

De cette façon, plus loin dans le script, je peux utiliser en toute sécurité $ foo et savoir qu'il "est défini", et qu'il est par défaut à null. Plus tard, je peux if ( is_null($foo) ) { /* ... */ }si j'ai besoin et savoir avec certitude que la variable existe, même si elle est nulle.

La documentation complète de l' iset lit un peu plus que ce qui a été initialement collé. Oui, il renvoie false pour une variable précédemment définie mais qui est maintenant nulle, mais il renvoie également false si une variable n'a pas encore été définie (jamais) et pour toute variable qui a été marquée comme non définie. Il note également que l'octet NULL ("\ 0") n'est pas considéré comme nul et retournera vrai.

Déterminez si une variable est définie.

Si une variable n'a pas été définie avec unset (), elle ne sera plus définie. isset () retournera FALSE si vous testez une variable qui a été définie sur NULL. Notez également qu'un octet NULL ("\ 0") n'est pas équivalent à la constante PHP NULL.


Il a obtenu les documents de ce lien. Lisez la première phrase, deuxième paragraphe de la section description sur le lien que vous avez fourni. C'est exactement ce qu'il a cité ci-dessus.
Zoredache le

Ce n'est pas une mauvaise pratique pour les scripts simples, mais dans les projets complexes (par exemple les grands OO), cela devient irréalisable. De plus, comme je l'ai dit ci-dessus, is_null () renvoie TRUE pour les variables qui ne sont pas définies, il n'y a donc vraiment aucune raison de faire ce que vous dites, sauf pour éviter un avertissement PHP.
chazomaticus

1
Pour les projets «grands OO» bien conçus, pourquoi cela poserait-il un problème? Pourquoi auriez-vous jamais $ foo dans un corps de méthode qui n'a peut-être pas été défini avant sa première utilisation?
Beau Simensen

1

Essayez d'utiliser

unset($v)

Il semble que la seule fois où une variable n'est pas définie, c'est lorsqu'elle est spécifiquement désactivée ($ v). Il semble que votre signification d '«existence» soit différente de la définition de PHP. NULL existe certainement, il est NULL.


Je ne sais pas ce que tu veux dire. Si vous avez un tableau, avec un élément 'a', vous n'avez pas besoin de désarmer () l'élément 'b' pour que l'élément 'b' n'existe pas en PHP, il n'existe tout simplement pas. Même chose avec par exemple les variables globales, que vous pouvez considérer comme des éléments du tableau $ GLOBALS.
chazomaticus

1
Mais je suis d'accord qu'une variable avec une valeur NULL existe en fait.
chazomaticus

0

Je dois dire qu'au cours de toutes mes années de programmation PHP, je n'ai jamais rencontré de problème pour isset()renvoyer false sur une variable nulle. OTOH, j'ai rencontré des problèmes d' isset()échec sur une entrée de tableau nul - mais array_key_exists()fonctionne correctement dans ce cas.

Pour certaines comparaisons, Icon définit explicitement une variable inutilisée comme retournant &nullafin que vous utilisiez le test is-null dans Icon pour également rechercher une variable non définie. Cela facilite les choses. D'autre part, Visual BASIC a plusieurs états pour une variable qui n'a pas de valeur (Null, Empty, Nothing, ...), et vous devez souvent en rechercher plusieurs. Ceci est connu pour être une source de bogues.


0

Selon le manuel PHP pour la fonction empty (), "Détermine si une variable est considérée comme vide. Une variable est considérée comme vide SI ELLE N'EXISTE PAS ou si sa valeur est égale à FALSE. Empty () ne génère pas d'avertissement si le variable n'existe pas. " (Je souligne.) Cela signifie que la fonction empty () devrait être qualifiée de "meilleure façon de tester l'existence d'une variable en PHP", selon le titre Question.

Cependant, ce n'est pas suffisant, car la fonction empty () peut être trompée par une variable qui existe et qui est définie sur NULL.

J'interromps ma réponse précédente pour présenter quelque chose de mieux, car elle est moins encombrante que ma réponse originale (qui fait suite à cette interruption, pour comparer).

  function undef($dnc) //do not care what we receive
  { $inf=ob_get_contents();             //get the content of the buffer
    ob_end_clean();                     //stop buffering outputs, and empty the buffer
    if($inf>"")                         //if test associated with the call to this function had an output
    { if(false!==strpos($inf, "Undef"); //if the word "Undefined" was part of the output
        return true;                    //tested variable is undefined
    }
    return false;                       //tested variable is not undefined
  }

Deux simples lignes de code peuvent utiliser la fonction ci-dessus pour révéler si une variable n'est pas définie:

  ob_start();                           //pass all output messages (including errors) to a buffer
  if(undef($testvar===null))            //in this case the variable being tested is $testvar

Vous pouvez suivre ces deux lignes avec tout ce qui est approprié, comme cet exemple:

    echo("variable is undefined");
  else
    echo("variable exists, holding some value");

Je voulais mettre l'appel à ob_start () et le ($ testvar === null) à l'intérieur de la fonction, et passer simplement la variable à la fonction, mais cela ne fonctionne pas. Même si vous essayez d'utiliser "passer par référence" de la variable à la fonction, la variable DEVIENT définie et la fonction ne peut jamais détecter qu'elle était précédemment indéfinie. Ce qui est présenté ici est un compromis entre ce que je voulais faire et ce qui fonctionne réellement.

Ce qui précède implique qu'il existe un autre moyen d'éviter de toujours rencontrer le message d'erreur "Variable non définie". (L'hypothèse ici est que la prévention d'un tel message est la raison pour laquelle vous voulez tester pour voir si une variable n'est pas définie.)

   function inst(&$v) { return; }  //receive any variable passed by reference; instantiates the undefined

Appelez simplement cette fonction avant de faire quelque chose sur votre $ testvar:

   inst($testvar);                //The function doesn't affect any value of any already-existing variable

La valeur de la variable nouvellement instanciée est définie sur null, bien sûr!

(L'interruption prend fin)

Donc, après quelques études et expérimentations, voici quelque chose qui fonctionnera:

 function myHndlr($en, $es, $ef, $el)
 { global $er;
   $er = (substr($es, 0, 18) == "Undefined variable");
   return;
 }

 $er = false;
 if(empty($testvar))
 { set_error_handler("myHndlr");
   ($testvar === null);
   restore_error_handler();
 }
 if($er)  // will be 1 (true) if the tested variable was not defined.
 { ; //do whatever you think is appropriate to the undefined variable
 }

L'explication: Une variable $ er est initialisée à une valeur par défaut de "pas d'erreur". Une "fonction gestionnaire" est définie. Si $ testvar (la variable que nous voulons savoir si elle n'est pas définie ou non) réussit le test préliminaire de la fonction empty (), alors nous faisons le test plus approfondi. Nous appelons la fonction set_error_handler () pour utiliser la fonction de gestionnaire précédemment définie. Ensuite, nous faisons une simple comparaison d'identité impliquant $ testvar, QUI SI NON DÉFINI DÉCLENCHERA UNE ERREUR. La fonction de gestionnaire capture l'erreur et teste spécifiquement pour voir si la raison de l'erreur est le fait que la variable n'est pas définie. Le résultat est placé dans la variable d'information d'erreur $ er, que nous pouvons tester ultérieurement pour faire tout ce que nous voulons en sachant avec certitude si $ testvar a été défini ou non. Étant donné que nous n'avons besoin de la fonction de gestionnaire que dans ce but limité, nous restaurons la fonction de gestion des erreurs d'origine. La fonction "myHndlr" n'a besoin d'être déclarée qu'une seule fois; l'autre code peut être copié à n'importe quel endroit approprié, pour $ testvar ou toute autre variable que nous voulons tester de cette façon.


1
Si l'intention est d'éviter un avertissement indiquant que vos variables n'ont pas été déclarées, la solution est de corriger votre code pour les déclarer correctement. Votre instfonction est fondamentalement comme l' @opérateur de suppression d'erreurs: "Je sais que je fais quelque chose de mal ici, mais je veux juste que ce message disparaisse, sans changer en fait le fonctionnement de mon code de quelque manière que ce soit".
IMSoP

Les méthodes de détection, par contre, sont ingénieuses, mais je suis toujours fermement convaincue que vous ne devriez jamais avoir d'autre utilité que de faire écho aux messages d'avertissement mêmes qu'ils captent. (Vous devriez probablement préciser que votre version de mise en mémoire tampon de sortie nécessite que error_reporting soit défini sur high et que display_errors soit activé.)
IMSoP

0

Je pense que la seule solution complète est de signaler les notifications avec

error_reporting(E_ALL); // Enables E_NOTICE

Mais vous devrez corriger tous les avis générés par des variables non définies, des constantes, des clés de tableau, des propriétés de classe, entre autres. Une fois que vous avez fait cela, vous n'aurez plus à vous soucier de la différence entre les variables nulles et non déclarées, et l'ambiguïté disparaît.

L'activation de la notification des notifications peut ne pas être une bonne alternative dans toutes les situations, mais il existe de bonnes raisons de l'activer:

Pourquoi devrais-je corriger les erreurs E_NOTICE?

Dans mon cas, c'était plus d'un an à travailler dans un projet sans cela, mais on avait l'habitude de faire attention à la déclaration des variables, donc la transition était rapide.


0

Le seul moyen de savoir si une variable est définie dans la portée actuelle ( $GLOBALSn'est pas digne de confiance) est array_key_exists( 'var_name', get_defined_vars() ).


1
Je pense que c'est ce que beaucoup d'autres ont dit auparavant, ou est-ce que je me trompe?
Stephan Vierkant

-1

Je préfère utiliser pas vide comme meilleure méthode pour vérifier l'existence d'une variable qui a) existe et que b) n'est pas nul.

if (!empty($variable)) do_something();

2
empty()ne vérifie pas si la variable est nulle, il vérifie si elle est fausse-y, par exemple pas l' un des ""(une chaîne vide), 0(0 comme un nombre entier), 0.0(0 en tant que flotteur), "0"(0 sous forme de chaîne) NULL, FALSE, array()(un tableau vide) et $var;(une variable déclarée, mais sans valeur). Supposons que vous ayez un champ radio obligatoire dans un formulaire avec deux entrées avec les valeurs 0et 1. Si vous utilisez empty()pour la validation et que l'utilisateur sélectionne celui- 0ci, vous feriez par inadvertance une erreur "le champ obligatoire ne peut pas être vide". Voir le manuel php.net/manual/en/function.empty.php
Halil Özgür
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.