Comment éviter isset () et empty ()

98

J'ai plusieurs applications plus anciennes qui lancent beaucoup de messages "xyz is undefined" et "undefined offset" lors de l'exécution sur le niveau d'erreur E_NOTICE, car l'existence de variables n'est pas explicitement vérifiée à l'aide de isset()et consorts.

J'envisage de travailler avec eux pour les rendre compatibles E_NOTICE, car les avis sur les variables ou les décalages manquants peuvent sauver des vies, il peut y avoir des améliorations mineures des performances à gagner, et c'est globalement la manière la plus propre.

Cependant, je n'aime pas ce qu'inflige des centaines de isset() empty()et array_key_exists()s à mon code. Il se gonfle, devient moins lisible, sans rien gagner en valeur ou en sens.

Comment structurer mon code sans excès de contrôles de variables, tout en étant compatible E_NOTICE?

Pekka
la source
6
Je suis complètement d'accord. C'est pourquoi j'aime tellement Zend Framework, le module de requête y est très bon. Si je travaille sur une petite application, je code généralement une classe de requête simple avec les méthodes magiques __set et __get qui fonctionnent de manière similaire à la requête de ZF. De cette façon, j'évite toutes les occurrences de isset et vide dans mon code. De cette façon, tout ce que vous devez utiliser est soit if (count ($ arr)> 0) sur les tableaux avant de les parcourir et if (null! == $ variable) à quelques endroits critiques. Cela a l'air beaucoup plus propre.
Richard Knop

Réponses:

128

Pour ceux qui sont intéressés, j'ai développé ce sujet dans un petit article, qui fournit les informations ci-dessous sous une forme un peu mieux structurée: The Definitive Guide To PHP's isset And empty


IMHO, vous devriez penser non seulement à rendre l'application "E_NOTICE compatible", mais à restructurer le tout. Avoir des centaines de points dans votre code qui essaient régulièrement d'utiliser des variables inexistantes ressemble à un programme plutôt mal structuré. Essayer d'accéder à des variables inexistantes ne devrait jamais arriver, d'autres langages hésitent à cela au moment de la compilation. Le fait que PHP vous permette de le faire ne signifie pas que vous devriez le faire.

Ces avertissements sont là pour vous aider , pas pour vous ennuyer. Si vous recevez un avertissement "Vous essayez de travailler avec quelque chose qui n'existe pas!" , votre réaction devrait être "Oups, mon mauvais, laissez-moi résoudre ce problème dès que possible." Sinon, comment allez-vous faire la différence entre "des variables qui fonctionnent très bien et non définies" et un code honnêtement erroné qui peut conduire à de graves erreurs ? C'est aussi la raison pour laquelle vous développez toujours, toujours avec le rapport d'erreur passé à 11 et continuez à brancher votre code jusqu'à ce que pas un seulNOTICEest émis. La désactivation des rapports d'erreurs est réservée aux environnements de production, afin d'éviter les fuites d'informations et de fournir une meilleure expérience utilisateur, même en cas de code bogué.


Élaborer:

Vous aurez toujours besoin issetou emptyquelque part dans votre code, le seul moyen de réduire leur occurrence est d'initialiser correctement vos variables. Selon la situation, il existe différentes manières de procéder:

Arguments de fonction:

function foo ($bar, $baz = null) { ... }

Il n'est pas nécessaire de vérifier si $barou $bazsont définis à l'intérieur de la fonction car vous venez de les définir, tout ce dont vous avez besoin est de savoir si leur valeur est évaluée à trueou false(ou quoi que ce soit d'autre).

Variables régulières n'importe où:

$foo = null;
$bar = $baz = 'default value';

Initialisez vos variables en haut d'un bloc de code dans lequel vous allez les utiliser. Cela résout le !issetproblème, garantit que vos variables ont toujours une valeur par défaut connue, donne au lecteur une idée de ce sur quoi le code suivant fonctionnera et sert ainsi également de sorte d'auto-documentation.

Tableaux:

$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);

La même chose que ci-dessus, vous initialisez le tableau avec les valeurs par défaut et les écrasez par les valeurs réelles.

Dans les cas restants, disons un modèle où vous produisez des valeurs qui peuvent ou non être définies par un contrôleur, il vous suffira de vérifier:

<table>
    <?php if (!empty($foo) && is_array($foo)) : ?>
        <?php foreach ($foo as $bar) : ?>
            <tr>...</tr>
        <?php endforeach; ?>
    <?php else : ?>
        <tr><td>No Foo!</td></tr>
    <?php endif; ?>
</table>

Si vous vous retrouvez à utiliser régulièrement array_key_exists, vous devriez évaluer à quoi vous l'utilisez. Le seul moment où cela fait une différence, c'est ici:

$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true

Comme indiqué ci-dessus cependant, si vous initialisez correctement vos variables, vous n'avez pas besoin de vérifier si la clé existe ou non, car vous le savez. Si vous obtenez le tableau à partir d' une source externe, la valeur sera très probablement pas nullmais '', 0, '0', falseou quelque chose comme ça, soit une valeur que vous pouvez évaluer avec issetou empty, selon votre intention. Si vous définissez régulièrement une clé de tableau sur nullet que vous voulez qu'elle ait une signification autre que false, c'est-à-dire si, dans l'exemple ci-dessus, les résultats différents de issetet array_key_existsfont une différence dans la logique de votre programme, vous devriez vous demander pourquoi. La simple existence d'une variable ne devrait pas être importante, seule sa valeur devrait être importante. Si la clé est un indicateur true/ false, utiliseztrueou falsenon null. La seule exception à cela serait les bibliothèques tierces qui veulent nullsignifier quelque chose, mais comme nullc'est si difficile à détecter en PHP, je n'ai pas encore trouvé de bibliothèque qui le fasse.

déceler
la source
4
C'est vrai, mais la plupart des tentatives d'accès qui échouent sont du type if ($array["xyz"])plutôt que isset()ou array_key_exists()que je trouve quelque peu légitimes, certainement pas des problèmes structurels (corrigez-moi si je me trompe). L'ajout array_key_exists()me semble un terrible gaspillage.
Pekka
9
Je ne peux penser à aucun cas où j'utiliserais array_key_existsau lieu d'un simple isset($array['key'])ou !empty($array['key']). Bien sûr, les deux ajoutent 7 ou 8 caractères à votre code, mais je n'appellerais pas cela un problème. Cela aide également à clarifier votre code: if (isset($array['key']))signifie que cette variable est en effet facultative et peut être absente, alors if ($array['key'])que signifie simplement "si vrai". Si vous recevez un avis pour ce dernier, vous savez que votre logique est foirée quelque part.
deceze
6
Je crois que la différence entre isset () et array_key_exists () est que ce dernier retournera true si la valeur est NULL. isset () ne le fera pas.
Htbaa
1
C'est vrai, mais je ne pouvais pas penser à un cas d'utilisation sensé où je devais faire la différence entre une variable inexistante et une clé d'ensemble dont la valeur est nulle. Si la valeur est évaluée à FALSE, la distinction doit être sans différence. :)
deceze
1
Les clés de tableau sont certainement plus ennuyeuses que les variables non définies. Mais si vous ne savez pas si un tableau contient une clé ou non, cela signifie que vous n'avez pas défini le tableau vous - même ou que vous le tirez d'une source que vous ne contrôlez pas. Aucun des deux scénarios ne devrait se produire très souvent; et si cela arrive, vous avez toutes les raisons de vérifier si le tableau contient ce que vous pensez qu'il fait. C'est une mesure de sécurité IMO.
kijin
37

Écrivez simplement une fonction pour cela. Quelque chose comme:

function get_string($array, $index, $default = null) {
    if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
        return get_magic_quotes_gpc() ? stripslashes($value) : $value;
    } else {
        return $default;
    }
}

que vous pouvez utiliser comme

$username = get_string($_POST, 'username');

Faites la même chose pour des choses triviales comme get_number(), get_boolean(), get_array()et ainsi de suite.

BalusC
la source
5
Cela semble bon, et vérifie également magic_quotes. Agréable!
Pekka
Excellente fonction! Merci beaucoup pour le partage.
Mike Moore
3
Notez que $ _POST ['quelque chose'] peut retourner un tableau, par exemple des entrées avec <input name="something[]" />. Cela provoquerait une erreur (car le trim ne peut pas être appliqué aux tableaux) en utilisant le code ci-dessus, dans ce cas, il faut utiliser is_stringet éventuellement strval. Ce n'est pas simplement un cas où l'on devrait utiliser l'un get_arrayou l'autre puisque l'entrée utilisateur (malveillante) peut-être n'importe quoi et l'analyseur d'entrée utilisateur ne devrait jamais générer d'erreur de toute façon.
Ciantic
1
J'utilise le même type de fonction mais défini comme tel: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ item: $ default; } L'avantage de cette fonction est que vous pouvez l'appeler avec des tableaux, des variables et des objets. L'inconvénient est que l'élément $ est initialisé (à null) par la suite s'il ne l'était pas.
Mat
Vous devriez désactiver les guillemets magiques globalement, au lieu de les traiter en une seule fonction. Il existe de nombreuses sources sur Internet expliquant les citations magiques.
Kayla
13

Je pense que l'un des meilleurs moyens de résoudre ce problème est d'accéder aux valeurs des tableaux GET et POST (COOKIE, SESSION, etc.) via une classe.

Créez une classe pour chacun de ces tableaux et déclarez les méthodes __getet __set( surcharge ). __getaccepte un argument qui sera le nom d'une valeur. Cette méthode doit vérifier cette valeur dans le tableau global correspondant, en utilisant isset()ou empty()et renvoyer la valeur si elle existe ou null(ou une autre valeur par défaut) sinon.

Après cela, vous pouvez accéder en toute confiance aux valeurs du tableau de cette manière: $POST->usernameet faire toute validation si nécessaire sans utiliser de isset()s ou de empty()s. Si usernamen'existe pas dans le tableau global correspondant, alors nullsera retourné, donc aucun avertissement ou avis ne sera généré.

Jamol
la source
1
C'est une excellente idée, et je suis prêt à restructurer le code. +1
Pekka
Malheureusement, vous ne pourrez pas rendre ces instances super globales à moins que vous ne les affectiez à $ _GET ou $ _POST, ce qui serait assez laid. Mais vous pouvez utiliser des classes statiques bien sûr ...
ThiefMaster
1
Vous ne pouvez pas utiliser des getters et des setters sur des "classes statiques". et écrire une classe par variable est une mauvaise pratique car cela implique une duplication de code, ce qui est mauvais. Je ne pense pas que cette solution soit la plus adéquate.
Mat
Un membre statique public d'une classe agit comme un superglobal, c'est-à-dire: HTTP :: $ POST-> username, où vous instanciez HTTP :: $ POST à ​​un moment donné avant son utilisation, c'est-à-dire. Classe HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow
6

Cela ne me dérange pas d'utiliser la array_key_exists()fonction. En fait, je préfère utiliser cette fonction spécifique plutôt que de me fier à des fonctions de piratage qui peuvent changer leur comportement à l'avenir comme emptyetisset (barré pour éviter les susceptibilités ).


Cependant, j'utilise une fonction simple qui est utile dans ce cas, et dans d'autres situations pour traiter les index de tableau :

function Value($array, $key, $default = false)
{
    if (is_array($array) === true)
    {
        settype($key, 'array');

        foreach ($key as $value)
        {
            if (array_key_exists($value, $array) === false)
            {
                return $default;
            }

            $array = $array[$value];
        }

        return $array;
    }

    return $default;
}

Disons que vous avez les tableaux suivants:

$arr1 = array
(
    'xyz' => 'value'
);

$arr2 = array
(
    'x' => array
    (
        'y' => array
        (
            'z' => 'value',
        ),
    ),
);

Comment obtenez-vous la «valeur» des tableaux? Facile:

Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');

Nous avons déjà couvert des tableaux unidimensionnels et multidimensionnels, que pouvons-nous faire d'autre?


Prenez le morceau de code suivant par exemple:

$url = '/programming/1960509';
$domain = parse_url($url);

if (is_array($domain) === true)
{
    if (array_key_exists('host', $domain) === true)
    {
        $domain = $domain['host'];
    }

    else
    {
        $domain = 'N/A';
    }
}
else
{
    $domain = 'N/A';
}

Assez ennuyeux n'est-ce pas? Voici une autre approche utilisant la Value()fonction:

$url = '/programming/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');

Comme exemple supplémentaire, prenez la RealIP()fonction pour un test:

$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));

Neat, hein? ;)

Alix Axel
la source
6
"S'appuyer sur des fonctions de piratage susceptibles de modifier leur comportement à l'avenir"?! Désolé, mais c'est à peu près la chose la plus ridicule que j'ai entendue de toute la semaine. Tout d' abord, issetet emptysont des constructions de langage , pas de fonctions. Deuxièmement, si des fonctions / constructions de langage de base de la bibliothèque changent de comportement, vous risquez ou non d'être vissé. Et si array_key_existschange son comportement? La réponse est que non, tant que vous l'utilisez tel que documenté. Et issetest documenté pour être utilisé exactement ainsi. Les pires fonctions des cas sont obsolètes sur une version majeure ou deux. Le syndrome du NIH est mauvais!
deceze
Je suis désolé deceze, mais tout d'abord le hack est en italique au cas où vous ne l'auriez pas remarqué. =) Deuxièmement, vous voulez dire qu'il ne faut pas se fier array_key_exists()pour vérifier si une clé existe dans un tableau ?! array_key_exists()a été créé exactement pour cela , je m'appuie plutôt dessus pour cela que isset()et spécialement empty()dont la description officielle est: "déterminer si une variable est vide", ne mentionne rien si elle existe réellement. Votre commentaire et votre vote négatif sont l'un des plus ridicules dont j'ai été témoin de tout le mois .
Alix Axel
3
Je dis issetet emptyne sont ni plus ni moins fiables que array_key_existset peuvent faire exactement le même travail. Votre deuxième exemple long peut être écrit comme $domain = isset($domain['host']) ? $domain['host'] : 'N/A';avec juste des fonctionnalités de langage de base, aucun appel de fonction ou déclaration supplémentaire n'est nécessaire (notez que je ne préconise pas nécessairement l'utilisation de l'opérateur ternaire; o)). Pour les variables scalaires ordinaires, vous devrez toujours utiliser issetou empty, et vous pouvez les utiliser pour les tableaux exactement de la même manière. La «fiabilité» est une mauvaise raison pour ne pas le faire.
deceze
1
Vous avez fait valoir votre point de vue, même si je ne suis pas d'accord avec la plupart des choses que vous avez dites. Je pense que vous vous êtes trompé sur les cas à 90% +, par exemple, j'utilise la valeur de «0» dans les champs cachés des formulaires tout le temps. Pourtant, je pense que la solution que j'ai fournie ne mérite pas le vote défavorable et pourrait bien être d'une certaine utilité pour Pekka.
Alix Axel
2
Bien que @deceze ait un point avec les fonctions personnalisées - je prends généralement la même position - l'approche value () semble suffisamment intéressante pour que je l'examine. Je pense que la réponse et le suivi permettront à tous ceux qui tomberont dessus plus tard de se décider. +1.
Pekka
3

Je suis là avec toi. Mais les concepteurs PHP ont fait des erreurs bien plus pires que cela. À moins de définir une fonction personnalisée pour toute lecture de valeur, il n'y a aucun moyen de contourner cela.

vava
la source
1
isset () trucs. Rendre tout nul par défaut éviterait beaucoup de problèmes.
vava
2
Et qu'est-ce que c'est «tout»? Cela semble être un gaspillage pour PHP d'avoir à imaginer chaque nom de variable imaginable et à définir chacun sur NULL juste pour qu'un développeur paresseux puisse éviter de taper 5 caractères.
Lotus Notes
5
@Byron, regardez, c'est vraiment simple, beaucoup d'autres langages font ça, Ruby et Perl comme quelques exemples. VM sait si la variable a été utilisée auparavant ou non, n'est-ce pas? Il peut toujours retourner null au lieu d'échouer avec ou sans message d'erreur. Et il ne s'agit pas de 5 caractères moche, il s'agit d'écrire params["width"] = params["width"] || 5pour définir les valeurs par défaut au lieu de toutes ces absurdités avec les isset()appels.
vava
3
Désolé d'avoir ressuscité un ancien fil. Deux des pires erreurs de PHP étaient register_globalset magic_quotes. Les problèmes qu'ils suscitent font que les variables non initialisées semblent presque inoffensives en comparaison.
staticsan
3

J'utilise ces fonctions

function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }

Exemples

$y = load($x); // null, no notice

// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
  // executes only if both login and pass were in POST
  // stored in $login and $pass variables
  $authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}
Jan Turoň
la source
2
Je l'utilise aussi mais rappelez-vous que dans certains cas, vos variables seront initialisées automatiquement: par exemple load ($ array ['FOO']) créerait une clé FOO dans $ array.
Mat
2

Bienvenue dans l'opérateur de fusion nul (PHP> = 7.0.1):

$field = $_GET['field'] ?? null;

PHP dit:

L'opérateur de fusion nul (??) a été ajouté comme sucre syntaxique pour le cas courant de besoin d'utiliser un ternaire en conjonction avec isset (). Il renvoie son premier opérande s'il existe et n'est pas NULL; sinon, il renvoie son deuxième opérande.

Alexandre Thebaldi
la source
1

Créez une fonction qui retourne falsesi elle n'est pas définie et, si elle est spécifiée, falsesi elle est vide. S'il est valide, il renvoie la variable. Vous pouvez ajouter plus d'options comme indiqué dans le code ci-dessous:

<?php
function isset_globals($method, $name, $option = "") {
    if (isset($method[$name])) {    // Check if such a variable
        if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty 
        if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); }    // Check length of string -- used when checking length of textareas
        return ($method[$name]);
    } else { return false; }
}

if (!isset_globals("$_post", "input_name", "empty")) {
    echo "invalid";
} else {
    /* You are safe to access the variable without worrying about errors! */
    echo "you uploaded: " . $_POST["input_name"];
}
?>
feu du dragon
la source
0

Le logiciel ne fonctionne pas comme par magie par la grâce de Dieu. Si vous attendez quelque chose qui manque, vous devez le gérer correctement.

Si vous l'ignorez, vous créez probablement des failles de sécurité dans vos applications. Dans les langages statiques, accéder à une variable non définie n'est tout simplement pas possible. Il ne compilera ni ne plantera simplement votre application si elle est nulle.

De plus, cela rend votre application impossible à gérer et vous allez devenir fou lorsque des choses inattendues se produisent. La rigueur du langage est un must et PHP, de par sa conception, est faux à bien des égards. Cela fera de vous un mauvais programmeur si vous n'êtes pas au courant.

knoopx
la source
Je suis bien conscient des lacunes de PHP. Comme je l'ai souligné dans la question, je parle de la refonte d'anciens projets.
Pekka
D'accord. En tant que développeur PHP, il m'est assez difficile de m'aventurer dans de nouveaux langages comme Java où vous devez tout déclarer.
Dzhuneyt
0

Je ne sais pas quelle est votre définition de la lisibilité, mais l'utilisation correcte des blocs empty (), isset () et try / throw / catch est assez importante pour l'ensemble du processus.

Si votre E_NOTICE provient de $ _GET ou $ _POST, alors ils doivent être vérifiés par rapport à empty () avec tous les autres contrôles de sécurité que ces données doivent passer.

S'il provient de sources externes ou de bibliothèques, il doit être encapsulé dans try / catch.

S'il vient de la base de données, $ db_num_rows () ou son équivalent doit être vérifié.

Si cela provient de variables internes, elles doivent être correctement initialisées. Souvent, ces types d'avis proviennent de l'affectation d'une nouvelle variable au retour d'une fonction qui renvoie FALSE en cas d'échec. Ceux-ci devraient être enveloppés dans un test qui, en cas d'échec, peut soit affecter à la variable une valeur par défaut acceptable que le code peut gérer, soit lever une exception que le code peut gérer.

Ces choses allongent le code, ajoutent des blocs supplémentaires et ajoutent des tests supplémentaires, mais je ne suis pas d'accord avec vous en ce sens que je pense qu'ils ajoutent certainement une valeur supplémentaire.

Mlutz
la source
-2

Qu'en est-il de l'utilisation de l' @opérateur?

Par exemple:

if(@$foo) { /* Do something */ }

Vous pouvez dire que c'est mauvais parce que vous n'avez aucun contrôle sur ce qui se passe "à l'intérieur" de $ foo (si c'est un appel de fonction qui contient une erreur PHP par exemple), mais si vous n'utilisez cette technique que pour les variables, c'est équivalent à:

if(isset($foo) && $foo) { /* ... */ }
Tapis
la source
if(isset($foo))est assez en fait. Il retournera TRUEsi l'expression est évaluée à TRUE.
Dzhuneyt
2
@ ColorWP.com il retournera également vrai si l'expression est évaluée à faux.
Jon Hulka
Vous ne devez utiliser le paramètre @ (pour ignorer l'avis) que sur du code qui n'est pas vraiment en cours de développement, ou sur du code à usage unique ou une solution rapide sur des projets existants, que vous ne voulez montrer à personne d'autre. Mais c'est une solution de contournement courante pour un piratage rapide.
rubo77