注冊(cè)錯(cuò)誤和異常處理機(jī)制有三個(gè)PHP函數(shù)需要學(xué)習(xí)
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
定義:該函數(shù)是來(lái)注冊(cè)一個(gè)會(huì)在PHP中止時(shí)執(zhí)行的函數(shù)
參數(shù)說(shuō)明:
void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )
注冊(cè)一個(gè) callback ,它會(huì)在腳本執(zhí)行完成或者 exit() 后被調(diào)用。
callback:待注冊(cè)的中止回調(diào)
parameter:可以通過(guò)傳入額外的參數(shù)來(lái)將參數(shù)傳給中止函數(shù)
PHP終止情況有三種
執(zhí)行完成
<?php function test() { echo '這個(gè)是中止方法test的輸出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; // => before // => 這個(gè)是中止方法test的輸出
注意輸出的順序,等執(zhí)行完成了之后才會(huì)去執(zhí)行register_shutdown_function的中止方法test
exit/die導(dǎo)致的中止
<?php function test() { echo '這個(gè)是中止方法test的輸出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; exit(); echo 'after' . PHP_EOL; // => before // => 這個(gè)是中止方法test的輸出
后面的after并沒(méi)有輸出,即exit或者是die方法導(dǎo)致提前中止。
發(fā)生致命錯(cuò)誤中止
<?php function test() { echo '這個(gè)是中止方法test的輸出'; } register_shutdown_function('test'); echo 'before' . PHP_EOL; // 這里會(huì)發(fā)生致命錯(cuò)誤 $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 // => 這個(gè)是中止方法test的輸出
后面的after也是沒(méi)有輸出,致命錯(cuò)誤導(dǎo)致提前中止了。
參數(shù):第一個(gè)參數(shù)支持以數(shù)組的形式來(lái)調(diào)用類中的方法,第二個(gè)以及后面的參數(shù)都是可以當(dāng)做額外的參數(shù)傳給中止方法。
<?php class Shutdown { public function stop() { echo "這個(gè)是stop方法的輸出"; } } // 當(dāng)PHP終止的時(shí)候(執(zhí)行完成或者是遇到致命錯(cuò)誤中止的時(shí)候)會(huì)調(diào)用new Shutdown的stop方法 register_shutdown_function([new Shutdown(), 'stop']); // 將因?yàn)橹旅e(cuò)誤而中止 $a = new a(); // 這一句并沒(méi)有執(zhí)行,也沒(méi)有輸出 echo '必須終止';
也可以在類中執(zhí)行:
<?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; /** 運(yùn)行: before class TestDemo->f():hello */
可以多次調(diào)用 register_shutdown_function,這些被注冊(cè)的回調(diào)會(huì)按照他們注冊(cè)時(shí)的順序被依次調(diào)用。
不過(guò)注意的是,如果在第一個(gè)注冊(cè)的中止方法里面調(diào)用exit方法或者是die方法的話,那么其他注冊(cè)的中止回調(diào)也不會(huì)被調(diào)用。代碼:<?php /** * 可以多次調(diào)用 register_shutdown_function,這些被注冊(cè)的回調(diào)會(huì)按照他們注冊(cè)時(shí)的順序被依次調(diào)用。 * 注意:如果你在f方法(第一個(gè)注冊(cè)的方法)里面調(diào)用exit方法或者是die方法的話,那么其他注冊(cè)的中止回調(diào)也不會(huì)被調(diào)用 */ /** * @param $str */ function f($str) { echo $str . PHP_EOL; // 如果下面調(diào)用exit方法或者是die方法的話,其他注冊(cè)的中止回調(diào)不會(huì)被調(diào)用 // exit(); } // 注冊(cè)第一個(gè)中止回調(diào)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; /** 運(yùn)行: before hello class TestDemo->f():hello 注意:如果f方法里面調(diào)用了exit或者是die的話,那么最后的class TestDemo->f():hello不會(huì)輸出 */
該函數(shù)的作用:
- 析構(gòu)函數(shù):在PHP4的時(shí)候,由于類不支持析構(gòu)函數(shù),所以這個(gè)函數(shù)經(jīng)常用來(lái)模擬實(shí)現(xiàn)析構(gòu)函數(shù)
- 致命錯(cuò)誤的處理:使用該函數(shù)可以用來(lái)捕獲致命錯(cuò)誤并且在發(fā)生致命錯(cuò)誤后恢復(fù)流程處理
代碼如下:
<?php /** * register_shutdown_function,注冊(cè)一個(gè)會(huì)在php中止時(shí)執(zhí)行的函數(shù),中止的情況包括發(fā)生致命錯(cuò)誤、die之后、exit之后、執(zhí)行完成之后都會(huì)調(diào)用register_shutdown_function里面的函數(shù) * Created by PhpStorm. * User: Administrator * Date: 2017/7/15 * Time: 17:41 */ class Shutdown { public function stop() { echo 'Begin.' . PHP_EOL; // 如果有發(fā)生錯(cuò)誤(所有的錯(cuò)誤,包括致命和非致命)的話,獲取最后發(fā)生的錯(cuò)誤 if (error_get_last()) { print_r(error_get_last()); } // ToDo:發(fā)生致命錯(cuò)誤后恢復(fù)流程處理 // 中止后面的所有處理 die('Stop.'); } } // 當(dāng)PHP終止的時(shí)候(執(zhí)行完成或者是遇到致命錯(cuò)誤中止的時(shí)候)會(huì)調(diào)用new Shutdown的stop方法 register_shutdown_function([new Shutdown(), 'stop']); // 將因?yàn)橹旅e(cuò)誤而中止 $a = new a(); // 這一句并沒(méi)有執(zhí)行,也沒(méi)有輸出 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異常類,這個(gè)類可以捕獲致命錯(cuò)誤,即可以使用try...catch(Throwable $e)來(lái)捕獲致命錯(cuò)誤,代碼如下:
<?php try { // 將因?yàn)橹旅e(cuò)誤而中止 $a = new a(); // 這一句并沒(méi)有執(zhí)行,也沒(méi)有輸出 echo 'end'; } catch (Throwable $e) { print_r($e); echo $e->getMessage(); }
運(yùn)行:
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來(lái)捕獲的話比使用register_shutdown_function這個(gè)函數(shù)來(lái)得更方便,也更推薦Throwable。
注意:Error類也是可以捕獲到致命錯(cuò)誤,不過(guò)Error只能捕獲致命錯(cuò)誤,不能捕獲異常Exception,而Throwable是可以捕獲到錯(cuò)誤和異常的,所以更推薦。
總結(jié):register_shutdown_function這個(gè)函數(shù)主要是用在處理致命錯(cuò)誤的后續(xù)處理上(PHP7更推薦使用Throwable來(lái)處理致命錯(cuò)誤),不過(guò)缺點(diǎn)也很明顯,只能處理致命錯(cuò)誤Fatal error,其他的錯(cuò)誤包括最高錯(cuò)誤Parse error也是沒(méi)辦法處理的。
2.set_error_handler
通過(guò) set_error_handler() 函數(shù)設(shè)置用戶自定義的錯(cuò)誤處理程序,然后觸發(fā)錯(cuò)誤(通過(guò) trigger_error()):
<?php // 用戶定義的錯(cuò)誤處理函數(shù) function myErrorHandler($errno, $errstr, $errfile, $errline) { echo "<b>Custom error:</b> [$errno] $errstr<br>"; echo " Error on line $errline in $errfile<br>"; } // 設(shè)置用戶定義的錯(cuò)誤處理函數(shù) set_error_handler("myErrorHandler"); $test=2; // 觸發(fā)錯(cuò)誤 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() 函數(shù)設(shè)置用戶定義的錯(cuò)誤處理函數(shù)。
注釋:如果使用該函數(shù),會(huì)繞過(guò)標(biāo)準(zhǔn) PHP 錯(cuò)誤處理程序,同時(shí)如果必要,用戶定義錯(cuò)誤程序通過(guò) die() 終止腳本。
注釋:如果錯(cuò)誤發(fā)生在腳本執(zhí)行之前(比如文件上傳時(shí)),將不會(huì)調(diào)用自定義的錯(cuò)誤處理程序因?yàn)樗形丛谀菚r(shí)注冊(cè)。
語(yǔ)法
set_error_handler(errorhandler,E_ALL|E_STRICT);
參數(shù) | 描述 |
---|
errorhandler | 必需。規(guī)定用戶錯(cuò)誤處理函數(shù)的名稱。 |
E_ALL|E_STRICT | 可選。規(guī)定顯示何種錯(cuò)誤報(bào)告級(jí)別的用戶定義錯(cuò)誤。默認(rèn)是 "E_ALL"。 |
技術(shù)細(xì)節(jié)
返回值: | 包含之前定義的錯(cuò)誤處理程序的字符串。 |
---|
PHP 版本: | 4.0.1+ |
---|
PHP 更新日志: | PHP 5.5:參數(shù) errorhandler 現(xiàn)在接受 NULL PHP 5.2: 錯(cuò)誤處理程序必須返回 FALSE 來(lái)顯示 $php_errormsg。 |
---|
3.set_exception_handler
設(shè)置用戶定義的異常處理函數(shù):
<?php // 用戶定義的異常處理函數(shù) function myException($exception) { echo "<b>Exception:</b> ", $exception->getMessage(); } // 設(shè)置用戶定義的異常處理函數(shù) set_exception_handler("myException"); // 拋出異常 throw new Exception("Uncaught exception occurred!"); ?>
以上代碼的輸出類似這樣:
Exception: Uncaught exception occurred!
定義和用法
set_exception_handler() 函數(shù)設(shè)置用戶定義的異常處理函數(shù)。
腳本會(huì)在此異常處理程序被調(diào)用后停止執(zhí)行。
語(yǔ)法
set_exception_handler(exceptionhandler);
參數(shù) | 描述 |
---|
exceptionhandler | 必需。規(guī)定當(dāng)一個(gè)未捕獲的異常發(fā)生時(shí)所調(diào)用函數(shù)的名稱。 注釋:也可以傳遞一個(gè) NULL 值用于重置異常處理函數(shù)為默認(rèn)值。 |
技術(shù)細(xì)節(jié)
返回值: | 返回包含之前定義的異常處理程序的名稱的字符串,或者在錯(cuò)誤時(shí)返回 NULL。 如果之前沒(méi)有定義一個(gè)錯(cuò)誤處理程序,也會(huì)返回 NULL。 如果參數(shù)使用了 NULL,重置處理程序?yàn)槟J(rèn)狀態(tài),并且會(huì)返回一個(gè) TRUE。 |
---|
PHP 版本: | 5.0+ |
---|
PHP 更新日志: | PHP 7.0.0:傳遞到 exception_handler 中的參數(shù)類型從 Exception 更改為 Throwable。 PHP 5.5:之前,如果傳遞 NULL,該函數(shù)返回 TRUE。從 PHP 5.5 起返回之前的處理程序。 |
---|
原創(chuàng)文章請(qǐng)隨便轉(zhuǎn)載。愿和大家分享,并且一起進(jìn)步。-- 江 coder
為了增強(qiáng)現(xiàn)在正在開(kāi)發(fā)的系統(tǒng)的健壯性,需要捕獲運(yùn)行時(shí)出現(xiàn)的無(wú)法預(yù)料而且沒(méi)有被處理(unhandled)的異常。查了資料后,找到了使用
Application.ThreadException 事件處理這些異常的方法,基本步驟包括,
1、為ThreadException事件添加一個(gè)處理異常的函數(shù)句柄
2、定義處理異常的函數(shù)
例子如下:
[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()); }
這種方法簡(jiǎn)單易行,而且處理效率較高,可以按照用戶的意圖,很方便的添加處理異常理的其他功能。但我發(fā)現(xiàn),如果使用第三方提供的控件時(shí),根本不起作用,原應(yīng)可能是第三方控件運(yùn)行在不同的線程中。在Microsoft的幫助中也確實(shí)提到了,上面的方法只能處理主線程中未處理的異常。好了,上網(wǎng)查,找到了下面的方法,使用
AppDomain.UnhandledException 替代
Application.ThreadException。
首先需要了解的是,此時(shí)定義的事件處理函數(shù)需要在拋出異常的線程中執(zhí)行,但是在主線程中給出異常提示都是在主線程中完成的,那么如何解決這個(gè)問(wèn)題呢?下面的代碼給出了一個(gè)比較完整的解決方案。
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 添加一個(gè)處理
2、
UnhandledExceptionEventArgs 參數(shù)中包含一個(gè)
IsTerminating 屬性,表示是否中止 common language runtime