在PHP中销毁对象的顺序是什么? [英] In which order are objects destructed in PHP?

查看:83
本文介绍了在PHP中销毁对象的顺序是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对象解构的确切顺序是什么?

What is the exact order of object deconstruction?

通过测试,我有一个主意:用于当前示波器的FIFO.

From testing, I have an idea: FIFO for the current scope.

class test1
{
    public function __destruct()
    {
        echo "test1\n";
    }
}

class test2
{
    public function __destruct()
    {
        echo "test2\n";
    }
}

$a = new test1();
$b = new test2();

一次又一次产生相同的结果:

Which produces the same results time and time again:

test1
test2

PHP手册含糊不清(强调我的意思是要突出不确定性) :只要没有其他对特定对象的引用,就会以或在关闭序列期间以任何顺序的方式调用析构函数方法."

The PHP manual is vague (emphasis mine to highlight uncertainty): "The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence."

解构的确切顺序是什么?谁能详细描述PHP使用的销毁命令的实现?而且,如果此顺序在任何PHP版本与所有PHP版本之间不一致,那么任何人都可以查明此命令更改的PHP版本吗?

What is the exact order of deconstruction? Can anyone describe in detail the implementation of destruction order that PHP uses? And, if this order is not consistent between any and all PHP versions, can anyone pinpoint which PHP versions this order changes in?

推荐答案

首先,此处介绍了一些一般的对象销毁顺序:

First of all, a bit on general object destruction order is covered here: https://stackoverflow.com/a/8565887/385378

在这个答案中,我将只关注对象在请求关闭期间仍处于活动状态(即,如果先前未通过引用计数机制或循环垃圾收集器销毁它们)会发生什么情况.

In this answer I will only concern myself with what happens when objects are still alive during the request shutdown, i.e. if they were not previously destroyed through the refcounting mechanism or the circular garbage collector.

PHP请求关闭在 php_request_shutdown 功能.关闭过程中的第一步是调用已注册的关闭函数,然后释放它们.如果其中一个关闭函数持有对某个对象的最后一个引用(或者如果关闭函数本身是一个对象,例如一个闭包),显然这也可能导致对象被破坏.

The PHP request shutdown is handled in the php_request_shutdown function. The first step during the shutdown is calling the registered shutdown functions and subsequently freeing them. This can obviously also result in objects being destructed if one of the shutdown functions was holding the last reference to some object (or if the shutdown function itself was an object, e.g. a closure).

关闭功能运行后,下一步是您感兴趣的步骤:PHP将运行 zend_call_destructors ,然后调用> c2> .此函数将(尝试)分三步调用所有析构函数:

After the shutdown functions have run the next step is the one interesting to you: PHP will run zend_call_destructors, which then invokes shutdown_destructors. This function will (try to) call all destructors in three steps:

  1. 第一个PHP将尝试破坏全局符号表中的对象.发生这种情况的方式非常有趣,因此我复制了以下代码:

  1. First PHP will try to destroy the objects in the global symbol table. The way in which this happens is rather interesting, so I reproduced the code below:

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函数将向后走符号表 ,即从最后创建的变量开始,再向最先创建的变量前进.行走时,它将销毁所有引用计数为1的对象.执行此迭代,直到没有其他对象被销毁为止.

The zend_hash_reverse_apply function will walk the symbol table backwards, i.e. start with the variable that was created last and going towards the variable that was created first. While walking it will destroy all objects with refcount 1. This iteration is performed until no further objects are destroyed with it.

因此,这基本上是在执行以下操作:a)删除全局符号表中的所有未使用的对象b)如果有新的未使用的对象,也将其删除c)依此类推.使用这种销毁方式,因此对象可以依赖于析构函数中的其他对象.除非全局范围内的对象具有复杂的(例如,循环的)相互关系,否则这通常可以很好地工作.

So what this basically does is a) remove all unused objects in the global symbol table b) if there are new unused objects, remove them too c) and so on. This way of destruction is used so objects can depend on other objects in the destructor. This usually works fine, unless the objects in the global scope have complicated (e.g. circular) interrelations.

全局符号表的破坏与所有其他符号表的破坏有很大不同.通常,通过遍历它们 forward 并将它们的refcount放到所有对象上,可以破坏符号表.另一方面,对于全局符号表,PHP使用了一种更聪明的算法来尝试尊重对象的依赖性.

The destruction of the global symbol table differs significantly from the destruction of all other symbol tables. Normally symbol tables are destructed by walking them forward and just dropping the refcount on all objects. For the global symbol table on the other hand PHP uses a smarter algorithm that tries to respect object dependencies.

第二步是调用所有剩余的析构函数:

The second step is calling all remaining destructors:

zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);

这将遍历所有对象(按创建顺序)并调用其析构函数.请注意,这仅调用"dtor"处理程序,而不调用"free"处理程序.这种区别在内部很重要,并且基本上意味着PHP仅会调用__destruct,而实际上不会破坏该对象(甚至不会更改其refcount).因此,如果其他对象引用了dtord对象,则该对象仍然可用(即使已经调用了析构函数).从某种意义上说,他们将使用某种半毁灭"的对象(请参见下面的示例).

This will walk all objects (in order of creation) and call their destructor. Note that this only calls the "dtor" handler, but not the "free" handler. This distinction is internally important and basically means that PHP will only call __destruct, but will not actually destroy the object (or even change its refcount). So if other objects reference the dtored object, it will still be available (even though the destructor was already called). They will be using some kind of "half-destroyed" object, in a sense (see example below).

如果在调用析构函数时停止执行(例如由于die导致的),则会调用其余的析构函数.相反,PHP将标记对象已被破坏:

In case the execution is stopped while calling the destructors (e.g. due to a die) the remaining destructors are not called. Instead PHP will mark the objects are already destructed:

zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);

这里重要的一课是,在PHP中,析构函数不一定称为.发生这种情况的情况相当少见,但有可能发生.此外,这意味着在这之后不再调用任何析构函数,因此(相当复杂的)关闭过程的其余部分不再重要.在关闭过程中的某个时刻,所有对象都将被释放,但是由于已经调用了析构函数,因此对于用户层来说这并不明显.

The important lesson here is that in PHP a destructor is not necessarily called. The cases when this happens are rather rare, but it can happen. Furthermore this means that after this point no more destructors will be called, so the remainder of the (rather complicated) shutdown procedure does not matter anymore. At some point during the shutdown all the objects will be freed, but as the destructors have already been called this is not noticeable for userland.

我应该指出,这是当前的关机顺序.过去已经改变了,将来可能会改变.这不是您应该依靠的东西.

I should point out that this is the shutdown order as it currently is. This has changed in the past and may change in the future. It's not something you should rely on.

这里是一个示例,显示有时可以使用已经具有其析构函数的对象:

Here is an example showing that it is sometimes possible to use an object that already had its destructor called:

<?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) {});

上述脚本将输出destructed .

这篇关于在PHP中销毁对象的顺序是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆