Cómo evitar isset () y empty ()

98

Tengo varias aplicaciones antiguas que arrojan muchos mensajes "xyz no está definido" y "desplazamiento indefinido" cuando se ejecutan en el nivel de error E_NOTICE, porque la existencia de variables no se comprueba explícitamente con isset()y consorts.

Estoy considerando trabajar a través de ellos para hacerlos compatibles con E_NOTICE, ya que los avisos sobre variables faltantes o compensaciones pueden salvar vidas, puede haber algunas mejoras menores en el rendimiento y, en general, es la forma más limpia.

Sin embargo, no me gusta lo que cientos de infligir isset() empty()y array_key_exists()S todavía a mi código. Se hincha, se vuelve menos legible, sin ganar nada en términos de valor o significado.

¿Cómo puedo estructurar mi código sin un exceso de comprobaciones de variables, y al mismo tiempo ser compatible con E_NOTICE?

Pekka
fuente
6
Concuerdo completamente. Es por eso que me gusta tanto Zend Framework, el módulo de solicitud es muy bueno allí. Si estoy trabajando en alguna aplicación pequeña, generalmente codifico alguna clase de solicitud simple con los métodos mágicos __set y __get que funcionan de manera similar a la solicitud de ZF. De esa manera evito todas las apariciones de isset y vacío en mi código. De esa manera, todo lo que necesita usar es if (count ($ arr)> 0) en matrices antes de iterar sobre ellas y if (null! == $ variable) en algunos lugares críticos. Parece mucho más limpio.
Richard Knop

Respuestas:

128

Para aquellos interesados, he ampliado este tema en un pequeño artículo, que proporciona la siguiente información en una forma algo mejor estructurada: La guía definitiva para isset And empty de PHP


En mi humilde opinión, deberías pensar no solo en hacer que la aplicación sea "compatible con E_NOTICE", sino en reestructurar todo. Tener cientos de puntos en su código que regularmente intentan usar variables inexistentes suena como un programa bastante mal estructurado. Intentar acceder a variables inexistentes nunca debería suceder, otros lenguajes se resisten a esto en tiempo de compilación. El hecho de que PHP te permita hacerlo no significa que debas hacerlo.

Estas advertencias están ahí para ayudarlo , no para molestarlo. Si recibe una advertencia "¡Está intentando trabajar con algo que no existe!" , tu reacción debería ser "Vaya, mi error, déjame arreglar eso lo antes posible". ¿De qué otra manera va a diferenciar entre "variables que funcionan bien sin definir" y código honestamente incorrecto que puede conducir a errores graves ? Esta es también la razón por la que siempre, siempre , desarrolla con informes de errores en 11 y sigue trabajando en su código hasta que noNOTICE se emite un . La desactivación de los informes de errores es solo para entornos de producción, para evitar la fuga de información y proporcionar una mejor experiencia de usuario incluso frente a códigos defectuosos.


Elaborar:

Siempre necesitará isseto emptyen algún lugar de su código, la única forma de reducir su ocurrencia es inicializar sus variables correctamente. Dependiendo de la situación, hay diferentes formas de hacerlo:

Argumentos de la función:

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

No hay necesidad de comprobar si $baro $bazse establece dentro de la función, ya que acaba de establecer ellos, todo lo que necesita es preocuparse por si sus evalúa el valor de trueu false(o cualquier otra cosa).

Variables regulares en cualquier lugar:

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

Inicialice sus variables en la parte superior de un bloque de código en el que las va a usar. Esto resuelve el !issetproblema, asegura que sus variables siempre tengan un valor predeterminado conocido, le da al lector una idea de en qué funcionará el siguiente código y, por lo tanto, también sirve como una especie de auto-documentación.

Matrices:

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

Lo mismo que antes, está inicializando la matriz con valores predeterminados y sobrescribiéndolos con valores reales.

En los casos restantes, digamos una plantilla en la que está generando valores que pueden o no ser establecidos por un controlador, solo tendrá que verificar:

<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 lo usa regularmente array_key_exists, debe evaluar para qué lo está usando. La única vez que marca la diferencia es aquí:

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

Sin embargo, como se indicó anteriormente, si está inicializando correctamente sus variables, no necesita verificar si la clave existe o no, porque sabe que existe. Si usted está recibiendo la matriz de una fuente externa, el valor más probable es que no sea nullpero '', 0, '0', falseo algo parecido, es decir, un valor que se puede evaluar con isseto empty, dependiendo de su intención. Si establece regularmente una clave de matriz en nully quiere que no signifique nada false, es decir, si en el ejemplo anterior los resultados diferentes de issety array_key_existshacen una diferencia en la lógica de su programa, debe preguntarse por qué. La mera existencia de una variable no debería ser importante, solo su valor debería ser de importancia. Si la clave es una bandera true/ false, utilicetrueo falsenonull. La única excepción a esto serían las bibliotecas de terceros que quieran nullsignificar algo, pero como nulles tan difícil de detectar en PHP, todavía tengo que encontrar una biblioteca que haga esto.

deceze
fuente
4
Es cierto, pero la mayoría de los intentos de acceso fallidos se encuentran en la línea de, en if ($array["xyz"])lugar de, isset()o array_key_exists()que considero algo legítimo, ciertamente no son problemas estructurales (corríjame si me equivoco). Agregar me array_key_exists()parece un desperdicio terrible.
Pekka
9
No puedo pensar en ningún caso en el que use en array_key_existslugar de un simple isset($array['key'])o !empty($array['key']). Claro, ambos agregan 7 u 8 caracteres a su código, pero difícilmente lo llamaría un problema. También ayuda a aclarar su código: if (isset($array['key']))significa que esta variable es opcional y puede estar ausente, mientras que if ($array['key'])solo significa "si es verdadera". Si recibe un aviso para el último, sabrá que su lógica está mal en alguna parte.
diciembre
6
Creo que la diferencia entre isset () y array_key_exists () es que este último devolverá verdadero si el valor es NULL. isset () no lo hará.
Htbaa
1
Es cierto, pero no puedo pensar en un caso de uso sensato en el que necesite diferenciar entre una variable inexistente y una clave establecida cuyo valor es nulo. Si el valor se evalúa como FALSO, la distinción debe ser sin diferencia. :)
diciembre
1
Las claves de matriz son ciertamente más molestas que las variables indefinidas. Pero si usted no está seguro de si una matriz contiene una clave o no, significa que o bien no se definió la matriz a sí mismo o que está tirando de él de una fuente que no controlas. Ninguno de los escenarios debería ocurrir con mucha frecuencia; y si sucede, tiene todas las razones para verificar si la matriz contiene lo que cree que contiene. Es una medida de seguridad en mi opinión.
kijin
37

Solo escribe una función para eso. Algo como:

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 puedes usar como

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

Haga lo mismo para cosas triviales como get_number(), get_boolean(), get_array()y así sucesivamente.

BalusC
fuente
5
Esto se ve bien, y también verifica magic_quotes. ¡Agradable!
Pekka
¡Gran función! Muchas gracias por compartir.
Mike Moore
3
Observe que $ _POST ['algo'] puede devolver una matriz, por ejemplo, entradas con <input name="something[]" />. Esto causaría un error (ya que el recorte no se puede aplicar a las matrices) usando el código anterior, en este caso se debería usar is_stringy posiblemente strval. Este no es simplemente un caso en el que se deba usar get_array, ya que la entrada del usuario (maliciosa) tal vez cualquier cosa y el analizador de entrada del usuario nunca debería arrojar un error de todos modos.
Ciantic
1
Utilizo el mismo tipo de función pero definida como tal: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ artículo: $ predeterminado; } La ventaja de esta función es que puede llamarla con matrices, variables y objetos. El inconveniente es que $ item se inicializa (a nulo) después si no lo fue.
Mat
Debe desactivar las citas mágicas de forma global, en lugar de tratar con ellas en una función. Hay muchas fuentes en Internet que explican citas mágicas.
Kayla
13

Creo que una de las mejores formas de lidiar con este problema es accediendo a los valores de las matrices GET y POST (COOKIE, SESSION, etc.) a través de una clase.

Cree una clase para cada una de esas matrices y declare __gety __setmétodos ( sobrecarga ). __getacepta un argumento que será el nombre de un valor. Este método debe verificar este valor en la matriz global correspondiente, ya sea usando isset()o empty()y devolver el valor si existe o null(o algún otro valor predeterminado) de lo contrario.

Después de eso, puede acceder con confianza a los valores de la matriz de esta manera: $POST->usernamey realizar cualquier validación si es necesario sin usar ninguna isset()s o empty()s. Si usernameno existe en la matriz global correspondiente null, se devolverá, por lo que no se generarán advertencias ni avisos.

Jamol
fuente
1
Esta es una gran idea y algo para lo que estoy listo para reestructurar el código. +1
Pekka
Desafortunadamente, no podrá convertir esas instancias en superglobales a menos que las asigne a $ _GET o $ _POST, lo que sería bastante feo. Pero podrías usar clases estáticas, por supuesto ...
ThiefMaster
1
No puedes usar getters y setters en "clases estáticas". y escribir una clase por variable es una mala práctica, ya que implica la duplicación de código, lo cual es malo. No creo que esta solución sea la más adecuada.
Mat
Un miembro público estático de una clase actúa como un superglobal, es decir: HTTP :: $ POST-> username, donde crea una instancia de HTTP :: $ POST en algún momento antes de su uso, es decir. Clase HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow
6

No me importa usar el array_key_exists() función. De hecho, prefiero utilizar esta función específica en lugar de depender de hackers funciones que pueden cambiar su comportamiento en el futuro como emptyyisset (strikedthrough Para evitar susceptibilidades ).


Sin embargo, utilizo una función simple que es útil en esto y en algunas otras situaciones al tratar con índices de matriz :

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

Digamos que tiene las siguientes matrices:

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

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

¿Cómo se obtiene el "valor" de las matrices? Sencillo:

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

Ya tenemos cubiertas uni y matrices multidimensionales, ¿qué más podemos hacer?


Tome el siguiente fragmento de código, por ejemplo:

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

Bastante aburrido, ¿no? Aquí hay otro enfoque que usa la Value()función:

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

Como ejemplo adicional, tome la RealIP()función para una prueba:

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

Limpio, ¿eh? ;)

Alix Axel
fuente
6
"¿Confiar en funciones de hackeo que pueden cambiar su comportamiento en el futuro"? Lo siento, pero eso es lo más ridículo que escuché en toda la semana. En primer lugar, issety emptyson construcciones del lenguaje , no funciones. En segundo lugar, si alguna de las funciones básicas de la biblioteca o las construcciones del lenguaje cambian su comportamiento, es posible que esté o no arruinado. ¿Y si array_key_existscambia su comportamiento? La respuesta es que no lo hará, siempre que lo esté utilizando como se documenta. Y issetestá documentado para usarse exactamente así. Las funciones del peor de los casos están en desuso con respecto a una versión principal o dos. ¡El síndrome de los NIH es malo!
diciembre
Lo siento, deceze, pero antes que nada el truco está en cursiva en caso de que no lo hayas notado. =) En segundo lugar, ¿quiere decir que uno no debería confiar en array_key_exists()comprobar si existe una clave en una matriz ? array_key_exists()fue creado exactamente para eso , prefiero confiar en él para este propósito que isset()y especialmente empty()cuya descripción oficial es: "determinar si una variable está vacía", no menciona nada si realmente existe. Su comentario y voto negativo es uno de los más ridículos que he presenciado en todo el mes .
Alix Axel
3
Estoy diciendo issety emptyhay más o menos fiable que array_key_existsy puede hacer exactamente el mismo trabajo. Su segundo ejemplo, de larga duración, puede escribirse como $domain = isset($domain['host']) ? $domain['host'] : 'N/A';con solo las características del lenguaje principal, sin llamadas de función adicionales o declaraciones necesarias (tenga en cuenta que no necesariamente abogo por el uso del operador ternario; o)). Para las variables escalares ordinarias, aún necesitará usar isseto empty, y puede usarlas para matrices exactamente de la misma manera. La "confiabilidad" es una mala razón para no hacerlo.
diciembre
1
Hizo su punto, aunque no estoy de acuerdo con la mayoría de las cosas que dijo. Creo que se equivocó mucho en los casos de más del 90%, por ejemplo, utilizo el valor de "0" en los campos ocultos de los formularios todo el tiempo. Aún así, creo que la solución que proporcioné no merece el voto negativo y bien puede ser de alguna utilidad para Pekka.
Alix Axel
2
Si bien @deceze tiene un punto con las funciones personalizadas, normalmente adopto la misma postura, el enfoque value () parece lo suficientemente interesante como para echarle un vistazo. Creo que la respuesta y el seguimiento permitirán a todos los que se encuentren con ella más tarde tomar una decisión. +1.
Pekka
3

Estoy aqui contigo. Pero los diseñadores de PHP han cometido errores mucho más graves que ese. Aparte de definir una función personalizada para cualquier lectura de valor, no hay forma de evitarlo.

vava
fuente
1
cosas de isset (). Hacer que todo sea nulo de forma predeterminada ahorraría muchos problemas.
vava
2
¿Y qué es este "todo"? Parecería un desperdicio para PHP tener que imaginar cada nombre de variable concebible y establecer cada uno en NULL solo para que un desarrollador perezoso pueda evitar escribir 5 caracteres.
Lotus Notes
5
@Byron, mira, es realmente simple, muchos otros lenguajes hacen eso, Ruby y Perl como pocos ejemplos. VM sabe si la variable se usó antes o no, ¿no es así? Siempre puede devolver nulo en lugar de fallar con o sin mensaje de error. Y no se trata de los pésimos 5 caracteres, se trata de escribir params["width"] = params["width"] || 5para establecer valores predeterminados en lugar de todas esas tonterías con las isset()llamadas.
vava
3
Perdón por resucitar un viejo hilo. Dos de los peores errores de PHP fueron register_globalsy magic_quotes. Los problemas que estos fomentan hacen que las variables no inicializadas parezcan casi inofensivas en comparación.
staticsan
3

Yo uso estas funciones

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

Ejemplos

$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ň
fuente
2
Yo también uso esto, pero recuerde que en algún caso, sus variables se inicializarán automáticamente: por ejemplo, load ($ array ['FOO']) crearía una clave FOO en $ array.
Mat
2

Bienvenido al operador de fusión nula (PHP> = 7.0.1):

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

PHP dice:

El operador coalescente nulo (??) se ha agregado como azúcar sintáctico para el caso común de necesitar usar un ternario junto con isset (). Devuelve su primer operando si existe y no es NULL; de lo contrario, devuelve su segundo operando.

Alexandre Thebaldi
fuente
1

Crea una función que regrese falsesi no se establece y, si se especifica, falsesi está vacía. Si es válido, devuelve la variable. Puede agregar más opciones como se ve en el siguiente código:

<?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"];
}
?>
fuego de Dragon
fuente
0

El software no funciona mágicamente por la gracia de Dios. Si está esperando algo que falta, debe manejarlo adecuadamente.

Si lo ignora, probablemente esté creando agujeros de seguridad en sus aplicaciones. En lenguajes estáticos, acceder a una variable no definida simplemente no es posible. No compilará o bloqueará simplemente su aplicación si es nula.

Además, hace que su aplicación no se pueda mantener y se volverá loco cuando sucedan cosas inesperadas. El rigor del lenguaje es imprescindible y PHP, por diseño, está mal en muchos aspectos. Te convertirá en un mal programador si no lo sabes.

Knoopx
fuente
Soy muy consciente de las deficiencias de PHP. Como señalé en la pregunta, me refiero a la revisión de proyectos más antiguos.
Pekka
Convenido. Siendo un desarrollador de PHP con mucho tiempo, es bastante difícil para mí aventurarme en nuevos lenguajes como Java, donde necesitas declarar todo.
Dzhuneyt
0

No estoy seguro de cuál es su definición de legibilidad, pero el uso adecuado de los bloques empty (), isset () y try / throw / catch es bastante importante para todo el proceso.

Si su E_NOTICE proviene de $ _GET o $ _POST, entonces deben comprobarse con empty () junto con todas las demás comprobaciones de seguridad que esos datos deberían tener que pasar.

Si proviene de fuentes o bibliotecas externas, debe incluirse en try / catch.

Si proviene de la base de datos, se debe marcar $ db_num_rows () o su equivalente.

Si proviene de variables internas, deben inicializarse correctamente. A menudo, este tipo de avisos provienen de asignar una nueva variable al retorno de una función que devuelve FALSE en caso de falla. Estos deben incluirse en una prueba que, en caso de falla, pueda asignar a la variable un valor predeterminado aceptable que el código pueda manejar, o lanzar una excepción que el código pueda manejar.

Estas cosas hacen que el código sea más largo, agregan bloques adicionales y agregan pruebas adicionales, pero no estoy de acuerdo con usted en que creo que definitivamente agregan un valor adicional.

Mlutz
fuente
-2

¿Qué pasa con el uso del @operador?

Por ejemplo:

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

Puede decir que esto es malo porque no tiene control de lo que sucede "dentro" de $ foo (si fuera una llamada de función que contiene un error de PHP, por ejemplo), pero si solo usa esta técnica para variables, esto es equivalente a:

if(isset($foo) && $foo) { /* ... */ }
Estera
fuente
if(isset($foo))es suficiente en realidad. Volverá TRUEsi la expresión se evalúa como TRUE.
Dzhuneyt
2
@ ColorWP.com también devolverá verdadero si la expresión se evalúa como falsa.
Jon Hulka
Solo debe usar el parámetro @ (para ignorar el aviso) en el código que realmente no está en desarrollo, o en un código de una sola vez o una solución rápida en proyectos existentes, que no desea mostrar a nadie más. Pero es una solución común para un truco rápido.
rubo77