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中英混合字符串截取函数代码
Jul 17 PHP
表格展示无限级分类(PHP版)
Aug 21 PHP
php文字水印和php图片水印实现代码(二种加水印方法)
Dec 25 PHP
php伪静态之APACHE篇
Jun 02 PHP
将CMYK颜色值和RGB颜色相互转换的PHP代码
Jul 28 PHP
推荐5款跨平台的PHP编辑器
Dec 25 PHP
Laravel 5 框架入门(二)构建 Pages 的管理功能
Apr 09 PHP
PHP几个实用自定义函数小结
Jan 25 PHP
PHP递归获取目录内所有文件的实现方法
Nov 01 PHP
Yii 2.0自带的验证码使用经验分享
Jun 19 PHP
PHP微信企业号开发之回调模式开启与用法示例
Nov 25 PHP
WordPress伪静态规则设置代码实例
Dec 10 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
Laravel 5框架学习之Eloquent (laravel 的ORM)
2015/04/08 PHP
摘自启点的main.js
2008/04/20 Javascript
jQuery获取文本节点之 text()/val()/html() 方法区别
2011/03/01 Javascript
js实现3D图片逐张轮播幻灯片特效代码分享
2015/09/09 Javascript
底部悬浮通栏可以关闭广告位的实现方法
2016/06/01 Javascript
浅谈angular2的http请求返回结果的subcribe注意事项
2017/03/01 Javascript
JavaScript使用小插件实现倒计时的方法讲解
2019/03/11 Javascript
微信小程序select下拉框实现源码
2019/11/08 Javascript
js实现计时器秒表功能
2019/12/16 Javascript
js实现简单抽奖功能
2020/11/24 Javascript
原生js实现下拉框选择组件
2021/01/20 Javascript
python开发中module模块用法实例分析
2015/11/12 Python
CentOS6.5设置Django开发环境
2016/10/13 Python
Python排序搜索基本算法之堆排序实例详解
2017/12/08 Python
Python元组及文件核心对象类型详解
2018/02/11 Python
python读取txt文件,去掉空格计算每行长度的方法
2018/12/20 Python
使用Python Pandas处理亿级数据的方法
2019/06/24 Python
python3的url编码和解码,自定义gbk、utf-8的例子
2019/08/22 Python
Python 动态变量名定义与调用方法
2020/02/09 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
Python 捕获代码中所有异常的方法
2020/08/03 Python
Ellos瑞典官网:北欧地区时尚、美容和住宅领域领先的电子商务网站
2019/11/21 全球购物
EJB的几种类型
2012/08/15 面试题
采购助理岗位职责
2014/02/16 职场文书
护士毕业实习感言
2014/03/05 职场文书
三分钟演讲稿范文
2014/04/24 职场文书
校运动会广播稿(100篇)
2014/09/12 职场文书
师德师风自查总结
2014/10/14 职场文书
2015年建筑工程工作总结
2015/05/13 职场文书
居安思危观后感
2015/06/11 职场文书
追悼词范文大全
2015/06/23 职场文书
员工手册董事长致辞
2015/07/29 职场文书
老舍《猫》教学反思
2016/02/17 职场文书
JavaScript 语句之常用 for 循环详解
2021/03/29 Javascript
微信小程序实现聊天室功能
2021/06/14 Javascript
Java Redisson多策略注解限流
2022/09/23 Java/Android