在 PHP 的面向对象编程中,很容易接触到析构函数的一些应用。在程序执行结束时,PHP 会自动调用执行对象的析构函数,通过利用这种特性,我们可以在析构函数里释放一些对象资源。但析构函数稍为运用不当,也会容易成为程序埋下难以发觉的 BUG。这里先提两个问题:

  • PHP 的析构函数一定会被执行到吗?
  • 如果不一定会被执行到,那什么情况下会出现析构函数在程序结束前不会被执行?

了解析构函数执行

先看一个例子,以帮助我们初步认识到析构函数的执行流程。

  • 新建文件 test.php
<?php

class Foo
{
    public function __construct() {
        echo __METHOD__, "\n";
    }

    public function __destruct() {
        echo __METHOD__, "\n";
    }
}

class Bar
{
    public function __construct() {
        echo __METHOD__, "\n";
    }

    public function __destruct() {
        echo __METHOD__, "\n";
    }
}

$foo = new Foo();
$bar = new Bar();
  • 执行
    $ php test.php
    
  • 输出
    Foo::__construct
    Bar::__construct
    Bar::__destruct
    Foo::__destruct
    

由上面的程序和运行结果我们可以看出,PHP 的析构函数自动执行顺序是和对象创建的顺序相反的顺序线性执行的,也就是相当于一个堆栈(后进先出)。由于顺序线性运行的程序都有一个特性,那就是中间任何一个环节出现问题导致程序执行不下去了,那程序就直接退出。那可以推断一下:

如果其中一个析构函数中断了 PHP 的执行,那这个析构函数创建前的所有对象的析构函数都不会被执行到了。

验证方法

为了验证上面的结论,下面分别以会中断 PHP 程序继续执行的几个函数实验一下。

  • 在析构函数里面使用 exit/die 函数退出程序,或在程序中直接抛出异常。
<?php

class Foo
{
    public function __construct() {
        echo __METHOD__, "\n";
    }

    public function __destruct() {
        echo __METHOD__, "\n";
    }
}

class Bar
{
    public function __construct() {
        echo __METHOD__, "\n";
    }

    public function __destruct() {
        echo __METHOD__, "\n";
        die();
       //exit();
       //throw new Exception();
    }
}

$foo = new Foo();
$bar = new Bar();
  • 执行
    $ php test.php
    
  • 输出
    Foo::__construct
    Bar::__construct
    Bar::__destruct
    

从执行结果可以证实,当在执行的析构函数时中止程序执行,会导致这对象创建前的对象的析构函数无法执行。对于这种情况在PHP的官方文档 - 构造函数和析构函数中也有提及,只不过学习过程中很容易被忽略。

结论

  • PHP 的析构函数是按对象创建的先后,以堆栈后进先出的特性线性执行。
  • 如果在析构函数中退出了程序,该对象创建之前所有对象的析构函数都不会被执行到了。

在程序设计中有用到析构函数特别要注意最后一点,避免在程序发生了不可预料的问题。