Ответ 1
Прежде всего, здесь описывается бит общего порядка уничтожения объектов: fooobar.com/questions/307420/...
В этом ответе я буду только заботиться о том, что происходит, когда объекты все еще живы во время выключения запроса, т.е. если они ранее не были уничтожены с помощью механизма refcounting или кругового сборщика мусора.
Отключение запроса PHP обрабатывается в php_request_shutdown
. Первый шаг во время выключения - это вызов зарегистрированных функций выключения и последующее их освобождение. Это может также привести к разрушению объектов, если одна из функций выключения удерживала последнюю ссылку на какой-либо объект (или если сама функция выключения была объектом, например закрытием).
После запуска функций останова вам будет интересен следующий шаг: PHP запустит zend_call_destructors
, который затем вызывает shutdown_destructors
. Эта функция (попытается) вызвать всех деструкторов в три этапа:
-
Первый PHP попытается уничтожить объекты в глобальной таблице символов. То, как это происходит, довольно интересно, поэтому я воспроизвел следующий код:
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)));
Функция
zend_hash_reverse_apply
будет перемещаться по таблице символов назад, т.е. начать с переменной, которая была создана последней, и перейти к первой, которая была создана. Во время ходьбы он уничтожит все объекты с помощью refcount 1. Эта итерация выполняется до тех пор, пока с ней не будут уничтожены никакие другие объекты.Так что в основном это: a) удалить все неиспользуемые объекты в глобальной таблице символов; b) если есть новые неиспользуемые объекты, удалите их c) и так далее. Этот способ разрушения используется, поэтому объекты могут зависеть от других объектов в деструкторе. Обычно это нормально работает, если объекты в глобальном масштабе не имеют сложных (например, круговых) взаимосвязей.
Уничтожение глобальной таблицы символов значительно отличается от уничтожения всех других таблиц символов. Обычно таблицы символов разрушаются, перемещая их вперед и просто отбрасывая refcount для всех объектов. С другой стороны, для глобальной таблицы символов PHP использует более интеллектуальный алгоритм, который пытается уважать зависимости объектов.
-
Второй шаг - вызов всех остальных деструкторов:
zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);
Это будет идти по всем объектам (в порядке их создания) и вызвать их деструктор. Обратите внимание, что это вызывает только обработчик "dtor", но не "свободный" обработчик. Это различие является внутренне важным и в основном означает, что PHP будет вызывать только
__destruct
, но фактически не уничтожит объект (или даже не изменит его refcount). Поэтому, если другие объекты ссылаются на объект dtored, он все равно будет доступен (хотя деструктор уже был вызван). Они будут использовать какой-то "полуразрушенный" объект в некотором смысле (см. Пример ниже). -
В случае остановки выполнения вызова при вызове деструкторов (например, из-за
die
) остальные деструкторы называются not. Вместо этого PHP отметит, что объекты уже разрушены:zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);
Важным уроком здесь является то, что в PHP деструктор не обязательно называется. Случаи, когда это происходит, довольно редки, но это может произойти. Кроме того, это означает, что после этого момента больше не будут вызваны деструкторы, поэтому оставшаяся часть (довольно сложная) процедура останова не имеет значения. В какой-то момент во время выключения все объекты будут освобождены, но поскольку деструкторы уже были вызваны, это не заметно для пользовательской области.
Я должен указать, что это порядок выключения, как это сейчас. Это изменилось в прошлом и может измениться в будущем. Это не то, на что вы должны положиться.
Пример использования уже разрушенного объекта
Вот пример, показывающий, что иногда можно использовать объект, который уже вызвал его деструктор:
<?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) {});
Вышеупомянутый script выведет destructed
.