深入理解PHP原理之异常机制


Posted in PHP onAugust 21, 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在包含一个找不到的问题的时候, 会前后抛出俩个错误,
WARNING : 在PHP试图打开这个文件的时候抛出. 
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异常流程

注: 图中有一处不严谨, 即在确定是否最后一个catch块的时候, 会同时判断(is_a), 如果是才进入最后一个catch块执行.
而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 相关文章推荐
一个用php3编写的简单计数器
Oct 09 PHP
首页四格,首页五格For6.0(GBK)(UTF-8)[12种组合][9-18][版主安装测试通过]
Sep 24 PHP
奇怪的PHP引用效率问题分析
Mar 23 PHP
Apache实现Web Server负载均衡详解(不考虑Session版)
Jul 05 PHP
PHP类中的魔术方法(Magic Method)简明总结
Jul 08 PHP
php实现从上传文件创建缩略图的方法
Apr 02 PHP
PHP如何通过AJAX方式实现登录功能
Nov 23 PHP
PHP实现通过URL提取根域名
Mar 31 PHP
PHP简单判断iPhone、iPad、Android及PC设备的方法
Oct 11 PHP
PHP-FPM 的管理和配置详解
Feb 17 PHP
php使用pthreads v3多线程实现抓取新浪新闻信息操作示例
Feb 21 PHP
Laravel相关的一些故障解决
Aug 19 PHP
php中var_export与var_dump的区别分析
Aug 21 #PHP
php visitFile()遍历指定文件夹函数
Aug 21 #PHP
php excel类 phpExcel使用方法介绍
Aug 21 #PHP
php下正则来匹配dede模板标签的代码
Aug 21 #PHP
php中使用ExcelFileParser处理excel获得数据(可作批量导入到数据库使用)
Aug 21 #PHP
使用VisualStudio开发php的图文设置方法
Aug 21 #PHP
利用Memcached在php下实现session机制 替换PHP的原生session支持
Aug 21 #PHP
You might like
PHP合并数组+与array_merge的区别分析
2010/08/01 PHP
WIN8.1下搭建PHP5.6环境
2015/04/29 PHP
解决laravel-admin 自己新建页面里 js 需要刷新一次的问题
2019/10/03 PHP
jquery实现文本框鼠标右击无效以及不能输入的代码
2010/11/05 Javascript
解决jquery的datepicker的本地化以及Today问题
2012/05/23 Javascript
DWZ table的原生分页浅谈
2013/03/01 Javascript
JavaScript字符串对象toUpperCase方法入门实例(用于把字母转换为大写)
2014/10/17 Javascript
JavaScript整除运算函数ceil和floor的区别分析
2015/04/14 Javascript
JS中判断字符串中出现次数最多的字符及出现的次数的简单实例
2016/06/03 Javascript
ES6学习之变量的解构赋值
2017/02/12 Javascript
js中new一个对象的过程
2017/02/20 Javascript
AngularJS实现controller控制器间共享数据的方法示例
2017/10/30 Javascript
js实现数字跳动到指定数字
2020/08/25 Javascript
[01:21:36]CHAOS vs Alliacne 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
Python的Bottle框架的一些使用技巧介绍
2015/04/08 Python
说一说Python logging
2016/04/15 Python
python使用正则表达式匹配字符串开头并打印示例
2017/01/11 Python
一张图带我们入门Python基础教程
2017/02/05 Python
Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例
2019/10/14 Python
django框架cookie和session用法实例详解
2019/12/10 Python
Django 设置admin后台表和App(应用)为中文名的操作方法
2020/05/10 Python
Python基于Twilio及腾讯云实现国际国内短信接口
2020/06/18 Python
python 用opencv实现图像修复和图像金字塔
2020/11/27 Python
HTML5通过调用canvas对象的getContext()方法来获取绘图环境
2014/06/23 HTML / CSS
英国领先的维生素和营养补充剂直接供应商:Healthspan
2019/04/22 全球购物
美国轻奢时尚购物网站:REVOLVE(支持中文)
2020/07/18 全球购物
最新英语专业学生求职信范文
2013/09/21 职场文书
外语学院毕业生的自我鉴定
2013/11/28 职场文书
大学生自助营养快餐店创业计划书
2014/01/13 职场文书
工商管理专业大学生职业生涯规划范文
2014/03/09 职场文书
五一劳动节活动记录
2014/03/23 职场文书
代理人委托书
2014/08/01 职场文书
CAD实训总结范文
2015/08/03 职场文书
MySQL系列之三 基础篇
2021/07/02 MySQL
SpringBoot连接MySQL获取数据写后端接口的操作方法
2021/11/02 MySQL
微信小程序APP的事件绑定以及传递参数时的冒泡和捕获
2022/04/19 Javascript