new - php orientado a objetos
¿En qué orden se destruyen los objetos en PHP? (2)
¿Cuál es el orden exacto de la deconstrucción de objetos?
De las pruebas, tengo una idea: FIFO para el alcance actual.
class test1
{
public function __destruct()
{
echo "test1/n";
}
}
class test2
{
public function __destruct()
{
echo "test2/n";
}
}
$a = new test1();
$b = new test2();
Lo que produce los mismos resultados una y otra vez:
test1
test2
El manual de PHP es vago (el énfasis es mío para resaltar la incertidumbre): "Se llamará al método destructor tan pronto como no haya otras referencias a un objeto en particular, o en cualquier orden durante la secuencia de apagado ".
¿Cuál es el orden exacto de deconstrucción? ¿Puede alguien describir en detalle la implementación del orden de destrucción que usa PHP? Y, si este orden no es consistente entre cualquiera y todas las versiones de PHP, ¿alguien puede identificar en qué versiones de PHP cambia este orden?
¿Cuál es el orden exacto de deconstrucción? ¿Puede alguien describir en detalle la implementación del orden de destrucción que usa PHP? Y, si este orden no es consistente entre cualquiera y todas las versiones de PHP, ¿alguien puede identificar en qué versiones de PHP cambia este orden?
Puedo responder tres de estos por ti, de una manera un tanto indirecta.
El orden exacto de destrucción no siempre es claro, pero siempre es coherente, dado un solo script y una versión de PHP. Es decir, el mismo script que se ejecuta con los mismos parámetros que crean objetos en el mismo orden básicamente siempre obtendrá el mismo orden de destrucción siempre que se ejecute en la misma versión de PHP.
El proceso de apagado, lo que desencadena la destrucción de objetos cuando se detiene la ejecución del script, ha cambiado en el pasado reciente, al menos dos veces de una manera que impactó indirectamente la orden de destrucción. Uno de estos dos errores introducidos en algún código antiguo que tuve que mantener.
El grande estaba de vuelta en 5.1. Antes de 5.1, la sesión del usuario se escribía en el disco al comienzo de la secuencia de apagado, antes de la destrucción del objeto. Esto significaba que los manejadores de sesión podían acceder a cualquier cosa que quedara sobre objetos, como, por ejemplo, objetos de acceso a bases de datos personalizados. En 5.1, las sesiones se escribieron después de un barrido de destrucción de objetos. Para conservar el comportamiento anterior, tenía que registrar manualmente una función de apagado (que se ejecuta en orden de definición al inicio del cierre antes de la destrucción) para poder escribir con éxito los datos de la sesión si las rutinas de escritura necesitaban un objeto (global).
No está claro si el cambio 5.1 fue pensado o si fue un error. He visto a ambos reclamados.
El siguiente cambio fue en 5.3, con la introducción del nuevo sistema de recolección de basura . Si bien el orden de las operaciones en el cierre siguió siendo el mismo, el orden preciso de destrucción ahora podría cambiar según el recuento de referencias y otros horrores encantadores.
La respuesta de NikiC tiene detalles sobre la implementación interna actual (en el momento de la redacción) del proceso de cierre.
Una vez más, esto no está garantizado en ninguna parte, y la documentación le dice muy expresamente que nunca asuma una orden de destrucción .
En primer lugar, un poco sobre el orden general de destrucción de objetos se trata aquí: https://.com/a/8565887/385378
En esta respuesta solo me ocuparé de lo que ocurra cuando los objetos aún estén vivos durante el cierre de la solicitud, es decir, si no se destruyeron previamente a través del mecanismo de recuento o el recolector de basura circular.
El cierre de la solicitud de PHP se maneja en la función php_request_shutdown
. El primer paso durante el apagado es llamar a las funciones de apagado registradas y liberarlas posteriormente. Obviamente, esto también puede hacer que los objetos se destruyan si una de las funciones de apagado mantiene la última referencia a algún objeto (o si la función de cierre en sí es un objeto, por ejemplo, un cierre).
Después de que las funciones de apagado se hayan ejecutado, el siguiente paso es el que le interesa: PHP ejecutará zend_call_destructors
, que luego invoca shutdown_destructors
. Esta función (intentará) llamará a todos los destructores en tres pasos:
Primero PHP intentará destruir los objetos en la tabla de símbolos global. La forma en que esto sucede es bastante interesante, así que reproduje el siguiente código:
int symbols; do { symbols = zend_hash_num_elements(&EG(symbol_table)); zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_CC); } while (symbols != zend_hash_num_elements(&EG(symbol_table)));
La función
zend_hash_reverse_apply
recorrerá la tabla de símbolos hacia atrás , es decir, comenzará con la variable que se creó en último lugar y se dirigirá hacia la variable que se creó primero. Mientras camina, destruirá todos los objetos con refcount 1. Esta iteración se realiza hasta que no se destruyan más objetos con ella.Entonces, lo que básicamente hace es a) eliminar todos los objetos no utilizados en la tabla de símbolos global b) si hay nuevos objetos no utilizados, eliminarlos también c) y así sucesivamente. Esta forma de destrucción se utiliza para que los objetos puedan depender de otros objetos en el destructor. Esto generalmente funciona bien, a menos que los objetos en el alcance global tengan interrelaciones complicadas (por ejemplo, circulares).
La destrucción de la tabla de símbolos global difiere significativamente de la destrucción de todas las demás tablas de símbolos. Normalmente, las tablas de símbolos se destruyen avanzándolas hacia adelante y simplemente soltando el refcount en todos los objetos. Por otro lado, para la tabla de símbolos global, PHP utiliza un algoritmo más inteligente que intenta respetar las dependencias de los objetos.
El segundo paso es llamar a todos los destructores restantes:
zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);
Esto recorrerá todos los objetos (en orden de creación) y llamará a su destructor. Tenga en cuenta que esto solo llama al controlador "dtor", pero no al controlador "libre". Esta distinción es importante internamente y básicamente significa que PHP solo llamará
__destruct
, pero no destruirá realmente el objeto (o incluso cambiará su refcount). Entonces, si otros objetos hacen referencia al objeto dtorado, seguirá estando disponible (aunque ya se haya llamado al destructor). Estarán usando algún tipo de objeto "medio destruido", en cierto sentido (vea el ejemplo a continuación).En caso de que la ejecución se detenga al llamar a los destructores (por ejemplo, debido a un
die
), los destructores restantes no son llamados. En su lugar, PHP marcará los objetos que ya están destruidos:zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);
La lección importante aquí es que en PHP no se llama necesariamente un destructor. Los casos en que esto sucede son bastante raros, pero pueden suceder. Además, esto significa que después de este punto ya no se llamarán más destructores , por lo que el resto del procedimiento de apagado (bastante complicado) ya no importa. En algún momento durante el apagado, todos los objetos se liberarán, pero como ya se ha llamado a los destructores, esto no se nota para el usuario.
Debo señalar que esta es la orden de cierre tal como está actualmente. Esto ha cambiado en el pasado y puede cambiar en el futuro. No es algo en lo que debas confiar.
Ejemplo para usar un objeto ya destruido
Aquí hay un ejemplo que muestra que a veces es posible usar un objeto que ya tenía su destructor llamado:
<?php
class A {
public $state = ''not destructed'';
public function __destruct() { $this->state = ''destructed''; }
}
class B {
protected $a;
public function __construct(A $a) { $this->a = $a; }
public function __destruct() { var_dump($this->a->state); }
}
$a = new A;
$b = new B($a);
// prevent early destruction by binding to an error handler (one of the last things that is freed)
set_error_handler(function() use($b) {});
La secuencia de comandos de arriba será desechada .