我可以相信PHP __destruct()方法会被调用吗?

43
在 PHP5 中,__destruct() 方法是否保证对每个对象实例都会调用?程序中的异常会阻止这种情况吗?
6个回答

45

值得一提的是,在具有自己析构函数的子类中,父类析构函数不会自动调用。

如果父类执行必要的清理操作,则必须在子类__destruct()方法中显式调用parent::__destruct()


21
我相信只有子类实现了自己的__destruct()方法时,该说法才是正确的,否则将会调用父类的__destruct()方法。 - Geoff
我认为这并不是回答问题的方法,但这是一个不错的附注... - SteveExdia

37
析构函数将在所有引用被释放或脚本终止时被调用。我认为这意味着当脚本正常终止时。我会说关键的异常并不能保证析构函数被调用。 PHP文档有点简略,但它确实指出析构函数中的异常会引发问题。

1
我可以说,在关闭过程中发生异常可能会导致分段错误。 - KingCrunch
4
同时,在已经处于 exit() 序列中的析构函数内调用 exit() 可能会导致进程在不调用其他析构函数的情况下被终止。 - Jason Cohen
1
抱歉,我无法理解您的示例。能否请您详细说明一下?@JasonCohen - revo
3
如果你需要即使在 PHP 致命错误的情况下也调用析构函数,那么可以在构造函数中将其注册为关闭处理程序:public function __construct() { register_shutdown_function(array($this, '__destruct')); }。这种解决方案的成本是对象引用(以及对象本身)存在到 PHP 脚本执行结束。然而,在某些情况下,这仍然是值得的,比如在析构函数中进行大型临时文件清除。 - Mojo
我不知道为什么这个回答的投票比排名第一的回答低,因为这个才是正确的答案。 - SteveExdia

14
根据我的经验,在PHP 5.3中析构函数总是会被调用,但需要注意的是,如果某些代码调用了exit()或发生致命错误,PHP将以“任意”顺序调用析构函数(我认为实际顺序是在内存中的顺序或分配给对象的内存顺序。在实践中,这个顺序几乎总是有问题的)。这在PHP文档中被称为“关闭顺序”。 PHP文档中关于析构函数的说明如下:
“PHP 5引入了一个类似于其他面向对象语言(如C++)的析构函数概念。当特定对象没有其他引用时,析构函数方法将立即被调用,或在关闭顺序期间以任何顺序被调用。”
因此,如果您拥有一个持有对Y的引用的X类,则X的析构函数可能在Y的析构函数已经被调用之后才被调用。希望对Y的引用不是那么重要...正式地说,这不是一个bug,因为它已经被记录在文档中。
然而,要解决这个问题非常困难,因为官方 PHP 并没有提供一种方式来判断析构函数是被正常调用的(析构函数按正确顺序被调用)还是以“任何”顺序被调用,其中您不能使用来自引用对象的数据,因为这些可能已经被销毁。可以使用 debug_backtrace() 和检查堆栈来解决缺乏检测的问题。在 PHP 5.3 中,正常堆栈的缺失似乎意味着“关闭序列”,但这也是未定义的。如果存在循环引用,则这些对象的析构函数在 PHP 5.2 或更低版本中根本不会被调用,并且在“关闭序列”期间将以“任何”顺序被调用,在这种情况下,逻辑上不存在一个正确的顺序,因此对于这些任何顺序都可以的。

当然,如果在其他析构函数中调用了 exit(),则不会调用任何剩余的析构函数 (http://php.net/manual/en/language.oop5.decon.php)。如果发生致命错误(许多可能的原因,例如尝试从任何其他析构函数中抛出异常可能是其中之一),则不会调用任何剩余的析构函数。

当然,如果 PHP 引擎遇到分段错误或发生其他内部错误,则所有赌注都是无效的。

如果您想了解“关闭序列”的当前实现,请参见https://dev59.com/I2Yq5IYBdhLWcg3w7EwF#14101767。请注意,这个实现可能会在未来的 PHP 版本中发生改变。


PHP的理念:“我坚持当前的实现,因为我认为这是最好的权衡。——Andi”。来源:http://www.mail-archive.com/internals@lists.php.net/msg06393.html - Mikko Rantalainen
3
基本上有两类程序员:(1)在析构函数中使用全局变量的程序员和(2)不在析构函数中使用全局变量且通过使用对相关对象的对象引用来正确标记依赖项的程序员。由于PHP试图针对底端,因此它偏向于支持(1)而非(2)。组(2)留下了无法正常工作的代码,并且没有官方方法来解决这个问题。 - Mikko Rantalainen
参见:https://dev59.com/pXE95IYBdhLWcg3wQ7uc - Mikko Rantalainen

10

它在5.3中被关闭了。 - robsch

8

2
从文档中得知:"可以进行多次调用register_shutdown_function(),每次调用的顺序与注册的顺序相同。如果在一个已注册的关闭函数中调用exit(),则处理将完全停止,并且不会调用任何其他已注册的关闭函数。" 如果在您的关闭函数之前运行了其他关闭函数,则不能保证会调用您的关闭函数。 - Mikko Rantalainen

1
值得注意的是,虽然析构函数可能会被调用,但不能保证它们会被调用,正如The Daily WTF中的故事所说明的那样:
“好吧,”我叹了口气。尽管让他描述刚刚捏造的进程编组概念很有诱惑力,但我决定让步。就在那时,我想到了完美的反驳。“但是如果你只是,比方说,拔掉插头呢?当电脑被关闭时,Finally块 __destruct不会执行!”
我以为开发人员会跳回来责备我“不合理的情况”,但他们的脸却变得苍白。他们缓慢地转过头来看着彼此。很明显,他们犯了许多人之前也犯过的错误:认为Try-Finally像数据库事务一样不可靠。
“而且……嗯……”我慢慢地说着,打破了尴尬的沉默,“这就是……你应该……永远不要将关键的……业务交易代码放在finally块中的原因。”
如果你只是释放资源或做一些日志记录,可以假设析构函数将被调用,但它们不安全用于撤销先前的操作以尝试使一些代码ACID兼容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接