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

根据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,并把所有动作放在一个函数里。
敬请大家指正。

(0)

相关推荐

  • PHP register_shutdown_function()函数的使用示例

    通过 register_shutdown_function 方法,可以让我们设置一个当执行关闭时可以被调用的另一个函数. 也就是说,当我们的脚本执行完成或者意外死掉导致 php 执行即将关闭时,我们的这个函数会被调用. [使用场景] ① 页面被(用户)强制停止 ② 程序代码意外终止或超时 ③ php4 中没有析构函数,可以使用该函数模拟析构函数 shutdown.php <?php header("content-type:text/html;charset=utf-8"); c

  • PHP register_shutdown_function函数的深入解析

    脚本时常死掉,而且并不总是那么好看. 我们可不想给用户显示一个致命错误,又或者一个空白页(在display_errors设为off的情况下) . PHP中有一个叫做  register_shutdown_function 的函数,可以让我们设置一个当执行关闭时可以被调用的另一个函数.也就是说当我们的脚本执行完成或意外死掉导致PHP执行即将关闭时,我们的这个函数将会 被调用.所以,我们可以使用在脚本开始处设置一个变量为false,然后在脚本末尾将之设置为true的方法,让PHP关闭回调函数检查脚本

  • PHP错误处理函数register_shutdown_function使用示例

    当程序在线上运行时,如果遇到BUG,想不在前端输出错误信息,同时能及时邮件通知开发者,register_shutdown_function函数就可以派上用场了. 注册一个会在脚本执行完成或者 exit() 后被调用的函数. 可以多次调用 register_shutdown_function() ,这些被注册的回调会按照他们注册时的顺序被依次调用. 如果你在注册的方法内部调用 exit(), 那么所有处理会被中止,并且其他注册的中止回调也不会再被调用. register_shutdown_func

  • php ignore_user_abort与register_shutdown_function 使用方法

    语法: int ignore_user_abort(int [setting]); 返回值: 整数 函数种类: PHP 系统功能 内容说明 0 - NORMAL(正常)1 - ABORTED(异常退出)2 - TIMEOUT(超时) 本函数配置或取得使用端连接中断后,PHP 程序是否仍继续执行.默认值为中断连接后就停止执行.在 PHP 配置文件中 (php3.ini/php.ini) 的 ignore_user_abort 选项就是配置处.本功能在 PHP 3.0.7 版之后才开始提供. con

  • php register_shutdown_function函数详解

    设定错误和异常处理三函数 register_shutdown_function(array('Debug','fatalError')); //定义PHP程序执行完成后执行的函数 set_error_handler(array('Debug','appError')); // 设置一个用户定义的错误处理函数 set_exception_handler(array('Debug','appException')); //自定义异常处理. 功能:register_shutdown_function(

  • PHP中使用register_shutdown_function函数截获fatal error示例

    我们在做项目时,偶尔会因为不谨慎而出现fatal error.如果display_errors设置成off,那用户将会看到的是一个空白的页面.若设置成了on,那fatal error的信息将会呈现出来(当然正常的人都不会这样干). 那我们有什么方法可以提前截获到fatal error,并用我们自己自定义的友好形式反馈给用户呢.PHP中有一个叫做  register_shutdown_function 的函数,可以让我们设置一个当执行关闭时可以被调用的另一个函数.也就是说当我们的脚本执行完成或意外

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

    根据php手册的解析. __destruct是 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行. 而register_shutdown_function是 Registers a callback to be executed after script execution finishes or exit() is called. 注册一个回调函数,此函数在脚本运行完毕或调用exit()时执行. 从字面上理解,__destruct是对象层面的,而register_shutdo

  • asp.net中button控制先执行js再执行后台程序的方法

    本文实例讲述了asp.net中button控制先执行js再执行后台程序的方法.分享给大家供大家参考.具体分析如下: 在.net中要实现button控制点击之后先执行js再执行后台程序这个看上去没什么难度,这里我们就一起来看一个asp.net中button控制先执行js再执行后台程序的实现方法,希望可以帮助到各位. 关于button这个服务器控件,我一直想减少它向服务器提交数据.那些检测,还是在客户端实现就好了. 这就需要javascript,但是我发现仅仅有javascript还是不够的.but

  • 网页中JS函数自动执行常用三种方法

    本文为大家分享了在网页中JS函数自动执行常用方法,供大家参考,具体内容如下 一.JS方法 1.最简单的调用方式,直接写到html的body标签里面: <body onload="myfunction()"> <html> <body onload="func1();func2();func3();"> </body> </html> 2.在JS语句调用: <script type="text

  • C#中实现一次执行多条带GO的sql语句实例

    本文实例讲述了C#中实现一次执行多条带GO的sql语句.分享给大家供大家参考.具体如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; using System.Text.RegularExpressions; using System.Data.SqlClient; namespace ConsoleApplicati

  • SQLServer中SELECT语句的执行顺序

    今天在写一条语句的时候,在查询分析器里边执行 要用10s,换用另外一种写法只用少于1s的时间,同事说是因为Sql句语执行顺序的原因.之前看过一点相 关的书,有一点印象,到网上找了资料,学习下. 逻辑查询处理步骤 复制代码 代码如下: (8)SELECT (9)DISTINCT (11)<TOP_specification> <select_list> (1)FROM <left_table> (3) <join_type> JOIN <right_ta

  • 浅谈js在html中的加载执行顺序,多个jquery ready执行顺序

    jQuery $(document).ready()执行顺序: 当页面DOM 元素全部加载完毕后就执行.ready().$(document).ready()是DOM结构绘制完毕后就执行,不必等到加载完毕. 如果在.ready()执行之前有javascript代码存在,那么javascript将怎么执行呢? 答案是先执行.ready()之前的javascript代码,然后执行.ready(). 多个$(document).ready()的执行顺序并非单纯的顺序执行,其与嵌套层级也有一定的关系.

  • Java中for循环的执行过程分析

    本文实例分析了Java中for循环的执行过程.分享给大家供大家参考.具体分析如下: public class Test01{ public static void main(String[] args) { int i = 0 ; for(foo('A');foo('B')&&i<3;foo('C')){ i++ ; foo('D') ; } } public static boolean foo(char c){ System.out.print(c + " "

  • 完全解析Java编程中finally语句的执行原理

    可不能小看这个简单的 finally,看似简单的问题背后,却隐藏了无数的玄机.接下来我就带您一步一步的揭开这个 finally 的神秘面纱. 问题分析 首先来问大家一个问题:finally 语句块一定会执行吗? 很多人都认为 finally 语句块是肯定要执行的,其中也包括一些很有经验的 Java 程序员.可惜并不像大多人所认为的那样,对于这个问题,答案当然是否定的,我们先来看下面这个例子. 清单 1. public class Test { public static void main(St

  • Shell脚本中多命令逻辑执行顺序的方法详解

    Linux中可以使用分号";".双and号"&&"和双竖线"||"来连接多个命令.单"&"符号也算命令连接符号,只不过它是将其前面的命令放入后台执行,所以可以变相地实现命令并行执行. 1.分号";" command1 ; command2 命令之间没有逻辑关系.分号连接的命令会按照顺序从前向后依次执行,但分号两端的命令之间没有任何逻辑关系,所有写出来的命令最终都会被执行,即使分号前面

  • 浅谈spring-boot的单元测试中,@Before不被执行的原因

    我们先来看下笔者的单元测试的依赖版本: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from reposi

随机推荐