注冊錯誤和異常處理機制有三個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