PHP原理之异常机制深入分析


Posted in PHP onAugust 08, 2010

PHP的异常机制的原理是什么?

在PHP每一个可独立执行的op array最后的ZEND_HANDLE_EXCEPTION是用来干什么呢?

让我们从一个问题说起, 上周的时候, blue5tar提了一个问题:”对于下面的代码, onError明明执行了, 但是onException却没有执行, 为什么?”.

<?php 
function onError($errCode, $errMesg, $errFile, $errLine) { 
echo "Error Occurred\n"; 
throw new Exception($errMesg); 
} function onException($e) { 
echo $e->getMessage(); 
} 
set_error_handler("onError"); 
set_exception_handler("onException"); 
/* 我从不会以我的名字命名文件, 所以这个文件不存在 */ 
require("laruence.php");

运行结果:
Error Occurred 
PHP Fatal error: main(): Failed opening required 'laruence.php

首先, 我们要知道, Require在包含一个找不到的问题的时候, 会前后抛出俩个错误,
1. WARNING : 在PHP试图打开这个文件的时候抛出. 
2. E_COMPILE_ERROR : 从PHP打开文件的函数返回失败以后抛出

而我们知道, set_error_handler是不能捕获E_COMPILE_ERROR错误的:

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

所以, 在onError中, 只能捕获到第一个WARNING错误, 而在onError中抛出的异常, 为什么没有被默认exception_handler捕获呢?

这就要说说PHP的异常机制了.

了解opcode(深入理解PHP原理之Opcodes的同学都知道, 在PHP5.3以前, 每一个可独立运行的op array(文件, 函数, 方法)的最后一条opcode都是ZEND_HANDLE_EXCEPTION, 而这个opcode是做什么用的呢?

原来在PHP中, 当有异常被throw的时候, 会跳到每一个op array的最后一行, 来执行这条ZEND_HANDLE_EXCEPTION, 伪码如下:

void on_throw_exception(zval *exception TSRMLS_DC) { 
1. 判断是否已经有异常抛出 
2. 记录exception 
3. 记录下一条要执行的op line的序号 
4. 下一条要执行的op line序号 = 当前op array的最后一条 
}

恩, 就和改写ip寄存器一样, 改写下一条要执行的op line的序号, 就改变了程序的流向, 这样, 就会进入到了ZEND_HANDLE_EXCEPTION的处理逻辑中.

而在ZEND_HANDLE_EXCEPTION中, 会判断这个异常是否在try catch中,

如果是 则把下一条要执行的op line, 置为第一个catch的op line, 并继续执行. 
如果不是 则销毁一些不需要的变量, 和opline, 然后直接结束执行过程

有的同学要问了:”那set_exception_handler设置的异常默认处理函数(user_exception_handler)什么时候起作用呢?”

恩, 是在执行完成退出执行LOOP以后才判断是否有默认异常处理函数, 如果有才调用:

//执行 
zend_execute(EG(active_op_array) TSRMLS_CC); 
if (EG(exception)) { 
if (EG(user_exception_handler)) { 
调用用户定义的默认异常处理函数 
} else { 
未捕获的异常 
} 
} else { 
没有异常 
} 
destroy_op_array(EG(active_op_array) TSRMLS_CC); 
efree(EG(active_op_array));

PHP原理之异常机制深入分析
PHP异常流程
而PHP在遇到Fatal Error的时候, 会直接zend_bailout, 而zend_bailout会导致程序流程直接跳过上面代码段, 也可以理解为直接exit了(longjmp), 这就导致了user_exception_handler没有机会发生作用.

了解到这些, 我想文章开头的问题的为什么? 也就很清晰了吧?

最后, 关于ZEND_HANDLE_EXCEPTION, 也许有同学会有疑问: 如果是这样, 那为什么每一个可独立执行的op array最后都有这个ZEND_HANDLE_EXCEPTION呢? 最简单的, 如果一个函数中不会throw, 那么这个opcode 是明显不需要的啊? 嘿嘿, 你很聪明, PHP 5.3开始, 已经按照你的想法调整了.. 只有在throw时刻, 才会动态的生成ZEND_HANDLE_EXCEPTION opline.

PHP5 changelog:

Changed exception handling. Now each op_array doesn't contain ZEND_HANDLE_EXCEPTION opcode in the end. (Dmitry)

PHP 相关文章推荐
php skymvc 一款轻量、简单的php
Jun 28 PHP
php中使用redis队列操作实例代码
Feb 07 PHP
php cURL和Rolling cURL并发方式比较
Oct 30 PHP
PHP获取当前url的具体方法全面解析
Nov 26 PHP
WampServer下安装多个版本的PHP、mysql、apache图文教程
Jan 07 PHP
php使用Cookie控制访问授权的方法
Jan 21 PHP
PHP中余数、取余的妙用
Jun 29 PHP
PHP中常见的缓存技术实例分析
Sep 23 PHP
php实现简单的上传进度条
Nov 17 PHP
php对xml文件的增删改查操作实现方法分析
May 19 PHP
Laravel框架实现简单的学生信息管理平台案例
May 07 PHP
Laravel 读取 config 下的数据方法
Oct 13 PHP
php include加载文件两种方式效率比较
Aug 08 #PHP
将一维或多维的数组连接成一个字符串的php代码
Aug 08 #PHP
php简单提示框alert封装函数
Aug 08 #PHP
php递归实现无限分类生成下拉列表的函数
Aug 08 #PHP
php用数组返回无限分类的列表数据的代码
Aug 08 #PHP
php自定义函数之递归删除文件及目录
Aug 08 #PHP
php自动获取目录下的模板的代码
Aug 08 #PHP
You might like
PHP遍历二维数组的代码
2011/04/22 PHP
laravel框架语言包拓展实现方法分析
2019/11/22 PHP
IE6背景图片不缓存问题解决方案及图片使用策略多个方法小结
2012/05/14 Javascript
Javascript 判断是否存在函数的方法
2013/01/03 Javascript
javascript中的nextSibling使用陷(da)阱(keng)
2014/05/05 Javascript
jQuery表单域选择器用法分析
2015/02/10 Javascript
jQuery Ajax中的事件详细介绍
2015/04/16 Javascript
JQUERY简单按钮轮换选中效果实现方法
2015/05/07 Javascript
JavaScript中数组继承的简单示例
2015/07/29 Javascript
js+css实现上下翻页相册代码分享
2015/08/18 Javascript
jquery实现点击弹出带标题栏的弹出层(从右上角飞入)效果
2015/09/19 Javascript
程序员必知35个jQuery 代码片段
2015/11/05 Javascript
javascript中的3种继承实现方法
2016/01/27 Javascript
js+canvas绘制五角星的方法
2016/01/28 Javascript
JavaScript判断数字是否为质数的方法汇总
2016/06/02 Javascript
JS中对Cookie的操作详解
2016/08/05 Javascript
jQuery实现可拖拽的许愿墙效果【附demo源码下载】
2016/09/14 Javascript
Jquery根据浏览器窗口改变调整大小的方法
2017/02/07 Javascript
微信小程序五星评分效果实现代码
2017/04/06 Javascript
基于Vue实现拖拽功能
2020/07/29 Javascript
bootstrap-table实现表头固定以及列固定的方法示例
2019/03/07 Javascript
vue生命周期的探索
2019/04/03 Javascript
vue中 this.$set的用法详解
2019/09/06 Javascript
Vue双向数据绑定(MVVM)的原理
2020/10/03 Javascript
绘制微信小程序验证码功能的实例代码
2021/01/05 Javascript
[58:54]EG vs RNG 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/18 DOTA
Python正则表达式分组概念与用法详解
2017/06/24 Python
python编写简易聊天室实现局域网内聊天功能
2018/07/28 Python
Pycharm生成可执行文件.exe的实现方法
2020/06/02 Python
Python unittest装饰器实现原理及代码
2020/09/08 Python
如何用PHP实现邮件发送
2012/12/26 面试题
客户经理竞聘演讲稿
2014/05/15 职场文书
Golang: 内建容器的用法
2021/05/05 Golang
Python实现Hash算法
2022/03/18 Python
【海涛DOTA】D-cup邀请赛NV.cn vs DT.Love
2022/04/01 DOTA
MySQL创建管理KEY分区
2022/04/13 MySQL