異常處理 本文面向希望瞭解PHP5異常處理機制的程式員。閱讀本文你需要具有一定物件導向編程和PHP基礎。
PHP5內建的異常類需要有以下成員方法:
__construct()建構函式,需要一個出錯資訊和一個可選的整型錯誤標記作參數getMessage()取得出錯資訊 getCode()
出錯的代碼 getFile()異常發生的檔案getLine()異常發生的行數 getTrace()跟蹤異常每一步傳遞的路線,存入數組,返回該數組 getTraceAsString()和getTrace()功能一樣,但可以將數組中的元素轉成字串並按一定格式輸出
可以看出來,Exception 類的結構和Pear_Error 很相似。當你的指令碼中遇到一個錯誤,你可以建立你的異常對象:
$ex = new Exception( "Could not open $this->file" );
Exception類的建構函式將接受一個出錯資訊和一個錯誤碼。
使用 throw關鍵字
建立一個Exception對象後你可以將對象返回,但不應該這樣使用,更好的方法是用throw關鍵字來代替。throw用來拋出異常:
throw new Exception( "my message", 44 );
throw 將指令碼的執行中止,並使相關的Exception對象對客戶代碼可用。
以下是改進過的getCommandObject() 方法:
index_php5.php
<?php // PHP 5
require_once('cmd_php5/Command.php');
class CommandManager {
private $cmdDir = "cmd_php5";
function getCommandObject($cmd) {
$path = "{$this->cmdDir}/{$cmd}.php";
if (!file_exists($path)) {
throw new Exception("Cannot find $path");
}
require_once $path;
if (!class_exists($cmd)) {
throw new Exception("class $cmd does not exist");
}
$class = new ReflectionClass($cmd);
if (!$class->isSubclassOf(new ReflectionClass('Command'))) {
throw new Exception("$cmd is not a Command");
}
return new $cmd();
}
}
?>
代碼中我們使用了PHP5的反射(Reflection)API來判斷所給的類是否是屬於Command 類型。在錯誤的路徑下執行本指令碼將會報出這樣的錯誤:
Fatal error: Uncaught exception 'Exception' with message 'Cannot find command/xrealcommand.php' in /home/xyz/BasicException.php:10
Stack trace:
#0 /home/xyz/BasicException.php(26):
CommandManager->getCommandObject('xrealcommand')
#1 {main}
thrown in /home/xyz/BasicException.php on line 10
預設地,拋出異常導致一個fatal error。這意味著使用異常的類內建有安全機制。而僅僅使用一個錯誤標記,不能擁有這樣的功能。處理錯誤標記失敗只會你的指令碼使用錯誤的值來繼續執行。
Try-catch 語句
為了進一步處理異常,我們需要使用try-catch語句—包括Try語句和至少一個的catch語句。任何調用 可能拋出異常的方法的代碼都應該使用try語句。Catch語句用來處理可能拋出的異常。以下顯示了我們處理getCommandObject()拋出的異常的方法:
index_php5.php 後半段
<?php
// PHP 5
try {
$mgr = new CommandManager();
$cmd = $mgr->getCommandObject('realcommand');
$cmd->execute();
} catch (Exception $e) {
print $e->getMessage();
exit();
}
?>
可以看到,通過結合使用throw關鍵字和try-catch語句,我們可以避免錯誤標記“汙染”類方法返回的值。因為“異常”本身就是一種與其它任何對象不同的PHP內建的類型,不會產生混淆。
如果拋出了一個異常,try語句中的指令碼將會停止執行,然後馬上轉向執行catch語句中的指令碼。
如果異常拋出了卻沒有被捕捉到,就會產生一個fatal error。
處理多個錯誤
在目前為止異常處理看起來和我們傳統的作法—檢驗返回的錯誤標識或對象的值沒有什麼太大區別。讓我們將CommandManager處理地更謹慎,並在建構函式中檢查command目錄是否存在。
index_php5_2.php
<?php
// PHP 5
require_once('cmd_php5/Command.php');
class CommandManager {
private $cmdDir = "cmd_php5";
function __construct() {
if (!is_dir($this->cmdDir)) {
throw new Exception("directory error: $this->cmdDir");
}
}
function getCommandObject($cmd) {
$path = "{$this->cmdDir}/{$cmd}.php";
if (!file_exists($path)) {
throw new Exception("Cannot find $path");
}
require_once $path;
if (!class_exists($cmd)) {
throw new Exception("class $cmd does not exist");
}
$class = new ReflectionClass($cmd);
if (!$class->isSubclassOf(new ReflectionClass('Command'))) {
throw new Exception("$cmd is not a Command");
}
return new $cmd();
}
}
?>
這裡有兩個地方的調用可能導致程式出錯(__construct()和getCommandObject())。儘管如此,我們不需要調整我們的客戶代碼。你可以在try語句中增添眾多內容,然後在catch中統一處理。如果CommandManager 對象的建構函式拋出一個異常,則try語句中的執行中止,然後catch語句被調用捕捉相關的異常。同樣地,getCommandObject()也是如此。這樣,我們有同時存在兩個潛在的引發錯誤的地方,和一個唯一的語句來處理所有的錯誤。這讓我們的代碼看起來更加整潔,又可以滿足錯誤處理的要求。和前面提到的PHP的傳統的錯誤方法相比,顯然很有優勢。
index_php5_2.php 後半段
注意:儘管和index_php5.php相比,前半段代碼有兩個可能出錯的地方,這段代碼和index_php5.php的後半段完全相同。
<?php
// PHP 5
try {
$mgr = new CommandManager(); // potential error
$cmd = $mgr->getCommandObject('realcommand');
// another potential error
$cmd->execute();
} catch (Exception $e) {
// handle either error here
print $e->getMessage();
exit();
}
?>
還有一個地方我們沒有提到。我們怎樣區分不同類型的錯誤?例如,我們可能希望用一種方法來處理找不到目錄的錯誤,而用另一種方法來處理非法的command類。
Exception類可以接受一個可選的整型的錯誤標識,這是在catch語句中區分不同錯誤類型的一個方法。
index_php5_3.php
<?php
// PHP 5
require_once('cmd_php5/Command.php');
class CommandManager {
private $cmdDir = "cmd_php5";
const CMDMAN_GENERAL_ERROR = 1;
const CMDMAN_ILLEGALCLASS_ERROR = 2;
function __construct() {
if (!is_dir($this->cmdDir)) {
throw new Exception("directory error: $this->cmdDir", self::CMDMAN_GENERAL_ERROR);
}
}
function getCommandObject($cmd) {
$path = "{$this->cmdDir}/{$cmd}.php";
if (!file_exists($path)) {
throw new Exception("Cannot find $path", self::CMDMAN_ILLEGALCLASS_ERROR);
}
require_once $path;
if (!class_exists($cmd)) {
throw new Exception("class $cmd does not exist", self::CMDMAN_ILLEGALCLASS_ERROR);
}
$class = new ReflectionClass($cmd);
if (!$class->isSubclassOf(new ReflectionClass('Command'))) {
throw new Exception("$cmd is not a Command", self::CMDMAN_ILLEGALCLASS_ERROR);
}
return $class->newInstance();
}
}
?>
通過傳遞 CMDMAN_ILLEGALCLASS_ERROR和 CMDMAN_GENERAL_ERROR其中之一的參數給我們拋出的異常對象,我們就可以讓客戶代碼區分不同類型的錯誤,並定義不同的處理策略。
index_php5_3.php
<?php // PHP 5
try {
$mgr = new CommandManager();
$cmd = $mgr->getCommandObject('realcommand');
$cmd->execute();
} catch (Exception $e) {
if ($e->getCode() == CommandManager::CMDMAN_GENERAL_ERROR) {
// no way of recovering
die($e->getMessage());
} else if ($e->getCode() == CommandManager::CMDMAN_ILLEGALCLASS_ERROR) {
error_log($e->getMessage());
print "attempting recovery\n";
// perhaps attempt to invoke a default command?
}
}
?>
我們也可以用另一種方法來實現這樣的效果—從最根本的Exception類中派生出代表不同類型異常的子類,再拋出和捕捉。
[1] [2] 下一頁