douche3244 2012-12-31 01:44
浏览 19
已采纳

在PHP中破坏对象的顺序是什么?

What is the exact order of object deconstruction?

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

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

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

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

Which produces the same results time and time again:

test1
test2

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."

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?

  • 写回答

2条回答 默认 最新

  • dpauxqt1281 2012-12-31 12:43
    关注

    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.

    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).

    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. 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)));
      

      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.

      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.

      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.

    2. The second step is calling all remaining destructors:

      zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);
      

      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).

    3. 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);
      

      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.

    Example for using an already destructed object

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

    The above script will output destructed.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 高德地图点聚合中Marker的位置无法实时更新
  • ¥15 DIFY API Endpoint 问题。
  • ¥20 sub地址DHCP问题
  • ¥15 delta降尺度计算的一些细节,有偿
  • ¥15 Arduino红外遥控代码有问题
  • ¥15 数值计算离散正交多项式
  • ¥30 数值计算均差系数编程
  • ¥15 redis-full-check比较 两个集群的数据出错
  • ¥15 Matlab编程问题
  • ¥15 训练的多模态特征融合模型准确度很低怎么办