Come evitare isset () e empty ()


98

Ho diverse applicazioni meno recenti che lanciano molti messaggi "xyz è undefined" e "undefined offset" quando sono in esecuzione sul livello di errore E_NOTICE, perché l'esistenza delle variabili non viene verificata esplicitamente utilizzando isset()e consorts.

Sto valutando di elaborarli per renderli compatibili con E_NOTICE, poiché gli avvisi sulle variabili mancanti o sugli offset possono essere salvavita, potrebbero esserci alcuni piccoli miglioramenti delle prestazioni da ottenere, ed è nel complesso il modo più pulito.

Tuttavia, non mi piace quello che infliggere centinaia di isset() empty()e array_key_exists()s fa al mio codice. Si gonfia, diventa meno leggibile, senza guadagnare nulla in termini di valore o significato.

Come posso strutturare il mio codice senza un eccesso di controlli variabili, pur essendo compatibile con E_NOTICE?


6
Sono completamente d'accordo. Ecco perché mi piace così tanto Zend Framework, il modulo di richiesta è molto buono lì. Se sto lavorando su qualche piccola app, di solito codifico una semplice classe di richiesta con metodi magici __set e __get che funzionano in modo simile alla richiesta di ZF. In questo modo evito tutte le occorrenze di isset e empty nel mio codice. In questo modo tutto ciò che devi usare è if (count ($ arr)> 0) sugli array prima di iterare su di essi e if (null! == $ variabile) in alcuni punti critici. Sembra molto più pulito.
Richard Knop

Risposte:


128

Per coloro che sono interessati, ho ampliato questo argomento in un piccolo articolo, che fornisce le seguenti informazioni in una forma strutturata un po 'meglio: The Definitive Guide To PHP's isset And empty


IMHO dovresti pensare non solo a rendere l'app "compatibile E_NOTICE", ma a ristrutturare il tutto. Avere centinaia di punti nel codice che tentano regolarmente di utilizzare variabili inesistenti suona come un programma strutturato piuttosto male. Il tentativo di accedere a variabili inesistenti non dovrebbe mai accadere, altri linguaggi esitano in questo momento della compilazione. Il fatto che PHP ti permetta di farlo non significa che dovresti.

Questi avvertimenti sono lì per aiutarti , non per infastidirti. Se ricevi un avviso "Stai cercando di lavorare con qualcosa che non esiste!" , la tua reazione dovrebbe essere "Oops, colpa mia, lasciami risolvere il problema il prima possibile." In quale altro modo saprai la differenza tra "variabili che funzionano bene indefinite" e codice onestamente sbagliato che può portare a gravi errori ? Questo è anche il motivo per cui sviluppi sempre, sempre , con la segnalazione degli errori impostata su 11 e continui a collegare il tuo codice fino a quando non un singoloNOTICEviene rilasciato. La disattivazione della segnalazione degli errori è solo per gli ambienti di produzione, per evitare la perdita di informazioni e fornire una migliore esperienza utente anche di fronte a codice difettoso.


Elaborare:

Avrai sempre bisogno isseto emptyda qualche parte nel tuo codice, l'unico modo per ridurne l'occorrenza è inizializzare correttamente le tue variabili. A seconda della situazione ci sono diversi modi per farlo:

Argomenti della funzione:

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

Non è necessario controllare se $baro $bazsono impostati all'interno della funzione perché li hai semplicemente impostati, tutto ciò di cui devi preoccuparti è se il loro valore restituisce trueo false(o qualsiasi altra cosa).

Variabili regolari ovunque:

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

Inizializza le tue variabili all'inizio di un blocco di codice in cui le userai. Ciò risolve il !issetproblema, garantisce che le variabili abbiano sempre un valore predefinito noto, dà al lettore un'idea di ciò su cui funzionerà il codice seguente e quindi serve anche come una sorta di auto-documentazione.

Array:

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

Come sopra, stai inizializzando l'array con i valori predefiniti e sovrascriverli con i valori effettivi.

Negli altri casi, diciamo un modello in cui stai emettendo valori che possono o non possono essere impostati da un controller, dovrai solo controllare:

<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>

Se ti array_key_existsaccorgi di usarlo regolarmente , dovresti valutare per cosa lo stai usando. L'unica volta che fa la differenza è qui:

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

Come affermato sopra, tuttavia, se stai inizializzando correttamente le tue variabili, non è necessario controllare se la chiave esiste o meno, perché sai che lo fa. Se stai ricevendo l'array da una sorgente esterna, il valore sarà molto probabilmente non sarà null, ma '', 0, '0', falseo qualcosa di simile, cioè un valore che si può valutare con isseto empty, a seconda del vostro intento. Se si imposta regolarmente una chiave di matrice su nulle si desidera che abbia un significato diverso false, cioè se nell'esempio precedente i risultati diversi issete array_key_existsfanno la differenza per la logica del programma, è necessario chiedersi il motivo. La semplice esistenza di una variabile non dovrebbe essere importante, solo il suo valore dovrebbe essere di conseguenza. Se la chiave è un flag true/ false, usatrueoppure falseno null. L'unica eccezione a questo sarebbe le librerie di terze parti che vogliono nullsignificare qualcosa, ma poiché nullè così difficile da rilevare in PHP, devo ancora trovare alcuna libreria che lo faccia.


4
Vero, ma la maggior parte in mancanza di tentativi di accesso sono lungo le linee di if ($array["xyz"])posto di isset()o array_key_exists()che trovo un po 'i problemi legittimo, non certo strutturale (correggetemi se sbaglio). L'aggiunta mi array_key_exists()sembra solo un terribile spreco.
Pekka

9
Non riesco a pensare a nessun caso in cui userei array_key_existsinvece di un semplice isset($array['key'])o!empty($array['key']) . Certo, entrambi aggiungono 7 o 8 caratteri al codice, ma difficilmente lo definirei un problema. Aiuta anche a chiarire il tuo codice: if (isset($array['key']))significa che questa variabile è davvero opzionale e può essere assente, mentre if ($array['key'])significa solo "se vero". Se ricevi un avviso per quest'ultimo, sai che la tua logica è fottuta da qualche parte.
inganno

6
Credo che la differenza tra isset () e array_key_exists () sia che quest'ultimo restituirà true se il valore è NULL. isset () non lo farà.
Htbaa

1
Vero, ma non riuscivo a pensare a un caso d'uso sano in cui ho bisogno di differenziare tra una variabile inesistente e una chiave impostata il cui valore è nullo. Se il valore restituisce FALSE, la distinzione dovrebbe essere senza differenze. :)
inganno

1
Le chiavi degli array sono sicuramente più fastidiose delle variabili non definite. Ma se non siete sicuri se un array contiene una chiave o no, significa o non hai definito la matrice da soli o si sta tirando da una fonte che non si controlla. Nessuno degli scenari dovrebbe accadere molto spesso; e se succede, hai tutte le ragioni per controllare se l'array contiene ciò che pensi che contenga. È una misura di sicurezza IMO.
kijin

37

Basta scrivere una funzione per questo. Qualcosa di simile a:

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;
    }
}

che puoi usare come

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

Fai lo stesso per cose banali come get_number() , get_boolean(), get_array()e così via.


5
Questo sembra buono e fa anche il controllo di magic_quotes. Bello!
Pekka

Grande funzione! Grazie mille per la condivisione.
Mike Moore

3
Si noti che $ _POST ['qualcosa'] può restituire un array, ad esempio input con <input name="something[]" />. Ciò causerebbe un errore (poiché il trim non può essere applicato agli array) utilizzando il codice sopra, in questo caso si dovrebbe usare is_stringe possibilmente strval. Questo non è semplicemente un caso in cui si dovrebbe usare get_arrayo poiché l'input dell'utente (dannoso) forse qualsiasi cosa e il parser dell'input dell'utente non dovrebbe mai generare errori comunque.
Ciantic

1
Uso lo stesso tipo di funzione ma definita come tale: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ elemento: $ predefinito; } Il vantaggio di questa funzione è che puoi chiamarla con array, variabili e oggetti. Lo svantaggio è che $ item viene inizializzato (a null) in seguito se non lo era.
Mat

Dovresti disattivare le virgolette magiche a livello globale, invece di gestirle in 1 funzione. Ci sono molte fonti su Internet che spiegano le citazioni magiche.
Kayla

13

Credo che uno dei modi migliori per affrontare questo problema sia accedere ai valori degli array GET e POST (COOKIE, SESSION, ecc.) Tramite una classe.

Creare una classe per ciascuno di questi array e dichiarare __gete __setmetodi ( sovraccarico ). __getaccetta un argomento che sarà il nome di un valore. Questo metodo dovrebbe controllare questo valore nell'array globale corrispondente, utilizzando isset()o empty()e restituire il valore se esiste onull (o qualche altro valore predefinito) altrimenti.

Dopodiché puoi accedere con sicurezza ai valori degli array in questo modo: $POST->usernameed eseguire qualsiasi convalida, se necessario, senza utilizzare isset()s o empty()s. Se usernamenon esiste nell'array globale corrispondente, nullverrà restituito, quindi non verranno generati avvisi o notifiche.


1
Questa è una grande idea e qualcosa per cui sono pronto a ristrutturare il codice. +1
Pekka

Sfortunatamente non sarai in grado di rendere queste istanze superglobali a meno che non le assegni a $ _GET o $ _POST, il che sarebbe piuttosto brutto. Ma potresti usare classi statiche ovviamente ...
ThiefMaster

1
Non è possibile utilizzare getter e setter su "classi statiche". e scrivere una classe per variabile è una cattiva pratica in quanto implica la duplicazione del codice, il che è male. Non credo che questa soluzione sia la più adeguata.
Mat

Un membro statico pubblico di una classe si comporta come un superglobale, ad esempio: HTTP :: $ POST-> nomeutente, dove si istanzia HTTP :: $ POST a un certo punto prima del suo utilizzo, ad es. Classe HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow

6

Non mi dispiace usare la array_key_exists()funzione. In effetti, preferisco utilizzare questa funzione specifica piuttosto che affidarmi a funzioni di hacking che potrebbero cambiare il loro comportamento in futuro come emptyeisset (barrato per evitare suscettibilità ).


Tuttavia, utilizzo una semplice funzione che è utile in questo e in alcune altre situazioni nella gestione degli indici di array :

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;
}

Supponiamo che tu abbia i seguenti array:

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

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

Come si ottiene il "valore" dagli array? Semplice:

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');

Abbiamo già coperto array uni e multidimensionali, cos'altro possiamo fare?


Prendi il seguente pezzo di codice, ad esempio:

$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';
}

Abbastanza noioso non è vero? Ecco un altro approccio che utilizza la Value()funzione:

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

Come esempio aggiuntivo, prendi la RealIP()funzione per un test:

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

Perfetto, eh? ;)


6
"Affidarsi a funzioni di hacking che potrebbero cambiare il loro comportamento in futuro" ?! Scusa, ma è la cosa più ridicola che ho sentito in tutta la settimana. Prima di tutto, issete emptysono costrutti del linguaggio , non funzioni. In secondo luogo, se una qualsiasi funzione della libreria / costrutto del linguaggio di base cambia il proprio comportamento, potresti o meno essere fregato. E se array_key_existscambia il suo comportamento? La risposta è che non lo farà, fintanto che lo utilizzi come documentato. Ed issetè documentato per essere utilizzato esattamente così. Le funzioni nel caso peggiore sono deprecate su una o due versioni di rilascio principale. La sindrome NIH è brutta!
inganno

Mi dispiace inganno, ma prima di tutto l' hack è in corsivo nel caso non l'avessi notato. =) In secondo luogo, intendi che non dovresti fare affidamento su array_key_exists()per verificare se una chiave esiste in un array ?! array_key_exists()è stato creato esattamente per questo , preferisco fare affidamento su di esso per questo scopo piuttosto che isset()e specialmente la empty()cui descrizione ufficiale è: "determina se una variabile è vuota", non menziona nulla se esiste effettivamente. Il tuo commento e voto negativo è uno dei più ridicoli a cui abbia assistito in tutto il mese .
Alix Axel

3
Sto dicendo issete emptynon sono più o meno affidabili array_key_existse possono fare lo stesso identico lavoro. Il tuo secondo esempio prolisso può essere scritto $domain = isset($domain['host']) ? $domain['host'] : 'N/A';solo con le caratteristiche del linguaggio di base, senza chiamate di funzioni o dichiarazioni aggiuntive necessarie (nota che non propongo necessariamente l'uso dell'operatore ternario, però; o)). Per le normali variabili scalari dovrai ancora usare isseto empty, e puoi usarle per gli array esattamente allo stesso modo. "Affidabilità" è una cattiva ragione per non farlo.
inganno

1
Hai fatto il tuo punto, anche se non sono d'accordo con la maggior parte delle cose che hai detto. Penso che tu abbia sbagliato nel 90% dei casi, ad esempio io uso sempre il valore "0" nei campi nascosti nei moduli. Tuttavia credo che la soluzione che ho fornito non meriti il ​​voto negativo e potrebbe essere di qualche utilità per Pekka.
Alix Axel

2
Mentre @deceze ha un punto con le funzioni personalizzate - di solito prendo la stessa posizione - l'approccio value () sembra abbastanza interessante da dargli un'occhiata. Penso che la risposta e il seguito consentiranno a tutti coloro che si imbattono in essa in seguito di prendere una decisione. +1.
Pekka,

3

Sono qui con te. Ma i progettisti PHP hanno commesso molti più errori peggiori di così. A meno di definire una funzione personalizzata per qualsiasi lettura di valori, non c'è alcun modo per aggirarla.


1
roba isset (). Rendere tutto nullo per impostazione predefinita risparmierebbe molti problemi.
vava

2
E cos'è questo "tutto"? Sembrerebbe uno spreco per PHP dover immaginare ogni nome di variabile immaginabile e impostarlo su NULL solo così uno sviluppatore pigro può evitare di digitare 5 caratteri.
Lotus Notes

5
@Byron, guarda, è davvero semplice, molti altri linguaggi lo fanno, Ruby e Perl come pochi esempi. VM sa se la variabile è stata utilizzata prima o no, non è vero? Può sempre restituire null invece di fallire con o senza messaggio di errore. E non si tratta di pessimi 5 caratteri, si tratta di scrivere params["width"] = params["width"] || 5per impostare i valori predefiniti invece di tutte quelle sciocchezze con le isset()chiamate.
vava

3
Ci scusiamo per aver resuscitato un vecchio thread. Due dei peggiori errori di PHP sono stati register_globalse magic_quotes. I problemi che questi favoriscono fanno sembrare le variabili non inizializzate quasi innocue al confronto.
staticsan

3

Uso queste funzioni

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

Esempi

$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";
}

2
Lo uso anche io, ma ricorda che in alcuni casi le tue variabili verranno inizializzate automaticamente: es. Load ($ array ['FOO']) creerebbe una chiave FOO in $ array.
Mat

2

Benvenuto nell'operatore di coalescenza nullo (PHP> = 7.0.1):

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

PHP dice:

L'operatore di coalescenza nullo (??) è stato aggiunto come zucchero sintattico per il caso comune di dover utilizzare un ternario insieme a isset (). Restituisce il suo primo operando se esiste e non è NULL; altrimenti restituisce il suo secondo operando.


1

Crea una funzione che ritorni falsese non impostata e, se specificato, falsese vuota. Se valido restituisce la variabile. Puoi aggiungere più opzioni come mostrato nel codice seguente:

<?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"];
}
?>

0

Il software non funziona magicamente per grazia di dio. Se ti aspetti qualcosa che manca, devi gestirlo correttamente.

Se lo ignori, probabilmente stai creando falle di sicurezza nelle tue applicazioni. Nei linguaggi statici l'accesso a una variabile non definita semplicemente non è possibile. Non compilerà o bloccherà semplicemente l'applicazione se è nulla.

Inoltre, rende la tua applicazione non gestibile e diventerai matto quando accadono cose inaspettate. Il rigore del linguaggio è un must e PHP, in base alla progettazione, è sbagliato in tanti aspetti. Ti renderà un cattivo programmatore se non ne sei consapevole.


Sono ben consapevole delle carenze di PHP. Come ho sottolineato nella domanda, sto parlando della revisione di vecchi progetti.
Pekka

Concordato. Essendo uno sviluppatore PHP di lunga data, è abbastanza difficile per me avventurarmi in nuovi linguaggi come Java in cui devi dichiarare tutto.
Dzhuneyt

0

Non sono sicuro di quale sia la tua definizione di leggibilità, ma l'uso corretto dei blocchi empty (), isset () e try / throw / catch è piuttosto importante per l'intero processo.

Se il tuo E_NOTICE proviene da $ _GET o $ _POST, allora dovrebbero essere controllati con empty () insieme a tutti gli altri controlli di sicurezza che quei dati dovrebbero superare.

Se proviene da feed o librerie esterni, dovrebbe essere racchiuso in try / catch.

Se proviene dal database, è necessario controllare $ db_num_rows () o il suo equivalente.

Se proviene da variabili interne, dovrebbero essere inizializzate correttamente. Spesso, questi tipi di avvisi derivano dall'assegnazione di una nuova variabile al ritorno di una funzione che restituisce FALSE in caso di errore. Questi dovrebbero essere racchiusi in un test che, in caso di errore, può assegnare alla variabile un valore predefinito accettabile che il codice può gestire o generare un'eccezione che il codice può gestire.

Queste cose allungano il codice, aggiungono blocchi extra e aggiungono test extra, ma non sono d'accordo con te in quanto penso che aggiungano sicuramente valore extra.


-2

E per quanto riguarda l'utilizzo @dell'operatore?

Per esempio:

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

Potresti dire che questo non va bene perché non hai il controllo di ciò che accade "all'interno" di $ foo (se fosse una chiamata di funzione che contiene un errore PHP per esempio), ma se usi questa tecnica solo per le variabili, questo è equivalente a:

if(isset($foo) && $foo) { /* ... */ }

if(isset($foo))è abbastanza in realtà. Tornerà TRUEse l'espressione restituisce TRUE.
Dzhuneyt

2
@ ColorWP.com restituirà anche true se l'espressione restituisce false.
Jon Hulka

Dovresti usare solo il parametro @ (per ignorare l'avviso) su codice che non è realmente in ulteriore sviluppo, o su codice monouso o una soluzione rapida su progetti esistenti, che non vuoi mostrare a nessun altro. Ma è una soluzione comune per un rapido hack.
rubo77
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.