一般我们为了捕获php的错误,需要自己设置error_handler,但是,如果error_handler里出错了,可能会形成递归调用。事实上这个问题根本不存在,因为php在出错以后调用了zend_error函数,然后这个函数里会判断EG(user_error_handler)是否存在,如果存在则用call_user_function_ex来调用用户方法,而在此之前,它会先将EG(user_error_handler)设置为null,这样一来如果出错的话,则不会递归进入call_user_function_ex了。
具体代码如下:
orig_user_error_handler = EG(user_error_handler); EG(user_error_handler) = NULL;
exception_handler的实现却不同,因为exception其实是一个顺着栈往上传递的,因此对它的处理一定得在最上层,所以我们可以看到zend_execute_scripts里对exception进行了处理。在execute开始之前一定先要判断有没有exception,如果有则直接退出,不停地这样子退栈。而set_exception_handler的用户自定义函数最终会被call_user_function_ex调用,调用之后如果调用失败了,它会检查有没有EG(exception),如果有,则转到zend_exception_error,这个是默认实现,换句话说,也不会递归。
具体的代码如下:
zend_execute(EG(active_op_array) TSRMLS_CC); if (EG(exception)) { if (EG(user_exception_handler)) { zval *orig_user_exception_handler; zval ***params, *retval2, *old_exception; params = (zval ***)emalloc(sizeof(zval **)); old_exception = EG(exception); EG(exception) = NULL; params[0] = &old_exception; orig_user_exception_handler = EG(user_exception_handler); if (call_user_function_ex(CG(function_table), NULL, orig_user_exception_handler, &retval2, 1, params, 1, NULL TSRMLS_CC) == SUCCESS) { if (retval2 != NULL) { zval_ptr_dtor(&retval2); } } else { if (!EG(exception)) { EG(exception) = old_exception; } zend_exception_error(EG(exception) TSRMLS_CC); }