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 isset
se 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é isset
la reacción a esta null
es la conducta lógica.
Casos de uso real (con soluciones)
1. Teclas de matriz
Las matrices pueden tratarse como colecciones de variables, unset
y isset
tratarlas 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 null
valores.
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
, protected
o 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_globals
caracterí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 global
palabra clave o $GLOBALS
matriz.
En primer lugar, register_globals
es poco probable que produzca una null
variable inesperadamente , ya que los valores GET, POST y cookie siempre serán cadenas (con ''
todavía regresando true
de 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 null
es solo un problema si esto sobrescribe alguna inicialización previa. "Sobreescribir" una variable no inicializada null
solo 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_vars
ycompact
Algunas funciones raramente utilizadas en PHP, como get_defined_vars
y 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. isset
es 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 htmlspecialchars
para 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 $a
y $b
en 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_function
puede regresar null
, existe la posibilidad de que echo
no se llegue a él aunque se haya some_test
devuelto true
. La intención del programador era detectar cuándo $result
nunca 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 $result
que 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 $result
un "valor terminal" que some_function
nunca 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_function
tiene un tipo de retorno extremadamente impredecible (que probablemente sea un mal signo en sí mismo), entonces $found
podría usarse un valor booleano adicional, por ejemplo , en su lugar.
Pensamiento experimento uno: la very_null
constante
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_function
no 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 $result
que 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 if
pregunta 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 isset
que 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_count
y 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_null
constante, 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.