注册错误和异常处理机制有三个PHP函数需要学习
1. register_shutdown_function('Bootstrap\Library\Frame::fatalError');
2. set_error_handler('Bootstrap\Library\Frame::appError');
3. set_exception_handler('Bootstrap\Library\Frame::appException');
1.register_shutdown_function
定义:该函数是来注册一个会在PHP中止时执行的函数
参数说明:
void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )
注册一个 callback ,它会在脚本执行完成或者 exit() 后被调用。
callback:待注册的中止回调
parameter:可以通过传入额外的参数来将参数传给中止函数
PHP终止情况有三种
执行完成
<?php function test() { echo '这个是中止方法test的输出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; // => before // => 这个是中止方法test的输出
注意输出的顺序,等执行完成了之后才会去执行register_shutdown_function的中止方法test
exit/die导致的中止
<?php function test() { echo '这个是中止方法test的输出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; exit(); echo 'after' . PHP_EOL; // => before // => 这个是中止方法test的输出
后面的after并没有输出,即exit或者是die方法导致提前中止。
发生致命错误中止
<?php function test() { echo '这个是中止方法test的输出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; // 这里会发生致命错误 $a = new a(); echo 'after' . PHP_EOL; // => before // => Fatal error: Uncaught Error: Class 'a' not found in D:\laragon\www\php_book\test.php on line 12 // => Error: Class 'a' not found in D:\laragon\www\php_book\test.php on line 12 // => Call Stack: // => 0.0020 360760 1. {main}() D:\laragon\www\php_book\test.php:0 // => 这个是中止方法test的输出
后面的after也是没有输出,致命错误导致提前中止了。
参数:第一个参数支持以数组的形式来调用类中的方法,第二个以及后面的参数都是可以当做额外的参数传给中止方法。
<?php class Shutdown { public function stop() { echo "这个是stop方法的输出"; } } // 当PHP终止的时候(执行完成或者是遇到致命错误中止的时候)会调用new Shutdown的stop方法 register_shutdown_function([new Shutdown(), 'stop']); // 将因为致命错误而中止 $a = new a(); // 这一句并没有执行,也没有输出 echo '必须终止';
也可以在类中执行:
<?php class TestDemo { public function __construct() { register_shutdown_function([$this, "f"], "hello"); } public function f($str) { echo "class TestDemo->f():" . $str; } } $demo = new TestDemo(); echo 'before' . PHP_EOL; /** 运行: before class TestDemo->f():hello */
可以多次调用 register_shutdown_function,这些被注册的回调会按照他们注册时的顺序被依次调用。
不过注意的是,如果在第一个注册的中止方法里面调用exit方法或者是die方法的话,那么其他注册的中止回调也不会被调用。代码:<?php /** * 可以多次调用 register_shutdown_function,这些被注册的回调会按照他们注册时的顺序被依次调用。 * 注意:如果你在f方法(第一个注册的方法)里面调用exit方法或者是die方法的话,那么其他注册的中止回调也不会被调用 */ /** * @param $str */ function f($str) { echo $str . PHP_EOL; // 如果下面调用exit方法或者是die方法的话,其他注册的中止回调不会被调用 // exit(); } // 注册第一个中止回调f方法 register_shutdown_function("f", "hello"); class TestDemo { public function __construct() { register_shutdown_function([$this, "f"], "hello"); } public function f($str) { echo "class TestDemo->f():" . $str; } } $demo = new TestDemo(); echo 'before' . PHP_EOL; /** 运行: before hello class TestDemo->f():hello 注意:如果f方法里面调用了exit或者是die的话,那么最后的class TestDemo->f():hello不会输出 */
该函数的作用:
- 析构函数:在PHP4的时候,由于类不支持析构函数,所以这个函数经常用来模拟实现析构函数
- 致命错误的处理:使用该函数可以用来捕获致命错误并且在发生致命错误后恢复流程处理
代码如下:
<?php /** * register_shutdown_function,注册一个会在php中止时执行的函数,中止的情况包括发生致命错误、die之后、exit之后、执行完成之后都会调用register_shutdown_function里面的函数 * Created by PhpStorm. * User: Administrator * Date: 2017/7/15 * Time: 17:41 */ class Shutdown { public function stop() { echo 'Begin.' . PHP_EOL; // 如果有发生错误(所有的错误,包括致命和非致命)的话,获取最后发生的错误 if (error_get_last()) { print_r(error_get_last()); } // ToDo:发生致命错误后恢复流程处理 // 中止后面的所有处理 die('Stop.'); } } // 当PHP终止的时候(执行完成或者是遇到致命错误中止的时候)会调用new Shutdown的stop方法 register_shutdown_function([new Shutdown(), 'stop']); // 将因为致命错误而中止 $a = new a(); // 这一句并没有执行,也没有输出 echo '必须终止';
Fatal error: Uncaught Error: Class 'a' not found in D:\laragon\www\php_book\1_23_register_shutdown.php on line 31 Error: Class 'a' not found in D:\laragon\www\php_book\1_23_register_shutdown.php on line 31 Call Stack: 0.0060 362712 1. {main}() D:\laragon\www\php_book\1_23_register_shutdown.php:0 Begin. Array ( [type] => 1 [message] => Uncaught Error: Class 'a' not found in D:\laragon\www\php_book\1_23_register_shutdown.php:31 Stack trace: #0 {main} thrown [file] => D:\laragon\www\php_book\1_23_register_shutdown.php [line] => 31 ) Stop.
注意:PHP7中新增了Throwable异常类,这个类可以捕获致命错误,即可以使用try...catch(Throwable $e)来捕获致命错误,代码如下:
<?php try { // 将因为致命错误而中止 $a = new a(); // 这一句并没有执行,也没有输出 echo 'end'; } catch (Throwable $e) { print_r($e); echo $e->getMessage(); }
运行:
Error Object ( [message:protected] => Class 'a' not found [string:Error:private] => [code:protected] => 0 [file:protected] => C:\laragon\www\php_book\throwable.php [line:protected] => 5 [trace:Error:private] => Array ( ) [previous:Error:private] => [xdebug_message] => Error: Class 'a' not found in C:\laragon\www\php_book\throwable.php on line 5 Call Stack: 0.0000 349856 1. {main}() C:\laragon\www\php_book\throwable.php:0 ) Class 'a' not found
这样的话,PHP7中使用Throwable来捕获的话比使用register_shutdown_function这个函数来得更方便,也更推荐Throwable。
注意:Error类也是可以捕获到致命错误,不过Error只能捕获致命错误,不能捕获异常Exception,而Throwable是可以捕获到错误和异常的,所以更推荐。
总结:register_shutdown_function这个函数主要是用在处理致命错误的后续处理上(PHP7更推荐使用Throwable来处理致命错误),不过缺点也很明显,只能处理致命错误Fatal error,其他的错误包括最高错误Parse error也是没办法处理的。
2.set_error_handler
通过 set_error_handler() 函数设置用户自定义的错误处理程序,然后触发错误(通过 trigger_error()):
<?php // 用户定义的错误处理函数 function myErrorHandler($errno, $errstr, $errfile, $errline) { echo "<b>Custom error:</b> [$errno] $errstr<br>"; echo " Error on line $errline in $errfile<br>"; } // 设置用户定义的错误处理函数 set_error_handler("myErrorHandler"); $test=2; // 触发错误 if ($test>1) { trigger_error("A custom error has been triggered"); } ?>
以上代码的输出类似这样:
Custom error: [1024] A custom error has been triggered Error on line 14 in C:\webfolder\test.php
定义和用法
set_error_handler() 函数设置用户定义的错误处理函数。
注释:如果使用该函数,会绕过标准 PHP 错误处理程序,同时如果必要,用户定义错误程序通过 die() 终止脚本。
注释:如果错误发生在脚本执行之前(比如文件上传时),将不会调用自定义的错误处理程序因为它尚未在那时注册。
语法
set_error_handler(errorhandler,E_ALL|E_STRICT);
参数 | 描述 |
---|
errorhandler | 必需。规定用户错误处理函数的名称。 |
E_ALL|E_STRICT | 可选。规定显示何种错误报告级别的用户定义错误。默认是 "E_ALL"。 |
技术细节
返回值: | 包含之前定义的错误处理程序的字符串。 |
---|
PHP 版本: | 4.0.1+ |
---|
PHP 更新日志: | PHP 5.5:参数 errorhandler 现在接受 NULL PHP 5.2: 错误处理程序必须返回 FALSE 来显示 $php_errormsg。 |
---|
3.set_exception_handler
设置用户定义的异常处理函数:
<?php // 用户定义的异常处理函数 function myException($exception) { echo "<b>Exception:</b> ", $exception->getMessage(); } // 设置用户定义的异常处理函数 set_exception_handler("myException"); // 抛出异常 throw new Exception("Uncaught exception occurred!"); ?>
以上代码的输出类似这样:
Exception: Uncaught exception occurred!
定义和用法
set_exception_handler() 函数设置用户定义的异常处理函数。
脚本会在此异常处理程序被调用后停止执行。
语法
set_exception_handler(exceptionhandler);
参数 | 描述 |
---|
exceptionhandler | 必需。规定当一个未捕获的异常发生时所调用函数的名称。 注释:也可以传递一个 NULL 值用于重置异常处理函数为默认值。 |
技术细节
返回值: | 返回包含之前定义的异常处理程序的名称的字符串,或者在错误时返回 NULL。 如果之前没有定义一个错误处理程序,也会返回 NULL。 如果参数使用了 NULL,重置处理程序为默认状态,并且会返回一个 TRUE。 |
---|
PHP 版本: | 5.0+ |
---|
PHP 更新日志: | PHP 7.0.0:传递到 exception_handler 中的参数类型从 Exception 更改为 Throwable。 PHP 5.5:之前,如果传递 NULL,该函数返回 TRUE。从 PHP 5.5 起返回之前的处理程序。 |
---|
原创文章请随便转载。愿和大家分享,并且一起进步。-- 江 coder
为了增强现在正在开发的系统的健壮性,需要捕获运行时出现的无法预料而且没有被处理(unhandled)的异常。查了资料后,找到了使用
Application.ThreadException 事件处理这些异常的方法,基本步骤包括,
1、为ThreadException事件添加一个处理异常的函数句柄
2、定义处理异常的函数
例子如下:
[STAThread]
static void Main() { Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); Application.Run(new FrmMain()); } private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { MessageBox.Show("Unhandled exception: "+e.Exception.ToString()); }
这种方法简单易行,而且处理效率较高,可以按照用户的意图,很方便的添加处理异常理的其他功能。但我发现,如果使用第三方提供的控件时,根本不起作用,原应可能是第三方控件运行在不同的线程中。在Microsoft的帮助中也确实提到了,上面的方法只能处理主线程中未处理的异常。好了,上网查,找到了下面的方法,使用
AppDomain.UnhandledException 替代
Application.ThreadException。
首先需要了解的是,此时定义的事件处理函数需要在抛出异常的线程中执行,但是在主线程中给出异常提示都是在主线程中完成的,那么如何解决这个问题呢?下面的代码给出了一个比较完整的解决方案。
private delegate void ExceptionDelegate(Exception x);
static private FrmMain _MainForm;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
_MainForm = new FrmMain();
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomain_UnhandledException);
Application.Run(_MainForm);
}
private static void AppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception exception;
exception = e.ExceptionObject as Exception;
if (exception == null)
{
// this is an unmanaged exception, you may want to handle it differently
return;
}
PublishOnMainThread(exception);
}
private static void PublishOnMainThread(Exception exception)
{
if (_MainForm.InvokeRequired)
{
// Invoke executes a delegate on the thread that owns _MainForms's underlying window handle.
_MainForm.Invoke(new ExceptionDelegate(HandleException), new object[] {exception});
}
else
{
HandleException(exception);
}
}
private static void HandleException(Exception exception)
{
if (SystemInformation.UserInteractive)
{
using (ThreadExceptionDialog dialog = new ThreadExceptionDialog(exception))
{
if (dialog.ShowDialog() == DialogResult.Cancel)
return;
}
Application.Exit();
Environment.Exit(0);
}
}
private void ThreadMethod()
{
throw new Exception("From new thread");
}
private void button1_Click(object sender, System.EventArgs e)
{
Thread thread;
thread = new Thread(new ThreadStart(ThreadMethod));
thread.Start();
}
需要注意的是:
1、需要为所有的
AppDomain 的
UnhandledException 添加一个处理
2、
UnhandledExceptionEventArgs 参数中包含一个
IsTerminating 属性,表示是否中止 common language runtime