php中__destruct与register_shutdown_function执行的先后顺序问题


Posted in PHP onOctober 17, 2014

根据php手册的解析。

__destruct是

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

register_shutdown_function

Registers a callback to be executed after script execution finishes or exit() is called. 注册一个回调函数,此函数在脚本运行完毕或调用exit()时执行。

从字面上理解,__destruct是对象层面的,而register_shutdown_function是整个脚本层面的,理应register_shutdown_function的级别更高,其所注册的函数也应最后执行。为证实我们的猜测,我们写一段脚本:

register_shutdown_function(function(){echo 'global';});

    class A {

        public function __construct(){

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    new A;

执行结果:

A::__destruct 

global

完全证实了我们的猜测,它按照对象->脚本的顺序被执行了。

但如果我们在对象中注册了register_shutdown_function呢?它还是一样的顺序吗?!

class A {

        public function __construct(){

            register_shutdown_function(function(){echo 'local', '<br/>';});

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    new A;

结果:

local 

A::__destruct

可以看到register_shutdown_function先被调用了,最后才是执行对象的__destruct。这表明register_shutdown_function注册的函数被当作类中的一个方法?!不得而知,这可能需要查看php源代码才能解析了。

我们可以扩大范围查看情况:

register_shutdown_function(function(){echo 'global', '<br/>';});

    class A {

        public function __construct(){

            register_shutdown_function(array($this, 'op'));

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

        public function op()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    class B {

        public function __construct()

        {

            register_shutdown_function(array($this, 'op'));

            $obj = new A;

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

        public function op()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    $b = new B;

我们在全局注册一个register_shutdown_function函数,在类AB中又各注册了一个,而且类中分别还有析构方法。最后运行结果会怎样呢?

global 

B::op 

A::op 

A::__destruct 

B::__destruct

结果完全颠覆了我们的想像,register_shutdown_function函数无论在类中注册还是在全局注册,它都是先被执行,类中执行的顺序就是它们被注册的先后顺序。如果我们再仔细研究,全局的register_shutdown_function函数无论放在前面还是后面都是这个结果,事情似乎有了结果,那就是register_shutdown_function比__destruct先执行,全局的register_shutdown_function函数又先于类中注册的register_shutdown_function先执行。

且慢,我无法接受这个结果,按照这样的结论,难道说脚本已经结束后还可以再执行__destruct?!因此,我还要继续验证这个结论---去掉类中注册register_shutdown_function,而保留全局register_shutdown_function:

class A {

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    class B {

        public function __construct()

        {

            $obj = new A;

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    register_shutdown_function(function(){echo 'global', '<br/>';});

输出:

A::__destruct 

global 

B::__destruct

结果令人茫然,A、B两个类的析构函数执行顺序无可质疑,因为B中调用了A,类A肯定比B先销毁,但全局的register_shutdown_function函数又怎么夹在它们中间被执行?!费解。

按照手册的解析,析构函数也可在调用exit时执行。

析构函数即使在使用 exit()终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。

如果在函数中调用exit,它们又如何被调用的呢?

class A {

        public function __construct(){

            register_shutdown_function(array($this, 'op'));

            exit;

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

        public function op()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    class B {

        public function __construct()

        {

            register_shutdown_function(array($this, 'op'));

            $obj = new A;

        }

        public function __destruct()

        {

            echo __class__,'::',__function__,'<br/>';

        }

        public function op()

        {

            echo __class__,'::',__function__,'<br/>';

        }

    }

    register_shutdown_function(function(){echo 'global', '<br/>';});

    $b = new B;

输出:

global 

B::op 

A::op 

B::__destruct 

A::__destruct

这个顺序与上述第三个例子相似,不同的且令人不可思议的是B类的析构函数先于类A执行,难道销毁B后类A的所有引用才被全部销毁?!不得而知。

结论:
1、尽量不要在脚本中将register_shutdown_function与__destruct混搭使用,它们的行为完全不可预测。
1、因为对象在相互引用,因此我们无法测知对象几时被销毁,当需要按顺序输出内容时,不应把内容放在析构函数__destruct里;
2、尽量不要在类中注册register_shutdown_function,因为它的顺序难以预测(只有调用这个对象时才会注册函数),而且__destruct完全可以代替register_shutdown_function;
3、如果需要在脚本退出时执行相关动作,最好在脚本开始时注册register_shutdown_function,并把所有动作放在一个函数里。
敬请大家指正。

PHP 相关文章推荐
聊天室php&amp;mysql(二)
Oct 09 PHP
分享一个php 的异常处理程序
Jun 22 PHP
destoon实现底部添加你是第几位访问者的方法
Jul 15 PHP
PHP实现利用MySQL保存session的方法
Aug 23 PHP
Codeigniter(CI)框架分页函数及相关知识
Nov 03 PHP
php调用新浪短链接API的方法
Nov 08 PHP
十大使用PHP框架的理由
Sep 26 PHP
浅析PHP7新功能及语法变化总结
Jun 17 PHP
PHP生成唯一ID之SnowFlake算法
Dec 17 PHP
基于php数组中的索引数组和关联数组详解
Mar 12 PHP
Yii2.0框架behaviors方法使用实例分析
Sep 30 PHP
详解PHP服务器如何在有限的资源里最大提升并发能力
May 25 PHP
PHP图片自动裁切应付不同尺寸的显示
Oct 16 #PHP
PHP 抽象方法与抽象类abstract关键字介绍及应用
Oct 16 #PHP
php开启与关闭错误提示适用于没有修改php.ini的权限
Oct 16 #PHP
php实现扫描二维码根据浏览器类型访问不同下载地址
Oct 15 #PHP
ThinkPHP基于PHPExcel导入Excel文件的方法
Oct 15 #PHP
ThinkPHP分页实例
Oct 15 #PHP
PHP中使用Session配合Javascript实现文件上传进度条功能
Oct 15 #PHP
You might like
PHP初学者头疼问题总结
2006/10/09 PHP
PHP个人网站架设连环讲(一)
2006/10/09 PHP
详解PHP的Yii框架中扩展的安装与使用
2016/04/01 PHP
PHP 等比例缩放图片详解及实例代码
2016/09/18 PHP
Thinkphp 中 distinct 的用法解析
2016/12/14 PHP
使用javascript:将其它类型值转换成布尔类型值的解决方法详解
2013/05/07 Javascript
jquery自动填充勾选框即把勾选框打上true
2014/03/24 Javascript
纯js实现遮罩层效果原理分析
2014/05/27 Javascript
JQuery EasyUI 加载两次url的原因分析及解决方案
2014/08/18 Javascript
7个有用的jQuery代码片段分享
2015/05/19 Javascript
Vue.js快速入门教程
2016/09/07 Javascript
Es6 写的文件import 起来解决方案详解
2016/12/13 Javascript
利用angularjs1.4制作的简易滑动门效果
2017/02/28 Javascript
深入理解JavaScript继承的多种方式和优缺点
2017/05/12 Javascript
ES6扩展运算符的用途实例详解
2017/08/20 Javascript
vue 监听某个div垂直滚动条下拉到底部的方法
2018/09/15 Javascript
关于vue v-for循环解决img标签的src动态绑定问题
2018/09/18 Javascript
微信小程序实现基于三元运算验证手机号/姓名功能示例
2019/01/19 Javascript
JS实现的进制转换,浮点数相加,数字判断操作示例
2019/11/09 Javascript
node.js 使用 net 模块模拟 websocket 握手进行数据传递操作示例
2020/02/11 Javascript
[01:07:20]DOTA2-DPC中国联赛 正赛 Dynasty vs XG BO3 第二场 2月2日
2021/03/11 DOTA
python2.7 json 转换日期的处理的示例
2018/03/07 Python
对numpy中数组转置的求解以及向量内积计算方法
2018/10/31 Python
Python 实现自动获取种子磁力链接方式
2020/01/16 Python
如何在windows下安装Pycham2020软件(方法步骤详解)
2020/05/03 Python
在keras中实现查看其训练loss值
2020/06/16 Python
html5中localStorage本地存储的简单使用
2017/06/16 HTML / CSS
美国机场停车位预订:About Airport Parking
2018/03/26 全球购物
EJB3推出JPA的原因
2013/10/16 面试题
智能电子应届生求职信
2013/11/10 职场文书
环境科学毕业生自荐信
2013/11/21 职场文书
人力资源管理专业毕业生自荐书
2014/05/25 职场文书
安全生产隐患排查制度
2015/08/05 职场文书
表扬稿表扬信的格式及范文
2019/06/24 职场文书
html+css实现环绕倒影加载特效
2021/07/07 HTML / CSS
超越Nginx的Web服务器caddy优雅用法
2022/06/21 Servers