介绍:
垃圾回收机制是一种动态存储分配方案,它会自动释放程序不再需要的已分配的内存块,PHP也在语言层实现了内存的动态管理。内存的动态管理将开发人员从繁琐的内存管理中解救出来
垃圾回收机制是一种动态存储分配方案。它会自动释放程序不再需要的已分配的内存块。 自动回收内存的过程叫垃圾收集。垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑。 在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征,如Python、PHP、Eiffel、C#、Ruby等都使用了垃圾回收机制。 虽然垃圾回收是现在比较流行的做法,但是它的年纪已经不小了。早在20世纪60年代MIT开发的Lisp系统中就已经有了它的身影, 但是由于当时技术条件不成熟,从而使得垃圾回收机制成了一个看起来很美的技术,直到20世纪90年代Java的出现,垃圾回收机制才被广泛应用。
PHP也在语言层实现了内存的动态管理,这在前面的章节中已经有了详细的说明, 内存的动态管理将开发人员从繁琐的内存管理中解救出来。与此配套,PHP也提供了语言层的垃圾回收机制, 让程序员不必过分关心程序内存分配。
在PHP5.3版本之前,PHP只有简单的基于引用计数的垃圾回收,当一个变量的引用计数变为0时, PHP将在内存中销毁这个变量,只是这里的垃圾并不能称之为垃圾。 并且PHP在一个生命周期结束后就会释放此进程/线程所点的内容,这种方式决定了PHP在前期不需要过多考虑内存的泄露问题。 但是随着PHP的发展,PHP开发者的增加以及其所承载的业务范围的扩大,在PHP5.3中引入了更加完善的垃圾回收机制。 新的垃圾回收机制解决了无法处理循环的引用内存泄漏问题。PHP5.3中的垃圾回收机制使用了文章引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems) 中的同步算法。关于这个算法的介绍我们就不再赘述,在PHP的官方文档有图文并茂的介绍:回收周期(Collecting Cycles)。
如前面所说,在PHP中,主要的内存管理手段是引用计数,引入垃圾收集机制的目的是为了打破引用计数中的循环引用,从而防止因为这个而产生的内存泄露。 垃圾收集机制基于PHP的动态内存管理而存在。PHP5.3为引入垃圾收集机制,在变量存储的基本结构上有一些变动。
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
与PHP5.3之前的版本相比,引用计数字段refcount和是否引用字段is_ref都在其后面添加了__gc以用于新的的垃圾回收机制。 在PHP的源码风格中,大量的宏是一个非常鲜明的特点。这些宏相当于一个接口层,它屏蔽了接口层以下的一些底层实现,如, ALLOC_ZVAL宏,这个宏在PHP5.3之前是直接调用PHP的内存管理分配函数emalloc分配内存,所分配的内存大小由变量的类型等大小决定。 在引入垃圾回收机制后,ALLOC_ZVAL宏直接采用新的垃圾回收单元结构,所分配的大小都是一样的,全部是zval_gc_info结构体所占内存大小, 并且在分配内存后,初始化这个结构体的垃圾回收机制。
#undef ALLOC_ZVAL
#define ALLOC_ZVAL(z) \
do { \
(z) = (zval*)emalloc(sizeof(zval_gc_info)); \
GC_ZVAL_INIT(z); \
} while (0)
zend_gc.h文件在zend.h的749行被引用:#include “zend_gc.h” 从而替换覆盖了在237行引用的zend_alloc.h文件中的ALLOC_ZVAL等宏 在新的的宏中,关键性的改变是对所分配内存大小和分配内容的改变,在以前纯粹的内存分配中添加了垃圾收集机制的内容, 所有的内容都包括在zval_gc_info结构体中:
zval z;
union {
gc_root_buffer *buffered;
struct _zval_gc_info *next;
} u;
} zval_gc_info;
对于任何一个ZVAL容器存储的变量,分配了一个zval结构,这个结构确保其和以zval变量分配的内存的开始对齐, 从而在zval_gc_info类型指针的强制转换时,其可以作为zval使用。在zval字段后面有一个联合体:u。 u包括gc_root_buffer结构的buffered字段和zval_gc_info结构的next字段。 这两个字段一个是表示垃圾收集机制缓存的根结点,一个是zval_gc_info列表的下一个结点, 垃圾收集机制缓存的结点无论是作为根结点,还是列表结点,都可以在这里体现。 ALLOC_ZVAL在分配了内存后会调用GC_ZVAL_INIT用来初始化替代了zval的zval_gc_info, 它会把zval_gc_info中的成员u的buffered字段设置成NULL,此字段仅在将其放入垃圾回收缓冲区时才会有值,否则会一直是NULL。 由于PHP中所有的变量都是以zval变量的形式存在,这里以zval_gc_info替换zval,从而成功实现垃圾收集机制在原有系统中的集成。
PHP的垃圾回收机制在PHP5.3中默认为开启,但是我们可以通过配置文件直接设置为禁用,其对应的配置字段为:zend.enable_gc。 在php.ini文件中默认是没有这个字段的,如果我们需要禁用此功能,则在php.ini中添加zend.enable_gc=0或zend.enable_gc=off。 除了修改php.ini配置zend.enable_gc,也可以通过调用gc_enable()/gc_disable()函数来打开/关闭垃圾回收机制。 这些函数的调用效果与修改配置项来打开或关闭垃圾回收机制的效果是一样的。 除了这两个函数PHP提供了gc_collect_cycles()函数可以在根缓冲区还没满时强制执行周期回收。 与垃圾回收机制是否开启在PHP源码中有一些相关的操作和字段。
在zend.c文件中有如下代码:
{
OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
if (GC_G(gc_enabled)) {
gc_init(TSRMLS_C);
}
return SUCCESS;
}
/* }}} */
ZEND_INI_BEGIN()
ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
STD_ZEND_INI_BOOLEAN("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, gc_enabled, zend_gc_globals, gc_globals)
#ifdef ZEND_MULTIBYTE
STD_ZEND_INI_BOOLEAN("detect_unicode", "1", ZEND_INI_ALL, OnUpdateBool, detect_unicode, zend_compiler_globals, compiler_globals)
#endif
ZEND_INI_END()
zend.enable_gc对应的操作函数为ZEND_INI_MH(OnUpdateGCEnabled),如果开启了垃圾回收机制, 即GC_G(gc_enabled)为真,则会调用gc_init函数执行垃圾回收机制的初始化操作。 gc_init函数在zend/zend_gc.c 121行,此函数会判断是否开启垃圾回收机制, 如果开启,则初始化整个机制,即直接调用malloc给整个缓存列表分配10000个gc_root_buffer内存空间。 这里的10000是硬编码在代码中的,以宏GC_ROOT_BUFFER_MAX_ENTRIES存在,如果需要修改这个值,则需要修改源码,重新编译PHP。 gc_init函数在预分配内存后调用gc_reset函数重置整个机制用到的一些全局变量,如设置gc运行的次数统计(gc_runs)和gc中垃圾的个数(collected)为0, 设置双向链表头结点的上一个结点和下一个结点指向自己等。除了这种提的一些用于垃圾回收机制的全局变量,还有其它一些使用较多的变量,部分说明如下:
zend_bool gc_enabled; /* 是否开启垃圾收集机制 */
zend_bool gc_active; /* 是否正在进行 */
gc_root_buffer *buf; /* 预分配的缓冲区数组,默认为10000(preallocated arrays of buffers) */
gc_root_buffer roots; /* 列表的根结点(list of possible roots of cycles) */
gc_root_buffer *unused; /* 没有使用过的缓冲区列表(list of unused buffers) */
gc_root_buffer *first_unused; /* 指向第一个没有使用过的缓冲区结点(pointer to first unused buffer) */
gc_root_buffer *last_unused; /* 指向最后一个没有使用过的缓冲区结点,此处为标记结束用(pointer to last unused buffer) */
zval_gc_info *zval_to_free; /* 将要释放的zval变量的临时列表(temporaryt list of zvals to free) */
zval_gc_info *free_list; /* 临时变量,需要释放的列表开头 */
zval_gc_info *next_to_free; /* 临时变量,下一个将要释放的变量位置*/
zend_uint gc_runs; /* gc运行的次数统计 */
zend_uint collected; /* gc中垃圾的个数 */
// 省略...
}
当我们使用一个unset操作想清除这个变量所占的内存时(可能只是引用计数减一),会从当前符号的哈希表中删除变量名对应的项, 在所有的操作执行完后,并对从符号表中删除的项调用一个析构函数,临时变量会调用zval_dtor,一般的变量会调用zval_ptr_dtor。
当然我们无法在PHP的函数集中找到unset函数,因为它是一种语言结构。 其对应的中间代码为ZEND_UNSET,在Zend/zend_vm_execute.h文件中你可以找到与它相关的实现。
zval_ptr_dtor并不是一个函数,只是一个长得有点像函数的宏。 在Zend/zend_variables.h文件中,这个宏指向函数_zval_ptr_dtor。 在Zend/zend_execute_API.c 424行,函数相关代码如下:
{
#if DEBUG_ZEND>=2
printf("Reducing refcount for %x (%x): %d->%d\n", *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) - 1);
#endif
Z_DELREF_PP(zval_ptr);
if (Z_REFCOUNT_PP(zval_ptr) == 0) {
TSRMLS_FETCH();
if (*zval_ptr != &EG(uninitialized_zval)) {
GC_REMOVE_ZVAL_FROM_BUFFER(*zval_ptr);
zval_dtor(*zval_ptr);
efree_rel(*zval_ptr);
}
} else {
TSRMLS_FETCH();
if (Z_REFCOUNT_PP(zval_ptr) == 1) {
Z_UNSET_ISREF_PP(zval_ptr);
}
GC_ZVAL_CHECK_POSSIBLE_ROOT(*zval_ptr);
}
}
/* }}} */
从代码我们可以很清晰的看出这个zval的析构过程,关于引用计数字段做了以下两个操作:
如果变量的引用计数为1,即减一后引用计数为0,直接清除变量。如果当前变量如果被缓存,则需要清除缓存如果变量的引用计数大于1,即减一后引用计数大于0,则将变量放入垃圾列表。如果变更存在引用,则去掉其引用。
将变量放入垃圾列表的操作是GC_ZVAL_CHECK_POSSIBLE_ROOT,这也是一个宏,其对应函数gc_zval_check_possible_root, 但是此函数仅对数组和对象执行垃圾回收操作。对于数组和对象变量,它会调用gc_zval_possible_root函数。
{
if (UNEXPECTED(GC_G(free_list) != NULL &&
GC_ZVAL_ADDRESS(zv) != NULL &&
GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
(GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
/* The given zval is a garbage that is going to be deleted by
* currently running GC */
return;
}
if (zv->type == IS_OBJECT) {
GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
return;
}
GC_BENCH_INC(zval_possible_root);
if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
GC_ZVAL_SET_PURPLE(zv);
if (!GC_ZVAL_ADDRESS(zv)) {
gc_root_buffer *newRoot = GC_G(unused);
if (newRoot) {
GC_G(unused) = newRoot->prev;
} else if (GC_G(first_unused) != GC_G(last_unused)) {
newRoot = GC_G(first_unused);
GC_G(first_unused)++;
} else {
if (!GC_G(gc_enabled)) {
GC_ZVAL_SET_BLACK(zv);
return;
}
zv->refcount__gc++;
gc_collect_cycles(TSRMLS_C);
zv->refcount__gc--;
newRoot = GC_G(unused);
if (!newRoot) {
return;
}
GC_ZVAL_SET_PURPLE(zv);
GC_G(unused) = newRoot->prev;
}
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;
GC_ZVAL_SET_ADDRESS(zv, newRoot);
newRoot->handle = 0;
newRoot->u.pz = zv;
GC_BENCH_INC(zval_buffered);
GC_BENCH_INC(root_buf_length);
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
}
}
}
在前面说到gc_zval_check_possible_root函数仅对数组和对象执行垃圾回收操作,然而在gc_zval_possible_root函数中, 针对对象类型的变量会去调用GC_ZOBJ_CHECK_POSSIBLE_ROOT宏。而对于其它的可用于垃圾回收的机制的变量类型其调用过程如下:
检查zval结点信息是否已经放入到结点缓冲区,如果已经放入到结点缓冲区,则直接返回,这样可以优化其性能。 然后处理对象结点,直接返回,不再执行后面的操作判断结点是否已经被标记为紫色,如果为紫色则不再添加到结点缓冲区,此处在于保证一个结点只执行一次添加到缓冲区的操作。
将结点的颜色标记为紫色,表示此结点已经添加到缓冲区,下次不用再做添加。
找出新的结点的位置,如果缓冲区满了,则执行垃圾回收操作。
将新的结点添加到缓冲区所在的双向链表。
在gc_zval_possible_root函数中,当缓冲区满时,程序调用gc_collect_cycles函数,执行垃圾回收操作。 其中最关键的几步就是:
第628行 此处为其官方文档中算法的步骤 B ,算法使用深度优先搜索查找所有可能的根,找到后将每个变量容器中的引用计数减1, 为确保不会对同一个变量容器减两次“1”,用灰色标记已减过1的。
第629行 这是算法的步骤 C ,算法再一次对每个根节点使用深度优先搜索,检查每个变量容器的引用计数。 如果引用计数是 0 ,变量容器用白色来标记。如果引用次数大于0,则恢复在这个点上使用深度优先搜索而将引用计数减1的操作(即引用计数加1), 然后将它们重新用黑色标记。
第630行 算法的最后一步 D ,算法遍历根缓冲区以从那里删除变量容器根(zval roots), 同时,检查是否有在上一步中被白色标记的变量容器。每个被白色标记的变量容器都被清除。 在[gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ]中我们可以看到, 对于白色标记的结点会被添加到全局变量zval_to_free列表中。此列表在后面的操作中有用到。
PHP的垃圾回收机制在执行过程中以四种颜色标记状态。
GC_WHITE 白色表示垃圾
GC_PURPLE 紫色表示已放入缓冲区
GC_GREY 灰色表示已经进行了一次refcount的减一操作
GC_BLACK 黑色是默认颜色,正常
相关的标记以及操作代码如下:
#define GC_BLACK 0x00
#define GC_WHITE 0x01
#define GC_GREY 0x02
#define GC_PURPLE 0x03
#define GC_ADDRESS(v) \
((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR))
#define GC_SET_ADDRESS(v, a) \
(v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)(a))))
#define GC_GET_COLOR(v) \
(((zend_uintptr_t)(v)) & GC_COLOR)
#define GC_SET_COLOR(v, c) \
(v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & ~GC_COLOR) | (c)))
#define GC_SET_BLACK(v) \
(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR))
#define GC_SET_PURPLE(v) \
(v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) | GC_PURPLE))
以上的这种以位来标记状态的方式在PHP的源码中使用频率较高,如内存管理等都有用到, 这是一种比较高效及节省的方案。但是在我们做数据库设计时可能对于字段不能使用这种方式, 应该是以一种更加直观,更加具有可读性的方式实现。
以下代码用到了scws,以及phpanalysis,供大家学习参考。
header("Content-Type:text/html; charset=utf-8");
define('APP_ROOT', str_replace()('\\', '/', dirname(__FILE__)));
$test = '中文测试代码!';
function get_tags_arr($title)
{
require(APP_ROOT.'/pscws4.class.php');
$pscws = new PSCWS4();
$pscws->set_dict(APP_ROOT.'/scws/dict.utf8.xdb');
$pscws->set_rule(APP_ROOT.'/scws/rules.utf8.ini');
$pscws->set_ignore(true);
$pscws->send_text($title);
$words = $pscws->get_tops(5);
$tags = array();
foreach ($words as $val) {
$tags[] = $val['word'];
}
$pscws->close();
return $tags;
}
print_r(get_tags_arr($test));
//=--------------------------------
function get_keywords_str($content){
require(APP_ROOT.'/phpanalysis.class.php');
PhpAnalysis::$loadInit = false;
$pa = new PhpAnalysis('utf-8', 'utf-8', false);
$pa->LoadDict();
$pa->SetSource($content);
$pa->StartAnalysis( false );
$tags = $pa->GetFinallyResult();
return $tags;
}
print(get_keywords_str($test));
SCWS – 简易中文分词系统
SCWS 在概念上并无创新成分,采用的是自行采集的词频词典,并辅以一定程度上的专有名称、人名、地名、数字年代等规则集,经小范围测试大概准确率在 90% ~ 95% 之间,已能基本满足一些中小型搜索引擎、关键字提取等场合运用。 SCWS 采用纯 C 代码开发,以 Unix-Like OS 为主要平台环境,提供共享函数库,方便植入各种现有软件系统。此外它支持 GBK,UTF-8,BIG5 等汉字编码,切词效率高。
系统平台:Windows/Unix
开发语言:C
使用方式:PHP扩展
演示网址:http://www.ftphp.com/scws/demo.php
开源官网:http://www.ftphp.com/scws/
注:作为PHP扩展,容易与现有的基于PHP架构的Web系统继续集成,是其一大优势。
PhpanAlysis - PHP无组件分词系统
PhpanAlysis分词系统是基于字符串匹配的分词方法 ,这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配 和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。
系统平台:PHP环境
开发语言:PHP
使用方式:HTTP服务
演示网址:http://www.itgrass.com/phpanalysis/
开源官网:http://www.itgrass.com/phpanalysis/
注:实现简单,容易使用,能做一些简单应用,但大数据量的计算效率不如前几种。
试用了几个系统,基本分词功能都没什么问题,只是在个别一些词的划分上存在一些差异;对于词性的确定,系统间有所不同。
PHP 5 提供了一种新的面向对象的错误处理方法,包括PHP 的异常处理、错误的抛出及回调函数等面向对象的错误处理方法。
异常处理用于在指定的错误(异常)情况发生时改变脚本的正常流程。这种情况称为异常。
PHP 5 添加了类似于其它语言的异常处理模块。在 PHP 代码中所产生的异常可被 throw 语句抛出并被 catch 语句捕获。需要进行异常处理的代码都必须放入 try 代码块内,以便捕获可能存在的异常。每一个 try 至少要有一个与之对应的 catch。使用多个 catch 可以捕获不同的类所产生的异常。当 try 代码块不再抛出异常或者找不到 catch 能匹配所抛出的异常时,PHP 代码就会在跳转到最后一个 catch 的后面继续执行。当然,PHP 允许在 catch 代码块内再次抛出(throw)异常。
当一个异常被抛出时,其后(译者注:指抛出异常时所在的代码块)的代码将不会继续执行,而 PHP 就会尝试查找第一个能与之匹配的 catch。如果一个异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出 Uncaught Exception ... (未捕获异常)的提示信息。
当异常被触发时,通常会发生:
1)当前代码状态被保存
2)代码执行被切换到预定义的异常处理器函数
3)根据情况,处理器也许会从保存的代码状态重新开始执行代码,终止脚本执行,或从代码中另外的位置继续执行脚本
一、错误、异常 等级常量表
error:不能在编译期发现的运行期错误,不如试图用 echo 输出一个未赋值的变量,这类问题往往导致程序或逻辑无法继续下去而需要中断;
exception:程序执行过程中出现意料之外的情况,逻辑上往往是行得通的,但不符合应用场景,比如接收到一个长度长错预定格式的用户命名,因此,异常主要靠编码人员做预先做判断后抛出,捕获异常后改变程序流程来处理这些情况,不必中断程序。
PHP 对于异常和错误的界定似乎不是很明显,尤其是低版本的PHP。
错误和日志记录值 常量 说明 备注
1 E_ERROR (integer)
致命的运行时错误。这类错误一般是不可恢复的情况,例如内存分配导致的问题。后果是导致脚本终止不再继续运行。
2 E_WARNING (integer)
运行时警告 (非致命错误)。仅给出提示信息,但是脚本不会终止运行。
4 E_PARSE (integer)
编译时语法解析错误。解析错误仅仅由分析器产生。
8 E_NOTICE (integer)
运行时通知。表示脚本遇到可能会表现为错误的情况,但是在可以正常运行的脚本里面也可能会有类似的通知。
16 E_CORE_ERROR(integer)
在PHP初始化启动过程中发生的致命错误。该错误类似 E_ERROR,但是是由PHP引擎核心产生的。 since PHP 4
32 E_CORE_WARNING(integer)
PHP初始化启动过程中发生的警告 (非致命错误) 。类似 E_WARNING,但是是由PHP引擎核心产生的。 since PHP 4
64 E_COMPILE_ERROR(integer)
致命编译时错误。类似E_ERROR, 但是是由Zend脚本引擎产生的。 since PHP 4
128 E_COMPILE_WARNING(integer)
编译时警告 (非致命错误)。类似 E_WARNING,但是是由Zend脚本引擎产生的。 since PHP 4
256 E_USER_ERROR(integer)
用户产生的错误信息。类似 E_ERROR, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。 since PHP 4
512 E_USER_WARNING(integer)
用户产生的警告信息。类似 E_WARNING, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。 since PHP 4
1024 E_USER_NOTICE(integer)
用户产生的通知信息。类似 E_NOTICE, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。 since PHP 4
2048 E_STRICT (integer)
启用 PHP 对代码的修改建议,以确保代码具有最佳的互操作性和向前兼容性。 since PHP 5
4096 E_RECOVERABLE_ERROR(integer)
可被捕捉的致命错误。 它表示发生了一个可能非常危险的错误,但是还没有导致PHP引擎处于不稳定的状态。 如果该错误没有被用户自定义句柄捕获 (参见 set_error_handler()),将成为一个 E_ERROR 从而脚本会终止运行。 since PHP 5.2.0
8192 E_DEPRECATED(integer)
运行时通知。启用后将会对在未来版本中可能无法正常工作的代码给出警告。 since PHP 5.3.0
16384 E_USER_DEPRECATED(integer)
用户产少的警告信息。 类似 E_DEPRECATED, 但是是由用户自己在代码中使用PHP函数 trigger_error()来产生的。 since PHP 5.3.0
30719 E_ALL (integer)
E_STRICT出外的所有错误和警告信息。 30719 in PHP 5.3.x, 6143 in PHP 5.2.x, 2047 previously
二、error_reporting() 及 try-catch、thrown
error_reporting() 函数可以获取(不传参时)、设定脚本处理哪些异常(并非所有异常都需要处理,例如 E_CORE_WARNING、E_NOTICE、E_DEPRECATED 是可以忽略的),该设定将覆盖 php.ini 中 error_reporting选项定义的异常处理设定。
例如:
try-catch 无法用于捕获异常,无法捕获错误,例如 trigger_error() 触发的错误,异常和错误是不一样的。
try{
// you codes that maybe cause an error
}catch(Exception $err){ // 这个错误对象需要声明类型, Exception 是系统默认异常处理类
echo $err->getMessage();
}
//thrown 可以抛出一个异常,如:
thrown new Exception('an error');
一个例子:
if ( empty( $var1 ) ) throw new NotEmptyException();
if ( empty( $var2 ) ) throw new NotEmptyException();
if ( ! preg_match() ) throw new InvalidInputException();
$model->write();
$template->render( 'success' );
} catch ( NotEmptyException $e ) {
$template->render( 'error_empty' );
} catch ( InvalidInputException $e ) {
$template->render( 'error_preg' );
}
Exception 类的结构:其中大部分方法都是 禁止改写的(final )
/* 属性 */
protected string $message ;
protected int $code ;
protected string $file ;
protected int $line ;
/* 方法 */
public __construct ([ string $message = "" [, int $code = 0 [, Exception $previous = null]]] )
final public string getMessage ( void ) //异常抛出的信息
final public Exception getPrevious ( void ) //前一异常
final public int getCode ( void ) //异常代码,这是用户自定义的
final public string getFile ( void ) //发生异常的文件路劲
final public int getLine ( void ) //发生异常的行
final public array getTrace ( void ) //异常追踪信息(array)
final public string getTraceAsString ( void ) //异常追踪信息(string)
public string __toString ( void ) //试图直接 将异常对象当作字符串使用时调用子函数的返回值
final private void __clone ( void ) //克隆异常对象时调用
}
扩展异常类
try-catch 可以有多个 catch 子句,从第一个 catch 子句开始,如果子句内的 异常变量 类型匹配 thrown 语句抛出的异常类型,则该子句会被执行而不再执行其他catch子句,否则继续尝试下一个 catch 子句,由于Exception 是所有 异常类的基类,因此抛出的异常都会与他匹配 ,如果你像个根据不同异常类型使用不同的处理方法,应该将 Exception 类型的 catch 子句放到最后。
Exception 是所有异常的基类,可以根据实际需要扩展异常类,
public errType = 'default';
public function __construct($errType=''){
$this->errType = $errType;
}
}
thrown new MyException (); //抛出一个异常
try{
// you codes that maybe cause an error
}catch(MyException $err){ // 这个错误对象需要声明类型
echo $err->errType();
}catch(ErrorException $err){ //ErrorException 是 PHP 5 增加的异常类,继承于 Exception
echo 'error !';
}catch(Exception $err){
redirect('/error.php');
}
你可能会在 catch 子句中判断异常的类型,或者根据 code 等信息来决定是否处理异常,如果你卸载 catch 子句的代码无法适当的处理捕获的异常,你可以在 catch 子句内继续 抛出异常。
三、Exception 异常的回调函数
set_exception_handler(callback functionName) //发生 Exception 或其 子类的 异常是会调用此函数
function exceptionHandlerFun($errObj){ // Exception 异常的回调函数 只有一个参数,就是抛出的异常对象。
//.......
}
Exception 异常的回调函数并不能像 set_error_handler 的回调函数那样通过返回 true 来使异常被消除,即使回调函数处理了异常,后继代码也不会被继续执行,因此想继续执行后续代码必须使用 try-catch。
但是有一个例外:脚本结束回调函数可以被执行,抛出的异常即使没有被处理,该回调函数也是能被执行的。
register_shutdown_function(callback functionName[,argument1,argument2,...]);
例如:
function shutdownfunction(){
echo 'script is end';
}
register_shutdown_function("shutdownfunction");
因为 shutdownfunction() 在脚本结束时被执行,所以 这个回调函数之内可以调用脚本中任意位置的函数,即使该函数定义在 错误抛出位置之后(函数定义是在 脚本编译期完成的)。
四、trigger_error(string errorMsg[,int user_error_type])
该函数用于主动触发一个错误: user_error_type 只能是 E_ALL、E_USER_ERROR、 E_USER_WARNING、 E_USER_NOTICE 或其组合的值。
set_error_handler(callbeck functionName[,user_error_type]); // 为 trigger_error() 设置一个回调函数来处理错误,包括系统抛出的错误和用户使用 trigger_error() 函数触发的错误。
可选参数 user_error_type :
如果设定此参数,则 trigger_error 抛出的错误类型符合 在user_error_type 的定义范围才能触发回调函数。
这个值的设置类似于 error_reporting() 函数 。
第一个参数(callbeck functionName):
一个函数名,该函数 可以有 5 个参数,其中前 2 个必选,依次是:
trigger_error 抛出的 user_error_type、trigger_error 抛出的 errorMsg、抛出错误的文件的绝对路劲、抛出错误的行号、抛出错误时的上下文环境 (一个数组,包含了trigger_error() 所在作用域内的所有变量、函数、类等数据 )
回调函数的返回值: 如果返回 false ,系统错误处理机制仍然继续抛出该错误,返回 true 或 无返回值 则消除错误。
trigger_error() 触发的错误不会被 try-catch 异常捕获语句捕获。
您可能感兴趣的文章:
PHP 异常处理相关知识
使用php异常处理类Exception的例子
php中的异常处理、错误的抛出及错误回调函数
一个简单的php自定义异常类
学习PHP错误与异常设置