El título puede parecer un poco tonto, pero estoy totalmente en serio con esto. Hoy en el trabajo me encontré con un comportamiento extraño de PHP que no podía explicar. Afortunadamente, este comportamiento se solucionó en PHP 7.4, por lo que parece que alguien también se topó con eso.
Hice un pequeño ejemplo para ilustrar lo que salió mal:
<?php
class A {
private $a = 'This is $a from A';
public $b = 'This is $b from A';
public function __sleep(): array
{
var_dump(array_keys(get_object_vars($this)));
return [];
}
}
class B extends A
{
public $a = 'This is $a from B';
}
$b = new B;
serialize($b);
Ejecute este código aquí: https://3v4l.org/DBt3o
Aquí hay una pequeña explicación de lo que está sucediendo aquí. Tenemos las clases A y B, que comparten una propiedad $a
. Los lectores cuidadosos notaron que la propiedad $a
tiene dos visibilidades diferentes (pública, privada). Nada lujoso hasta ahora. La magia ocurre en el __sleep
método que se llama mágicamente cuando hacemos serialize
nuestra instancia. Queremos tener todas las variables de objeto que se obtiene con get_object_vars
reducir esto a sólo las teclas con array_keys
y salida con todo var_dump
.
Esperaría algo como esto (esto sucede desde PHP 7.4 y es mi salida esperada):
array(2) {
[0]=>
string(1) "b"
[1]=>
string(1) "a"
}
Pero lo que obtengo es esto:
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "a"
}
¿Cómo podría ser que PHP entregue una matriz con dos claves completamente idénticas? ¿Quién puede explicar qué sucede aquí internamente porque en PHP simple no puedo generar una matriz con dos claves completamente idénticas? ¿O extraño algo obvio aquí?
Al principio, mis compañeros de trabajo no querían creerme, pero ninguno de ellos tuvo una buena explicación después de entender lo que está sucediendo aquí.
Realmente me encantaría ver una buena explicación.
fuente
var_dump(array_keys((array)$this));
Respuestas:
No pude encontrar un informe para el error en la pregunta, pero curiosamente parece que este commit aborda lo mismo:
El código de prueba está bien escrito, con un simple cambio podríamos tenerlo aquí:
Llamando
var_dump()
a$props
espectáculos:De vuelta a su pregunta:
Sí, no puede tener una matriz con dos claves idénticas:
resultados en:
pero no me permita estar de acuerdo con usted
two completely identical keys
ya que estos dos elementos con nombres de clave idénticos no se almacenan con claves idénticas internamente dentro de una tabla hash. Es decir, se almacenan como enteros únicos, excepto en posibles colisiones y, como esto ha estado ocurriendo internamente, se ignoró la restricción de las entradas del usuario.fuente
Después de jugar un poco con esto, parece que esto no depende
__sleep()
.Aparentemente, este siempre fue el caso en versiones anteriores de PHP 7 (pero aparentemente no en PHP 5). Este ejemplo más pequeño muestra el mismo comportamiento.
Salida de PHP 7.0 - 7.3
Creo que lo privado
$a
en el padre es una propiedad diferente al público$a
en el niño. Cuando se cambia la visibilidad enB
que no va a modificar la visibilidad del$a
enA
, en realidad está haciendo una nueva propiedad con el mismo nombre. Sivar_dump
el objeto en sí mismo puede ver ambas propiedades.Sin embargo, no debería tener mucho efecto, ya que no podría acceder a la propiedad privada desde la clase principal en la clase secundaria, aunque puede ver que existe en esas versiones anteriores de PHP 7.
fuente
a
devuelve el segundoThis is $a from A
.Mi pareja centavos.
No sé sobre compañeros de trabajo, pero no creía y pensé que esto era una broma.
Para la explicación, definitivamente el problema está en la variable "get_object_vars", ya que devuelve una matriz asociativa duplicada. Debería haber dos valores de tabla hash diferentes para la misma clave (que no es posible, pero la única explicación viene). No pude encontrar ningún enlace a la implementación interna get_object_vars () (aunque PHP se basa en código abierto, por lo que es posible obtener código y depurar de alguna manera). También estoy pensando (sin éxito hasta ahora) en la forma de ver la representación de matriz en la memoria, incluida la tabla hash. Por otro lado, pude usar las funciones "legales" de PHP y hacer algunos trucos con la matriz.
Este es mi intento de probar alguna funcionalidad con esa matriz asociativa. A continuación se muestra la salida. No se requiere explicación: puede ver todo y probar el mismo código usted mismo, por lo que solo algunos comentarios.
Mi entorno es php 7.2.12 x86 (32 bit) - bueno ... sí, lástima de mí
Me deshago de la "magia" y la serialización, solo dejo cosas que traen problemas.
Completó algunas refactorizaciones en las clases A y B, así como la llamada a la función.
La clave $ bajo la clase A debe ser privada, de lo contrario no debe haber ningún milagro.
Varias pruebas de piezas: nada interesante, excepto el problema principal.
Prueba parcial copy_vars - ¡la matriz se copió con duplicado! La nueva clave se agregó con éxito.
Parte de iteración de prueba y new_vars: la iteración pasó por duplicado sin problema, pero la nueva matriz no aceptó duplicado, la última clave fue aceptada.
Reemplazo de prueba: reemplazo completado en la segunda clave, duplicación de la suspensión.
Prueba de ksort: la matriz no cambió, no se reconoció el duplicado
Prueba de clasificación: después de cambiar valores y ejecutar una clasificación, pude cambiar el orden e intercambiar claves duplicadas. Ahora la primera clave se convierte en la segunda y la nueva clave es la que llamamos matriz por clave o asignamos una clave. ¡Como resultado pude cambiar ambas teclas! Antes pensaba que la clave duplicada es invisible, ahora está claro que la última clave funciona cuando hacemos referencia o asignamos la clave.
Conversión a objeto stdClass - ¡de ninguna manera! ¡Solo se acepta la última clave!
Pruebas de desarmado: ¡buen trabajo! Última clave eliminada, pero la primera clave está a cargo y la única clave restante, sin duplicados.
Prueba de representación interna: este es un tema para agregar algunas otras funciones y ver la fuente de duplicación. Estoy pensando en eso ahora.
El resultado resultante está debajo del código.
Salida ahora:
fuente