La mejor manera de probar la existencia de una variable en PHP; isset () está claramente roto

187

De los isset()documentos :

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

Básicamente, isset()no verifica si la variable está configurada en absoluto, sino si está configurada en algo diferente NULL.

Dado eso, ¿cuál es la mejor manera de verificar la existencia de una variable? Intenté algo como:

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

( @es necesario para evitar la advertencia cuando $vno está configurado) pero is_null()tiene un problema similar al siguiente isset(): ¡regresa TRUEen variables no configuradas! También parece que:

@($v === NULL)

funciona exactamente como @is_null($v) , así que eso también está fuera.

¿Cómo se supone que debemos verificar de manera confiable la existencia de una variable en PHP?


Editar: hay claramente una diferencia en PHP entre las variables que no están configuradas y las variables que están configuradas en NULL:

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

PHP muestra que $a['b']existe y tiene un NULLvalor. Si agrega:

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

Puedes ver la ambigüedad de la que estoy hablando con la isset()función. Aquí está el resultado de los tres var_dump()s:

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

Edición adicional: dos cosas.

Uno, un caso de uso. Una matriz que se convierte en los datos de una UPDATEinstrucción SQL , donde las claves de la matriz son las columnas de la tabla, y los valores de la matriz son los valores que se aplicarán a cada columna. Cualquiera de las columnas de la tabla puede contener un NULLvalor, significando pasando un NULLvalor en la matriz. Usted necesita una manera de diferenciar entre una clave matriz no existente, y el valor de un array se configura a NULL; esa es la diferencia entre no actualizar el valor de la columna y actualizar el valor de la columna a NULL.

En segundo lugar, la respuesta de Zoredache , array_key_exists()funciona correctamente, para mi caso de uso por encima y por ninguna variable global:

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

salidas:

bool(true)
bool(false)

Dado que eso se maneja correctamente en casi todas partes, puedo ver que existe alguna ambigüedad entre las variables que no existen y las variables configuradas NULL, estoy llamando a array_key_exists()la forma oficial más fácil en PHP para verificar realmente la existencia de una variable .

(Solo otro caso en el que puedo pensar es para las propiedades de clase, para las cuales hay property_exists(), que, según sus documentos , funciona de manera similar, ya array_key_exists()que distingue adecuadamente entre no establecerse y establecerse en NULL).

chazomaticus
fuente
No puede verificar, pero ¿por qué necesita hacerlo?
demasiado php
12
NULL tiene un significado muy específico en PHP, y es un concepto completamente separado de si una variable está configurada o no.
chazomaticus
33
Hay razones para diferenciar entre nulo e inexistente. Por ejemplo, está creando un objeto para representar una fila en una tabla de base de datos. Para cada columna de la fila, crea una variable privada, accesible solo a través del método getter del objeto. Supongamos que un valor de columna es nulo. Ahora, ¿cómo sabe el método getter si no hay tal columna en la tabla o si este objeto tiene un valor nulo allí? Afortunadamente, en mi caso, la variable privada es en realidad una entrada en una matriz privada, por lo que puedo usar array_key_exists, pero este es un problema real.
Nathan Long
1
Se ha eliminado de las nuevas versiones de PHP, sí. Desafortunadamente, no se ha ido de cada implementación de PHP. Además, parece un detalle semántico inútil discutir sobre si estamos hablando de elementos de matriz o variables. Independientemente de los estándares que cree que debe cumplir el código, es útil saber cómo solucionar una inconsistencia en el lenguaje PHP.
chazomaticus
2
@chazomaticus Pero las variables y los elementos de la matriz son cosas fundamentalmente diferentes ; el hecho de que pueda hacer algunas de las mismas cosas con ellos no significa que sean o deberían ser 100% intercambiables. Aquí no hay "inconsistencia en el lenguaje PHP", solo algo que no te gusta / no entiendes. En cuanto a register_globals, todavía me cuesta pensar en una situación en la que incluso eso requeriría tal distinción, ya que cualquier cosa registrada desde la solicitud HTTP siempre sería una cadena, no null.
IMSoP

Respuestas:

97

Si la variable que está verificando estaría en el ámbito global que podría hacer:

array_key_exists('v', $GLOBALS) 
Zoredache
fuente
3
Ah ja! ¡AHORA estás hablando! ¿Cómo harías eso para, por ejemplo, las propiedades de clase?
chazomaticus
22
Como variación, si la verificación necesita funcionar también para las variables de alcance local, on puede hacer un $defined_vars = get_defined_vars();y luego probar a través de array_key_exists('v', $defined_vars);.
Henrik Opel
1
Esto parece un poco feo para mí, pero en el caso de que en realidad estás comprobando un elemento de matriz, que tiene mucho más sentido: isset($foo[$bar])se convierte enarray_key_exists($bar, $foo)
Arild
property_existsparece prometedor, excepto por esto:> La función property_exists () no puede detectar propiedades que sean mágicamente accesibles usando el método mágico __get.
alexw
@alexw Las variables "creadas" a través de __get no existen. __get es un código arbitrario utilizado como reserva para variables inexistentes, que puede devolver lo que quiera, independientemente de si alguna vez se almacenaron datos relevantes.
Brilliand
46

Intentando dar una visión general de las diversas discusiones y respuestas:

No hay una respuesta única a la pregunta que pueda reemplazar todas las formas en que issetse puede utilizar. Algunos casos de uso son abordados por otras funciones, mientras que otros no resisten el escrutinio o tienen un valor dudoso más allá del código golf. Lejos de estar "roto" o "inconsistente", otros casos de uso demuestran por qué issetla reacción a esta nulles la conducta lógica.

Casos de uso real (con soluciones)

1. Teclas de matriz

Las matrices pueden tratarse como colecciones de variables, unsety issettratarlas como si lo fueran. Sin embargo, dado que pueden iterarse, contarse, etc., un valor faltante no es lo mismo que uno cuyo valor es null.

La respuesta en este caso, es usar en array_key_exists()lugar deisset() .

Como esto toma la matriz para verificar como argumento de función, PHP aún generará "avisos" si la matriz en sí misma no existe. En algunos casos, se puede argumentar válidamente que cada dimensión debería haberse inicializado primero, por lo que el aviso está haciendo su trabajo. Para otros casos, una función "recursiva" array_key_exists, que verificaba cada dimensión de la matriz a su vez, evitaría esto, pero básicamente sería lo mismo que @array_key_exists. También es algo tangencial al manejo de nullvalores.

2. Propiedades del objeto

En la teoría tradicional de la "Programación orientada a objetos", la encapsulación y el polimorfismo son propiedades clave de los objetos; en una aplicación orientada a objetos basado en clases como PHP de las propiedades encapsulados se declaran como parte de la definición de clase, y dados los niveles de acceso ( public, protectedo private).

Sin embargo, PHP también le permite agregar dinámicamente propiedades a un objeto, como lo haría con las claves de una matriz, y algunas personas usan objetos sin clase (técnicamente, instancias de lo incorporado stdClass, que no tiene métodos o funcionalidad privada) camino a matrices asociativas. Esto lleva a situaciones en las que una función puede querer saber si se ha agregado una propiedad particular al objeto que se le ha asignado.

Al igual que con claves de matriz, una solución para el control de las propiedades del objeto se incluye en el lenguaje, llamado, razonablemente,property_exists .

Casos de uso no justificables, con discusión

3. register_globals, y otra contaminación del espacio de nombres global

La register_globalscaracterística agregó variables al ámbito global cuyos nombres fueron determinados por aspectos de la solicitud HTTP (parámetros GET y POST y cookies). Esto puede conducir a códigos con errores e inseguros, por lo que se ha deshabilitado de forma predeterminada desde PHP 4.2, lanzado en agosto de 2000 y eliminado por completo en PHP 5.4, lanzado en marzo de 2012 . Sin embargo, es posible que algunos sistemas todavía se estén ejecutando con esta función habilitada o emulada. También es posible "contaminar" el espacio de nombres global de otras maneras, utilizando la globalpalabra clave o $GLOBALSmatriz.

En primer lugar, register_globalses poco probable que produzca una nullvariable inesperadamente , ya que los valores GET, POST y cookie siempre serán cadenas (con ''todavía regresando truede isset), y las variables en la sesión deben estar completamente bajo el control del programador.

En segundo lugar, la contaminación de una variable con el valor nulles solo un problema si esto sobrescribe alguna inicialización previa. "Sobreescribir" una variable no inicializada nullsolo sería problemático si el código en otro lugar distinguiera entre los dos estados, por lo que esta posibilidad es un argumento en contra de hacer tal distinción.

4. get_defined_varsycompact

Algunas funciones raramente utilizadas en PHP, como get_defined_varsy compact, le permiten tratar los nombres de variables como si fueran claves en una matriz. Para las variables globales, la matriz superglobal$GLOBALS permite un acceso similar y es más común. Estos métodos de acceso se comportarán de manera diferente si una variable no está definida en el alcance relevante.

Una vez que haya decidido tratar un conjunto de variables como una matriz utilizando uno de estos mecanismos, puede realizar las mismas operaciones en él que en cualquier matriz normal. En consecuencia, ver 1.

La funcionalidad que existía solo para predecir cómo estas funciones están a punto de comportarse (por ejemplo, "¿habrá una clave 'foo' en la matriz devuelta por get_defined_vars?") Es superflua, ya que simplemente puede ejecutar la función y descubrir sin efectos nocivos.

4a. Variables variables ( $$foo)

Aunque no es lo mismo que las funciones que convierten un conjunto de variables en una matriz asociativa, la mayoría de los casos que usan "variables variables" ("asignar a una variable nombrada en base a esta otra variable") pueden y deben cambiarse para usar una matriz asociativa. .

Un nombre de variable, fundamentalmente, es la etiqueta dada a un valor por el programador; si lo está determinando en tiempo de ejecución, no es realmente una etiqueta sino una clave en algún almacén de valores clave. Más prácticamente, al no usar una matriz, está perdiendo la capacidad de contar, iterar, etc. También puede ser imposible tener una variable "fuera" del almacén de valores clave, ya que podría sobrescribirse $$foo.

Una vez que se cambia para usar una matriz asociativa, el código será apto para la solución 1. El acceso indirecto a la propiedad del objeto (por ejemplo $foo->$property_name) se puede abordar con la solución 2.

5. issetes mucho más fácil de escribir quearray_key_exists

No estoy seguro de que esto sea realmente relevante, pero sí, los nombres de funciones de PHP pueden ser bastante largos e inconsistentes a veces. Aparentemente, las versiones prehistóricas de PHP usaban la longitud del nombre de una función como una clave hash, por lo que Rasmus inventaba deliberadamente nombres de funciones como htmlspecialcharspara que tuvieran una cantidad inusual de caracteres ...

Aún así, al menos no estamos escribiendo Java, ¿eh? ;)

6. Las variables no inicializadas tienen un tipo

La página del manual sobre conceptos básicos variables incluye esta declaración:

Las variables no inicializadas tienen un valor predeterminado de su tipo según el contexto en el que se utilizan

No estoy seguro de si hay alguna noción en el motor Zend de "tipo no inicializado pero conocido" o si esto está leyendo demasiado en la declaración.

Lo que está claro es que no hace una diferencia práctica en su comportamiento, ya que los comportamientos descritos en esa página para las variables no inicializadas son idénticos al comportamiento de una variable cuyo valor es null. Para elegir un ejemplo, ambos $ay $ben este código terminarán como el entero 42:

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

$b = null;
$b += 42;

(El primero generará un aviso sobre una variable no declarada, en un intento de hacer que escriba un código mejor, pero no hará ninguna diferencia en cómo se ejecuta el código).

99. Detectar si una función se ha ejecutado

(Mantener este último, ya que es mucho más largo que los demás. Tal vez lo edite más tarde ...)

Considere el siguiente código:

$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_functionpuede regresar null, existe la posibilidad de que echono se llegue a él aunque se haya some_testdevuelto true. La intención del programador era detectar cuándo $resultnunca se había configurado, pero PHP no les permite hacerlo.

Sin embargo, hay otros problemas con este enfoque, que se vuelven claros si agrega un bucle externo:

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

Debido a $resultque nunca se inicializa explícitamente, tomará un valor cuando pase la primera prueba, lo que hace imposible saber si las pruebas posteriores pasaron o no. Esto es realmente un error extremadamente común cuando las variables no se inicializan correctamente.

Para solucionar esto, necesitamos hacer algo en la línea donde he comentado que falta algo. La solución más obvia es establecer $resultun "valor terminal" que some_functionnunca pueda regresar; Si es así null, el resto del código funcionará bien. Si no hay un candidato natural para un valor terminal porque some_functiontiene un tipo de retorno extremadamente impredecible (que probablemente sea un mal signo en sí mismo), entonces $foundpodría usarse un valor booleano adicional, por ejemplo , en su lugar.

Pensamiento experimento uno: la very_nullconstante

PHP teóricamente podría proporcionar una constante especial, así como también null, para usar aquí como valor terminal; presumiblemente, sería ilegal devolver esto desde una función, o sería forzado a hacerlo null, y lo mismo probablemente se aplicaría al pasarlo como un argumento de función. Eso haría que este caso muy específico fuera un poco más simple, pero tan pronto como decidiera volver a factorizar el código, por ejemplo, para poner el bucle interno en una función separada, se volvería inútil. Si la constante se pudiera pasar entre funciones, no podría garantizar que some_functionno la devolvería, por lo que ya no sería útil como valor de terminal universal.

El argumento para detectar variables no inicializadas en este caso se reduce al argumento de esa constante especial: si reemplaza el comentario unset($result)y lo trata de manera diferente $result = null, está introduciendo un "valor" para $resultque no se pueda pasar, y solo puede ser detectado por funciones incorporadas específicas.

Pensamiento experimento dos: contador de asignación

Otra forma de pensar acerca de lo que el último ifpregunta es "algo ha hecho una asignación a$result ?" En lugar de considerarlo como un valor especial de $result, tal vez podría pensar en esto como "metadatos" sobre la variable, un poco como la "contaminación variable" de Perl. Así que en lugar de issetque podría llamar has_been_assigned_to, y en lugar de unset, reset_assignment_state.

Pero si es así, ¿por qué detenerse en un booleano? ¿Qué sucede si desea saber cuántas veces pasó la prueba? simplemente podría extender sus metadatos a un número entero y tener get_assignment_county reset_assignment_count...

Obviamente, agregar tal característica tendría una compensación en la complejidad y el rendimiento del lenguaje, por lo que debería sopesarse cuidadosamente en función de su utilidad esperada. Al igual que con una very_nullconstante, sería útil solo en circunstancias muy estrechas y sería igualmente resistente a la refactorización.

La pregunta con suerte obvia es por qué el motor de tiempo de ejecución de PHP debería asumir de antemano que desea realizar un seguimiento de tales cosas, en lugar de dejar que lo haga explícitamente, utilizando un código normal.

IMSoP
fuente
En cuanto a las clases y propiedades, lamentablemente property_exists () no funciona cuando la propiedad es array, por ejemplo: Class {public $ property = array ()}. Lanza un error.
Andrew
1
@ Andrew Parece que funciona bien para mí: 3v4l.org/TnAY5 ¿Te importaría dar un ejemplo completo?
IMSoP
Sí, parece funcionar bien, algo mal estaba con mi configuración. Perdón por la falsa alarma :)
Andrew
20

A veces me pierdo un poco tratando de averiguar qué operación de comparación usar en una situación dada. isset()solo se aplica a valores no inicializados o explícitamente nulos. Pasar / asignar nulo es una excelente manera de garantizar que una comparación lógica funcione como se esperaba.

Aún así, es un poco difícil de pensar, así que aquí hay una matriz simple que compara cómo diferentes valores serán evaluados por diferentes operaciones:

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

Para ajustar la tabla comprimí un poco las etiquetas:

  • $a; se refiere a una variable declarada pero no asignada
  • todo lo demás en la primera columna se refiere a un valor asignado, como:
    • $a = null;
    • $a = [];
    • $a = 0;
    • ...
  • las columnas se refieren a operaciones de comparación, como:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false
    • ...

Todos los resultados son booleanos, truese imprimen y falsese omiten.

Puede ejecutar las pruebas usted mismo, verifique esta esencia:
https://gist.github.com/mfdj/8165967

Mark Fox
fuente
Tal vez fuera del alcance de esta pregunta, pero es posible que desee agregar "0"a la tabla, para completar y aclarar la emptyoperación
Rik Schaaf
17

Puede usar la construcción de lenguaje compacto para probar la existencia de una variable nula. Las variables que no existen no aparecerán en el resultado, mientras que se mostrarán valores nulos.

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

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

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

En el caso de tu ejemplo:

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

Por supuesto, para las variables de alcance global también puede usar array_key_exists ().

Por cierto, personalmente evitaría situaciones como la peste donde hay una diferencia semántica entre una variable que no existe y la variable que tiene un valor nulo. PHP y la mayoría de los otros lenguajes simplemente no creen que exista.

Matijs
fuente
3
PHP no lo hace, pero no diría que la mayoría de los otros lenguajes no lo hacen. La mayoría de los lenguajes que declaran variables arrojarán un error si no se ha declarado una variable, pero puede configurarlos en NULL. Semánticamente, NULLdebería significar "sin recurso", pero no definir una variable es un error del programador.
M Miller
1
@MMiller Claro, pero escribir código que siga una ruta en el caso de "sin recurso" y una ruta diferente en el caso de "error del programador" es bastante absurdo. Si desea detectar variables no declaradas durante la depuración, use una herramienta de análisis estático, como lo haría para encontrar posibles errores en cualquier idioma.
IMSoP
@ Miller, genial, ¿cómo pensaste en esto?
Pacerier
1
@MMiller Pero no funciona como una refutación, porque la declaración en la respuesta es explícitamente sobre "una variable no existente", y su contraejemplo es sobre una propiedad de objeto / clave hash no existente . La distinción entre estos casos no es solo incidental.
IMSoP
1
@ Miller - de hecho, ese es un mejor ejemplo. Aún así, después de más de 20 años de programación en lenguajes estrictos, las situaciones en las que necesitaba distinciones undefinedy nullson tan raras que no me lo pierdo. En mi humilde opinión, el uso principal undefinedes "error de programador en un lenguaje no estricto". En un lenguaje estricto, si necesito un estado distinto para client did not state a value, entonces declaro un valor apropiado para la situación y lo pruebo. En el peor de los casos, tiene que agregar una variable de bandera separada. ¡Pero hacer eso rara vez es mejor que SIEMPRE hacer frente a DOS estados diferentes sin valor!
ToolmakerSteve
15

Explicando NULL, lógicamente pensando

Supongo que la respuesta obvia a todo esto es ... No inicialice sus variables como NULL, inicialícelas como algo relevante para lo que están destinadas a convertirse.

Tratar NULL adecuadamente

NULL debe tratarse como "valor no existente", que es el significado de NULL. La variable no puede clasificarse como existente en PHP porque no se le ha dicho qué tipo de entidad está tratando de ser. Es posible que tampoco exista, por lo que PHP simplemente dice "Bien, no lo hace porque de todos modos no tiene sentido y NULL es mi forma de decir esto".

Un argumento

Discutamos ahora. "Pero NULL es como decir 0 o FALSE o ''.

Incorrecto, 0-FALSO- '' todavía se clasifican como valores vacíos, pero se especifican como algún tipo de valor o respuesta predeterminada a una pregunta. FALSO es la respuesta a sí o no '', es la respuesta al título que alguien envió, y 0 es la respuesta a la cantidad o el tiempo, etc. ESTÁN configurados como algún tipo de respuesta / resultado que los hace válidos como establecidos.

NULL simplemente no tiene respuesta, no nos dice sí o no y no nos dice la hora y no nos dice que se envió una cadena en blanco. Esa es la lógica básica para entender NULL.

Resumen

No se trata de crear funciones extrañas para solucionar el problema, solo está cambiando la forma en que su cerebro ve NULL. Si es NULL, suponga que no está configurado como nada. Si está predefiniendo variables, defínelas como 0, FALSO o "" dependiendo del tipo de uso que desee para ellas.

Siéntase libre de citar esto. Está fuera de la parte superior de mi cabeza lógica :)

grandioso
fuente
55
Gran respuesta. Muchas veces veo a personas quejándose de cómo odian esta o aquella característica de un idioma. Pero parecen estar asumiendo que "si no lo hace a mi manera, entonces está roto". Sí, hay malas decisiones de diseño. ¡Pero también hay desarrolladores muy cerrados!
curtisdf
23
Hay una gran diferencia entre la variable no establecida y la variable === nulo. Uno no existe, el otro tiene un valor nulo. Los argumentos que nulos significan que ningún valor simplemente no es cierto. Nulo ES UN VALOR de tipo nulo. Es un valor perfectamente válido y no hay razón para que php lo trate como un valor no existente, lo que lamentablemente hace. Estaría bien, si las variables no existentes fueran nulas y todas las variables existentes no fueran nulas y la asignación de nulo en variable lo desarmaría. Pero hay MUCHAS situaciones, donde las funciones devuelven nulo como valor real. Entonces estamos jodidos, porque no hay una forma sangrienta de probarlo.
enrey
2
Sé que "no se supone" que verifiquemos la existencia de variables en php, demonios, ni siquiera hay una forma real de verificarlo. No voy a escribir código que dependa de él, porque no es posible en php. Esa es una limitación de php. Claramente, hay una diferencia entre la variable no establecida y la nula, pero php no proporciona formas de distinguirlas. Sin embargo, gran parte de la meta funcionalidad depende internamente: la lectura de var no existente produce un aviso, isset($a['x'])le dirá falso si xes nulo, pero aparecerá en count($a)... compactfuncionará en todas las variables establecidas, incluidas nulls, etc.
enrey
3
Esta respuesta es defectuosa de una manera importante: en la programación OO, nulo es la opción lógica que significa "sin objeto". Por ejemplo, en circunstancias excepcionales cuando una función puede devolver un objeto o ningún objeto, la elección obvia es nulo. Técnicamente en PHP, podría usarse falso o cualquier otro valor considerado falso en contexto booleano, pero luego está perdiendo algo de pureza semántica. Por lo tanto, nulo es un valor perfectamente razonable para inicializar una variable que eventualmente debería contener un objeto, porque es relevante para lo que se pretende convertir.
chazomaticus
3
Mientras PHP arroje errores para variables indefinidas, pero no para valores nulos, entonces hay una diferencia. Si nulo e indefinido fueran realmente el mismo concepto, entonces PHP debería asumir que las variables predeterminadas indefinidas / no declaradas son nulas y nunca arrojan un error, pero nadie quiere eso porque es una pesadilla de desarrollo. Puede que nulo e indefinido no sea realmente diferente en el contexto de la semántica de valores, pero son muy diferentes cuando se trata de escribir código claro y depurable.
Chris Middleton
9

Property_exists puede verificar la existencia de las propiedades de los objetos

Ejemplo de una prueba unitaria:

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

fuente
4

Como una adición a la discusión de greatbigmassive sobre lo que significa NULL , considere lo que realmente significa "la existencia de una variable".

En muchos idiomas, debe declarar explícitamente cada variable antes de usarla ; esto puede determinar su tipo, pero lo más importante es que declara su alcance . Una variable "existe" en todas partes en su alcance, y en ninguna parte fuera de ella, ya sea una función completa o un solo "bloque".

Dentro de su alcance, una variable asigna algún significado a una etiqueta que usted, el programador, ha elegido. Fuera de su alcance, esa etiqueta no tiene sentido (si usa la misma etiqueta en un alcance diferente es básicamente irrelevante).

En PHP, no es necesario declarar las variables : cobran vida tan pronto como las necesite. Cuando escribe en una variable por primera vez, PHP asigna una entrada en memoria para esa variable. Si lee de una variable que actualmente no tiene una entrada, PHP considera que esa variable tiene el valor NULL.

Sin embargo, los detectores automáticos de calidad de código generalmente le avisarán si usa una variable sin "inicializarla" primero. En primer lugar, esto ayuda a detectar errores tipográficos, como asignar a, $thingIdpero leer desde $thing_id; pero en segundo lugar, te obliga a considerar el alcance sobre el cual esa variable tiene significado, tal como lo haría una declaración.

Cualquier código que se preocupe si una variable "existe" es parte del alcance de esa variable , ya sea que se haya inicializado o no, usted como programador le ha dado a esa etiqueta el significado en ese punto del código. Como lo está utilizando, en cierto sentido debe "existir", y si existe, debe tener un valor implícito; en PHP, ese valor implícito es null.

Debido a la forma en que funciona PHP, es posible escribir código que trate el espacio de nombres de las variables existentes no como un alcance de etiquetas a las que le ha dado significado, sino como algún tipo de almacén de valores clave. Puede, por ejemplo, ejecutar código de la siguiente manera: $var = $_GET['var_name']; $$var = $_GET['var_value'];. Solo porque puedas, no significa que sea una buena idea.

Resulta que PHP tiene una forma mucho mejor de representar las tiendas de valores clave, llamadas matrices asociativas. Y aunque los valores de una matriz se pueden tratar como variables, también puede realizar operaciones en la matriz como un todo. Si tiene una matriz asociativa, puede probar si contiene una clave usando array_key_exists().

También puede usar objetos de manera similar, estableciendo dinámicamente propiedades, en cuyo caso puede usar property_exists()exactamente de la misma manera. Por supuesto, si se define una clase, se puede declarar qué propiedades se ha - incluso se puede elegir entre public, privatey protectedámbito de aplicación.

Aunque existe una diferencia técnica entre una variable (en oposición a una clave de matriz o una propiedad de objeto) que no se ha inicializado (o que se ha explícitamente unset()) y una cuyo valor es null, cualquier código que considere que esa diferencia es significativa está utilizando variables de una manera que no están destinadas a ser utilizadas.

IMSoP
fuente
1
Muy buenos puntos, aunque no exactamente una respuesta a la pregunta.
chazomaticus
1
A la pregunta explícita "¿Cómo se supone que debemos verificar confiablemente la existencia de una variable en PHP?" mi respuesta es "no lo eres, y he aquí por qué". Tanto esta respuesta como la de greatbigmassive también responden a la pregunta implícita "¿por qué se isset()comporta de esa manera?".
IMSoP
"Si lee de una variable que actualmente no tiene una entrada, PHP considera que esa variable tiene el valor NULL". Esto es falso Una variable indefinida es simplemente indefinida. Puede volver nulo cuando intentas acceder a él, pero eso es irrelevante.
Hugo Zink
@HugoZink ¿Irrelevante para qué? Cualquier prueba que haga del valor de una variable indefinida le dirá que el valor es null. Si ese valor existe antes de mirarlo es una cuestión para los filósofos, pero en lo que respecta a cualquier comportamiento observable, el valor es consistente null.
IMSoP
3

issetcomprueba si la variable está establecida y, si es así, si su valor no es NULL. La última parte (en mi opinión) no está dentro del alcance de esta función. No existe una solución alternativa decente para determinar si una variable es NULL porque no está establecida o porque está establecida explícitamente en NULL .

Aquí hay una posible solución:

$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

Otra solución es probar la salida 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
Salman A
fuente
2

No estoy de acuerdo con su razonamiento sobre NULL , y decir que necesita cambiar su mentalidad sobre NULL es simplemente extraño.

Creo que isset () no se diseñó correctamente, isset () debería decirle si la variable se ha establecido y no debería preocuparse por el valor real de la variable.

¿Qué sucede si está comprobando los valores devueltos por una base de datos y una de las columnas tiene un valor NULL? Todavía desea saber si existe incluso si el valor es NULL ... no, no confíe en isset () aquí.

igualmente

$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 () debería haber sido diseñado para funcionar así:

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

de esta manera, dejamos que el programador verifique los tipos y no lo dejemos en isset () para asumir que no está allí porque el valor es NULL, es simplemente un diseño estúpido

Christof Coetzee
fuente
Su ejemplo no es verificar la existencia de una variable, sino de una clave de matriz. Existe una solución a eso, en forma de array_key_exists. Nunca debe encontrarse en una situación en la que no sabe en tiempo de ejecución si existe una variable real.
IMSoP
@chazomaticus Bueno, nunca deberías estar en una situación en la que register_globals esté activado, así que mantengo esa afirmación.
IMSoP
Oh, estoy de acuerdo Aún así, no todos pueden controlar dónde se implementa su código. Es útil tener información para cada situación, ya sea cómo las cosas "deberían" ser o no.
chazomaticus
@chazomaticus Si su problema es register_globals, entonces su respuesta no es un cambio a isset(). El manual de PHP menciona que "generalmente es una buena práctica de programación inicializar variables primero", lo que resuelve register_globalsen tiempo de diseño en lugar de en tiempo de ejecución. También hay una entrada de preguntas frecuentes que ofrece una unregister_globals()función para tratarla en tiempo de ejecución.
IMSoP
2

Voy a agregar dos centavos rápidos a esto. Una razón por la cual este problema es confuso es porque este escenario parece devolver el mismo resultado con un informe de error no completo:

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

Podría suponer a partir de este resultado que la diferencia entre $a = nully no definir $bnada es nada.

Error de arranque informando:

NULL

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

Nota: arrojó un error variable indefinido, pero el valor de salida devar_dump todavía NULL.

PHP obviamente tiene una capacidad interna para distinguir entre una variable nula y una variable indefinida. Me parece que debería haber una función integrada para verificar esto.

Creo que la respuesta aceptada es buena en su mayor parte, pero si fuera a implementarla, escribiría un contenedor para ella. Como se mencionó anteriormente en esta respuesta , tengo que aceptar que en realidad no he encontrado una situación en la que esto haya sido un problema. Parece que casi siempre termino en un escenario donde mis variables están establecidas y definidas, o no lo están (indefinidas, sin definir, nulas, en blanco, etc.). No quiere decir que una situación como esta no ocurra en el futuro, pero como parece ser un problema único, no me sorprende que los desarrolladores de PHP no se hayan molestado en poner esto.

Robbie Averill
fuente
La advertencia sobre variables indefinidas es una pista para el programador de que han hecho algo mal en el código. Depuración externa (para la cual hay herramientas fuera del lenguaje), nunca debería ser necesario que un programa detecte dicho estado, porque el programador siempre debe saber qué variables están declarando.
IMSoP
1

Si ejecuto lo siguiente:

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

Me sale un error:

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

Si ejecuto lo siguiente:

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

No recibo el error.

Si tengo una variable que debería establecerse, generalmente hago algo como lo siguiente.

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

o

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

De esa manera, más adelante en el script, puedo usar $ foo de manera segura y saber que "está configurado" y que por defecto es nulo. Luego puedoif ( is_null($foo) ) { /* ... */ } si lo necesito y sé con certeza que la variable existe, incluso si es nula.

La documentación completa de isset lee un poco más de lo que se pegó inicialmente. Sí, devuelve falso para una variable que se estableció anteriormente pero ahora es nula, pero también devuelve falso si una variable aún no se ha establecido (alguna vez) y para cualquier variable que se haya marcado como no establecida. También observa que el byte NULL ("\ 0") no se considera nulo y devolverá verdadero.

Determine si se establece una variable.

Si una variable se ha desarmado con unset (), ya no se establecerá. isset () devolverá FALSE si prueba una variable que se ha establecido en NULL. También tenga en cuenta que un byte NULL ("\ 0") no es equivalente a la constante PHP NULL.

Beau Simensen
fuente
Sacó los documentos de ese enlace. Lea la primera oración, el segundo párrafo de la sección de descripción en el enlace que proporcionó. Es exactamente lo que citó anteriormente.
Zoredache
Esta no es una mala práctica para scripts simples, pero en proyectos complejos (por ejemplo, grandes OO), se vuelve inviable. Además, como dije anteriormente, is_null () devuelve TRUE para las variables que no están establecidas, por lo que realmente no hay razón para hacer lo que está diciendo, excepto para evitar una advertencia de PHP.
chazomaticus
1
Para proyectos "OO grandes" bien diseñados, ¿por qué esto sería un problema? ¿Por qué alguna vez tendrías $ foo en un cuerpo de método que puede no haberse configurado antes de su primer uso?
Beau Simensen el
1

Intenta usar

unset($v)

Parece que la única vez que no se establece una variable es cuando se desarma específicamente ($ v). Parece que su significado de 'existencia' es diferente a la definición de PHP. NULL ciertamente existe, es NULL.

Joe Phillips
fuente
No estoy seguro de lo que quieres decir. Si tiene una matriz, con un elemento 'a', no tiene que desarmar () el elemento 'b' para que el elemento 'b' no exista en PHP, simplemente no existe. Lo mismo con, por ejemplo, las variables globales, que puede considerar como elementos de la matriz $ GLOBALS.
chazomaticus
1
Pero estoy de acuerdo en que existe una variable con un valor NULL.
chazomaticus
0

Tengo que decir que en todos mis años de programación PHP, nunca me he encontrado con un problema al isset()devolver false en una variable nula. OTOH, he encontrado problemas al isset()fallar en una entrada de matriz nula, pero array_key_exists()funciona correctamente en ese caso.

Para alguna comparación, Icon define explícitamente una variable no utilizada como retorno, &nullpor lo que utiliza la prueba nula en Icon para verificar también una variable no establecida. Esto hace las cosas más fáciles. Por otro lado, Visual BASIC tiene múltiples estados para una variable que no tiene un valor (Nulo, Vacío, Nada, ...), y a menudo tiene que verificar más de uno de ellos. Esto se sabe que es una fuente de errores.

staticsan
fuente
0

De acuerdo con el Manual de PHP para la función empty (), "Determine si una variable se considera vacía. Una variable se considera vacía SI NO EXISTE o si su valor es FALSO. Empty () no genera una advertencia si el la variable no existe ". (Mi énfasis.) Eso significa que la función empty () debería calificar como "la mejor manera de probar la existencia de una variable en PHP", según el título Pregunta.

Sin embargo, esto no es lo suficientemente bueno, porque la función empty () puede ser engañada por una variable que sí existe y está establecida en NULL.

Estoy interrumpiendo mi respuesta anterior para presentar algo mejor, porque es menos engorroso que mi respuesta original (que sigue a esta interrupción, para comparar).

  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
  }

Dos líneas simples de código pueden usar la función anterior para revelar si una variable no está definida:

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

Puede seguir esas dos líneas con cualquier cosa apropiada, como este ejemplo:

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

Quería poner la llamada a ob_start () y el ($ testvar === null) dentro de la función, y simplemente pasar la variable a la función, pero no funciona. Incluso si intenta utilizar "pasar por referencia" de la variable a la función, la variable SE CONVIERTE en definida, y luego la función nunca puede detectar que previamente no había sido definida. Lo que se presenta aquí es un compromiso entre lo que quería hacer y lo que realmente funciona.

Lo anterior implica que hay otra forma de evitar siempre encontrarse con el mensaje de error "Variable indefinida". (La suposición aquí es que la prevención de dicho mensaje es la razón por la que desea probar para ver si una variable no está definida).

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

Simplemente llame a esa función antes de hacer algo a su $ testvar:

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

¡El valor de la variable recién instanciada se establece en nulo, por supuesto!

(Finaliza la interrupción)

Entonces, después de estudiar y experimentar, aquí hay algo que funciona:

 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
 }

La explicación: una variable $ er se inicializa a un valor predeterminado de "sin error". Se define una "función de controlador". Si $ testvar (la variable que queremos saber si no está definida o no) pasa la prueba preliminar de función empty (), entonces hacemos la prueba más exhaustiva. Llamamos a la función set_error_handler () para usar la función de controlador previamente definida. Luego hacemos una comparación de identidad simple que involucra $ testvar, QUE SI NO ESTÁ DEFINIDO, ACTIVARÁ UN ERROR. La función de controlador captura el error y prueba específicamente para ver si la razón del error es el hecho de que la variable no está definida. El resultado se coloca en la variable de información de error $ er, que luego podemos probar para hacer lo que queramos como resultado de saber con seguridad si $ testvar se definió o no. Debido a que solo necesitamos la función de controlador para este propósito limitado, restauramos la función original de manejo de errores. La función "myHndlr" solo necesita declararse una vez; el otro código se puede copiar en cualquier lugar que sea apropiado, para $ testvar o cualquier otra variable que queramos probar de esta manera.

vernonner3voltazim
fuente
1
Si la intención es evitar una advertencia de que sus variables no han sido declaradas, entonces la solución es arreglar su código para declararlas correctamente. Su instfunción es básicamente como el @operador de supresión de errores: "Sé que estoy haciendo algo mal aquí, pero solo quiero que ese mensaje desaparezca, sin cambiar realmente el funcionamiento de mi código de ninguna manera".
IMSoP
Los métodos de detección, por otro lado, son ingeniosos, pero sigo creyendo firmemente que nunca debería tener otro uso que no sea hacer eco de los mensajes de advertencia que están captando. (Probablemente debería aclarar que su versión de almacenamiento en búfer de salida requiere un conjunto de
informes de error
0

Creo que la única solución completa es informar avisos con

error_reporting(E_ALL); // Enables E_NOTICE

Pero tendrá que corregir todos los avisos generados por variables indefinidas, constantes, claves de matriz, propiedades de clase, entre otros. Una vez que haya hecho eso, no tendrá que preocuparse por la diferencia entre variables nulas y no declaradas, y la ambigüedad desaparece.

Habilitación de informes de notificación puede no ser una buena alternativa en todas las situaciones, pero existen buenas razones para hacerlo:

¿Por qué debería corregir los errores de E_NOTICE?

En mi caso, llevaba más de un año trabajando en un proyecto sin él, pero solía tener cuidado al declarar variables, por lo que la transición fue rápida.

mikl
fuente
0

La única forma de saber si una variable está definida en el alcance actual ( $GLOBALSno es confiable) es array_key_exists( 'var_name', get_defined_vars() ).

Kostas Podias
fuente
1
Creo que eso es lo que muchas otras personas dijeron antes, ¿o me equivoco?
Stephan Vierkant
-1

Prefiero usar no vacío como el mejor método para verificar la existencia de una variable que a) existe yb) no es nula.

if (!empty($variable)) do_something();
Hal
fuente
2
empty()no comprueba si la variable es nulo, se comprueba si es falso-y, por ejemplo, no una de la ""(una cadena vacía), 0(0 como un entero), 0.0(0 como un flotador), "0"(0 como una cadena), NULL, FALSE, array()(una matriz vacía) y $var;(una variable declarada, pero sin un valor). Digamos que tiene un campo de radio requerido en un formulario con dos entradas con los valores 0y 1. Si lo usa empty()para la validación y el usuario selecciona el que está usando 0, accidentalmente se equivocará "el campo requerido no puede estar vacío". Consulte el manual php.net/manual/en/function.empty.php
Halil Özgür