SlideShare ist ein Scribd-Unternehmen logo
1 von 52
Downloaden Sie, um offline zu lesen
精通 PHP 錯誤處理
讓除錯更自在
PHP 也有 Day #35
2018.05.29
Simon Asika (飛鳥)
認識 PHP 的 Error
PHP 的常見錯誤種類
• E_ERROR 執行期的 Fatal Error,無法進行錯誤修復,程式會直接停止。
• E_WARNING 警告,但不會停止程式。
• E_NOTICE 不屬於錯誤,但可能會發生錯誤,因此提示你。
• E_STRICT 更嚴格的 PHP 規範提示。
• E_DEPRECATED 被棄用的 function 等,提示你趕快換掉成新的用法。
• 其它
 E_PARCE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, E_COMPILE_ERROR,
E_RECOVERABLE_ERROR, E_ALL 等
 See http://php.net/manual/en/errorfunc.constants.php
E_ERROR
• 產生以下錯誤
Fatal error: Uncaught Error: Call to undefined function bar() in
D:wwwslimpublicindex.php:7
• 因為 function 根本不存在,系統無從猜測可能的行為,也無法修復錯誤,故為
Fatal Error,強制停止程式運作。
function foo() {
echo 'foo';
}
bar(); // 呼叫了不存在的 function
E_WARNING
• 產生以下錯誤畫面:
Warning: A non-numeric value encountered in D:wwwslimpublicindex.php on line 3
123
• 不正確的資料操作與計算,可能造成程式 BUG,但是系統可以修復其行為,所以
程式不會中斷,可以被隱藏。
$result = 123 + 'ABC'; // 數字加字串
echo sprintf('<span style="color: red;">%s</span>', $result);
E_NOTICE
• 產生以下畫面
Notice: Undefined variable: b in D:wwwslimpublicindex.php on line 6
A
• $b 沒有被預先宣告,這種寫法在 PHP 中是允許的,但是這樣子有很大機率出現
BUG ,故 PHP 會用 Notice 提示你最好預先宣告。
$a = 'A';
$ab = $a . $b; // $b 不存在
echo $ab;
E_STRICT
• 產生以下錯誤訊息
Warning: Declaration of B::foo() should be compatible with A::foo($a = 123)
in D:wwwslimpublicindex.php on line 17
對PHP開發過程更嚴謹的要求。
class A {
public function foo($a = 123)
{
}
}
class B extends A
{
// 與 parent class 介面不一樣
public function foo()
{
}
}
E_DEPRECATED
• 產生以下畫面
Deprecated: Function mcrypt_create_iv() is deprecated
in D:wwwslimpublicindex.php on line 6
�Ի��eD��IN�
• 若使用了已被棄用的語言功能就會發出此提示,提醒你趕快改用新功能。
error_reporting(E_ALL);
// PHP 7.2 deprecated mcrypt
$v = mcrypt_create_iv(16);
echo $v;
你也可以產生屬於自己的錯誤訊息
• 用 trigger_error() 來立即觸發使用者定義的錯誤訊息
• 預設值是 E_USER_NOTICE,所以程式會繼續執行下去,只是跳 Notice 訊息。
Notice: $a is not A in D:wwwslimpublicindex.php on line 6
B
$a = 'B';
if ($a !== 'A') {
trigger_error('$a is not A', E_USER_NOTICE);
}
echo $a;
改用 E_USER_ERROR
• 這次用的是 ERROR type,程式就會終止執行了:
Fatal error: $a is not A in D:wwwslimpublicindex.php on line 6
• 可用的種類有:
 E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED 等
 大多數 E_* 的錯誤訊息,都有 E_USER_* 的對應
if ($a !== 'A') {
trigger_error('$a is not A', E_USER_ERROR);
}
看不到 Notice 或 Deprecated 怎麼辦
• 可以在 php.ini 修改 error_reporting 直接用 E_ALL
 error_reporting = E_ALL
• 或是直接 runtime 時用 function 設定:
// 回報所有錯誤, php 5.4 以後 E_STRICT 包含在內
error_reporting(E_ALL);
// 回報特定錯誤
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
// 回報所有錯誤,Notice 除外
error_reporting(E_ALL & ~E_NOTICE);
// 設為 -1 是最大值,所有可能的錯誤全部顯示,無關版本
error_reporting(-1);
// 關閉錯誤訊息,不顯示。
error_reporting(0);
用 @ 隱藏錯誤訊息
• 可放在一行的最開頭,或是 function call 前面。通常用在極度不確定外部輸入格式時。
• 將這一行的錯誤訊息隱藏,這個案例中原本會產生下面的訊息,但實際上被屏蔽了:
Warning: substr() expects parameter 1 to be string, array given
• 其原理是在這一行開始執行時,背景將 error_reporting 改成 0,然後下一行執行前再改
回來。
• 所以如果有特別寫錯誤處理器的話,依然抓的到此錯誤。
@$a = substr([], 0, 5);
var_dump($a);
建議的設定
• PHP 的預設設定是 E_ALL & ~E_NOTICE ,意思是顯示所有錯誤,Notice 除外。
• 開發過程,建議強制設成 E_ALL 或 -1,開發者應該要清空所有可能的 Warning &
Notice 不要讓任何可能的 BUG 有機會出現。
• 老舊系統運作時,考慮設成 0,不要讓使用者看見大量的 Notice 訊息。
• 網站正式運作時,也可以設定成 0。但最好確保重要的錯誤有被 log 記錄下來。
• 從每個字敲出來就用最高標準對待自己的程式碼,未來的錯誤才會少。
不養成良好的編寫習慣,其它開發者拿到你的 code 就
變成這樣
寫程式時要保持這種心態:就好像將來要維護你這些代碼的人是一位
殘暴的精神病患者,而且他知道你住在哪。
--- John Woods (1991)
自行捕獲錯誤
使用 set_error_handler() 捕獲錯誤
• 會印出:
錯誤[2]: substr() expects parameter 1 to be string, array given - 位置:
D:wwwslimpublicindex.php (8)
• 變數說明
 $code 錯誤碼,例如 E_WARNING 就是 2
 $msg 錯誤訊息
 $file/$line 錯誤發生的檔案與行
 $context 錯誤當下的環境相關資訊 (php7.2 deprecated)
set_error_handler(function ($code, $msg, $file, $line, $context) {
echo sprintf('錯誤[%d]: %s - 位置: %s (%s)', $code, $msg, $file, $line);
die;
});
$a = substr([], 0, 5);
• 加上一點改變,現在我們可以根據錯誤碼加上不同的提示 (這裡只抓前四個示範)
• 本範例會印出
警告: substr() expects parameter 1 to be string, array given - 位置: D:wwwslimpublicindex.php (23)
set_error_handler(function ($code, $msg, $file, $line) {
$errormaps = [
E_ERROR => '錯誤', // 1
E_WARNING => '警告', // 2
E_PARSE => '語法錯誤', // 4
E_NOTICE => '提醒', // 8
];
echo sprintf(
'%s: %s - 位置: %s (%s)',
$errormaps[$code] ?? '錯誤', // 用 code 取出錯誤說明
$msg,
$file,
$line
);
die;
});
$a = substr([], 0, 5);
• 但是你會發現,現在加上 @ 或關閉錯誤訊息都沒用。為內建的錯誤處理已經被我
們強制覆蓋了。
• 照樣印出錯誤訊息
警告: substr() expects parameter 1 to be string, array given - 位置:
D:wwwslimpublicindex.php (23)
error_reporting(0); // 這個沒用了
set_error_handler(function ($code, $msg, $file, $line) {
$errormaps = [
// ... 略
];
echo sprintf(
'%s: %s - 位置: %s (%s)',
$errormaps[$code] ?? '錯誤', // 用 code 取出錯誤說明
$msg,
$file,
$line
);
die;
});
@$a = substr([], 0, 5); // 加上 @ 也沒用了
• 之前有說過,@ 的作用就是當下即時把 error_reporting 改成 0,所以我們加上
error_reporting 的判斷,如果是 0,就直接略過。
• 現在不會再出現錯誤訊息了。
set_error_handler(function ($code, $msg, $file, $line) {
if (error_reporting() === 0) {
return;
}
$errormaps = [
// ... 略
];
echo sprintf(
'%s: %s - 位置: %s (%s)',
$errormaps[$code] ?? '錯誤',
$msg,
$file,
$line
);
die;
});
@$a = substr([], 0, 5);
• 如果你希望錯誤的顯示與否與之前設定 error_reporting 的內容相同,則可以用位
元運算子的 & 符號來做比對
• 詳情請見
 http://bit.ly/2LyEcbC
 https://stackoverflow.com/questions/4705838/when-should-i-use-a-bitwise-operator
 http://php.net/manual/en/language.operators.bitwise.php
set_error_handler(function ($code, $msg, $file, $line) {
if ((error_reporting() & $code) === 0) {
return;
}
// ...
});
$a = substr([], 0, 5);
搭配 log 紀錄錯誤訊息
• 隱蔽錯誤是有風險的,我們可以嘗試把錯誤都記錄在 log 內,至少發生不明錯誤時
還有紀錄可以查詢
• error_log() 的參數說明請見 http://php.net/manual/en/function.error-log.php
set_error_handler(function ($code, $msg, $file, $line) {
// ...
error_log($msg . PHP_EOL, 3, __DIR__ . '/logs/error.log');
// ...
});
• 左邊是完整的範例
• 先組好messsage,然後立即 log
• 接著判斷 error_reporting 有必要才
echo 錯誤訊息
• error_log() 不會幫你換行,記得加上
換行符號。
set_error_handler(function ($code, $msg, $file, $line) {
$errormaps = [
E_ERROR => '錯誤', // 1
E_WARNING => '警告', // 2
E_PARSE => '語法錯誤', // 4
E_NOTICE => '提醒', // 8
];
$msg = sprintf(
'%s: %s - 位置: %s (%s)',
$errormaps[$code] ?? '錯誤',
$msg,
$file,
$line
);
error_log($msg . PHP_EOL, 3, __DIR__ . '/logs/error.log');
if ((error_reporting() & $code) === 0) {
return;
}
echo $msg;
die;
});
log 紀錄結果
• 這只是一個簡單的範例,實際的網站開發請另外使用 Monolog 之類的套件來處理
log檔,並記得做 rotating 免得log塞爆。
• 有 DevOps 人員或採用 microservice 的團隊,可以考慮把 log 服務拉出去成為一台
獨立伺服器,所有訊息都往遠端打出去,就不用擔心 log 爆量問題。
• 主流框架大多都幫你處理好這些工作了,感謝上天,感謝 Opensource。
別忘了做個美美的錯誤畫面
• 送出 500 HTTP 錯誤碼,然後 render 錯誤畫面,畫上可愛的插圖,大功告成。
set_error_handler(function ($code, $msg, $file, $line) {
// ...略
if ((error_reporting() & $code) === 0) {
return;
}
http_response_code(500);
echo view('error.default', compact(['msg', 'code', 'file', 'line']));
die;
});
可以把錯誤當作 Exception 丟出喔
• Warning 可以當作 Exception 一樣 catch 到,很神奇吧。這樣就可以把所有錯誤一致
性的交給 exception handler 處理了。
• 注意: Fatal Error 不能 catch
set_error_handler(function ($code, $msg, $file, $line) {
// ...略
if ((error_reporting() & $code) === 0) {
return;
}
throw new ErrorException($msg, 500, $code, $file, $line);
});
try {
$a = substr([], 0, 5);
} catch (ErrorException $e) {
echo $e;
}
認識 Exception
如何使用 Exception
• 任何開發者自己認為是錯誤的地方,都可以丟出 Exception 中斷程式流程。
• 丟出 Exception 後,下方的程式就不會再執行,但此時整個程序沒有終止 (不像 Fatal Error 會直接終
止)
• 我們可以 catch 丟出去的 Exception,然後轉而執行其它程式或流程。
$a = 'B';
if ($a !== 'A') {
throw new RuntimeException('$a is not A', 500); // 從這裡中斷執行
}
echo $a; // 這裡不會再執行了...
捕獲 Exception
• 用 try ... catch 包住 Exception,就能自訂中斷流程,做額外的錯誤處理
• 在 try 區塊裡面,只要丟出 Exception 的話,後方程式就不會再執行,但會跳到 catch 的
區塊,所以可以另外執行除錯工作。
• 如果在 catch 內沒有 die 掉程式的話,try ... catch 後面的程式可以繼續執行,不會終止
程序。
$a = 'B';
try {
if ($a !== 'A') {
throw new RuntimeException('$a is not A'); // 直接跳出
}
echo $a; // 這裡不會執行
} catch (Exception $e) {
error_log($e->getMessage(), 3, 'logs/error.log'); // '$a is not A’
}
echo 123; // 這裡又可以繼續執行了
更複雜的案例
• Exception 不一定是自己丟出的,也可能是
核心獲第三方函市庫丟出來的,範例中丟
出的 PDOExcception 通常是 SQL 有誤時會丟
出。
• Exception 可以分多個種類,用不同的 catch
來補獲。
• 越下面的 catch 包含範圍越廣大。
• 最後可以用一個 finally 來執行出現錯誤後一
定要做的任何處理。
$pdo = new PDO('mysql:...');
try {
$pdo->prepare($sql)->execute();
} catch (PDOException $e) {
// 處理 PDO 本身的錯誤
} catch (Exception $e) {
// 處理其它可能的錯誤
} catch (Throwable $e) {
// 處理 php7 error
} finally {
unset($pdo); // 終止連線
}
echo '這裡會繼續執行';
善用 Code 判斷錯誤種類
• 就算是相同的 Exception 類型,也可以用 code 來區隔其錯誤狀態。
try {
if (!$user->isLogin()) {
throw new RuntimeException('Access denied.', 401);
}
// ...略
} catch (RuntimeException $e) {
if ($e->getCode() === 401) {
// 未登入
} elseif ($e->getCode() === 404) {
// 找不到頁面
} else {
// 其它錯誤
}
}
自定義 Exception
• 你可以繼承 Exception 或 RuntimeException 建立自己的 Exception
• 拋出之後,用 catch (IDontFeelSoGood $e) 就可以針對這個 Exception 做自訂
義錯誤處理。
class IDontFeelSoGoodException extends Exception
{
}
if (count($infinityGems) === 6) {
throw new IDontFeelSoGoodException('Tony I'm sorry', 404);
}
Catch 到 Exception 之後怎麼辦
• 範例中根據捕獲的 Exception 種類有不
同的操作。
• 有的做 redirect,有的直接 404 或 403。
• 善用自訂義 Exceptions 搭配多層 catch
可以做到很靈活的錯誤處理。
try {
User::save($userData);
} catch (UserNotLoginException $e) {
header('Location: /login');
} catch (UserNotFoundException $e) {
http_response_code(404);
die('Sorry, this user not found.');
} catch (UnauthorisedExceotion $e) {
http_response_code(403);
die('Forbidden');
}
Exception 可以無視層次跳躍
• 範例中 Exception 是在 function 內拋出,但是外部的 try ... catch 可以抓到。
• Exception 是無視層次的,會向上一直跳到有 try ... catch 的地方才停止。
• 如果沒有 try ... catch,則會跳到最上層,成為 Fatal Error 終止程序。
function foo($a) {
if ($a !== 'A') {
throw new RuntimeException('$a is not A');
}
echo $a;
}
try {
foo('B');
} catch (RuntimeException $e) {
echo $e->getMessage();
}
沒有捕獲 Exception 的結果
• 會顯示 Uncaught Exception 然後終止程序
Fatal error: Uncaught RuntimeException: $a is not A in D:wwwslimpublicindex.php:5
Stack trace: #0 D:wwwslimpublicindex.php(11): foo('B') #1 {main} thrown
in D:wwwslimpublicindex.php on line 5
function foo($a) {
if ($a !== 'A') {
throw new RuntimeException('$a is not A');
}
echo $a;
}
foo('B');
但我們一樣可以捕獲最上層 Exception
• 還記得前面的 set_error_handler() 嗎?
• 我們也可以用 set_exception_handler(); 來抓取拋到最外層的 Exception。
set_exception_handler(function (Throwable $e) {
http_response_code($e->getCode());
echo <<<HTML
<h1>{$e->getMessage()}</h1>
<strong>Code:</strong> {$e->getCode()} <br/>
<strong>File:</strong> <code>{$e->getFile()} ({$e->getLine()})</code>
<h3>Call Stack</h3>
<pre>{$e->getTraceAsString()}</pre>
HTML;
die;
});
throw new RuntimeException('Oops, something went wrong.', 403);
抓取到 Exception 後的結果
抓取到 Exception 後的結果
看!很簡單吧?
我們完成了自製的錯誤處理器
把雙劍客合起來用
• 前面提到 error handler 可以把錯
誤當做 Exception 丟出去。
• 所以搭配 exception handler 就可
以集中處理所有可能的錯誤訊息。
• 這裡開始就複雜多了,沒關係,
框架們都幫你搞定了。
• 還有很多靈活用法,請參考:
 http://www.w3school.com.cn/php/p
hp_exception.asp
 https://code.tutsplus.com/tutorials/p
hp-exceptions--net-22274
// 把所有 Error 也當作 Exception 丟出去
set_error_handler(function ($code, $msg, $file, $line) {
// ...
throw new ErrorException($msg, 500, $code, $file, $line);
});
// 所有的 Error, Warning, Notice & Exceptions 通通集中在這邊處理
set_exception_handler(function (Throwable $e) {
http_response_code($e->getCode());
echo <<<HTML
<h1>{$e->getMessage()}</h1>
<strong>Code:</strong> {$e->getCode()} <br/>
<strong>File:</strong> <code>{$e->getFile()} ({$e-
>getLine()})</code>
<h3>Call Stack</h3>
<pre>{$e->getTraceAsString()}</pre>
HTML;
die;
});
PHP7 的
Exceptions
See http://asika.windspeaker.co/post/3503-php-exceptions
Exception的使用觀念
例外不是錯誤
Exception 不代表 Error,他可以是流程控制的一部份
但必須被認定為【異常狀況】
異常狀況的處理
• 在一個大量迴圈的任務中,我們希望即便少數的 job 失敗了,還是要繼續跑完後面
的 jobs
• 且我們希望每一個 Job 失敗時,會 mail 通知管理員
• 因此在這邊,Exception 做為異常狀況處理器,會控制流程去寄送通知信,但又不
中斷程序,使得迴圈繼續跑下去。
foreach ($jobs as $job) {
try {
Queue::process($job);
} catch (QueueException $e) {
Mailer::send('A queue job error', $message);
}
}
但Exception也不是流程控制
可以用 if else 解決的問題,就不要用 Exception
不要這樣用
• 如果你只是想讓未登入 user 轉過去登入頁面,在這個案例中,用 if 就能處理了
try {
if ($user->group === 'guest') {
throw new UnauthorisedException('Please login', 401);
} elseif (...) {
} elseif (...) {
} else {
}
} catch (UnauthorisedException $e) {
header('Location: /login');
}
if ($user->group === 'guest') {
header('Location: /login');
}
但可以這樣用
• 這個案例中,user group 如果出現預定義的格式以外的值,肯定屬於異常狀況,就
直接拋出 Exception 吧。
switch ($user->group) {
case 'guest':
// Please login first.
case 'member':
// Welcome Back
case 'manager':
// Sir, yes sir.
default:
throw new UnauthorisedException('Uh... Who are you?', 403);
}
或是這樣用
• 將多個 function 呼叫的 Exception 做集中處理,可以讓異常處理流程更乾淨易讀。
try {
$obj->methodA();
$obj->methodB();
$obj->methodC();
} catch (BadRouteException $e) {
} catch (PDOException $e) {
} catch (RuntimeException $e) {
} catch (Throwable $e) {
}
Exception 一定要處理
不要隱蔽錯誤
try {
foo();
} catch (RuntimeException $e) {
// No action
}
使用 Exception 的情境
• 通常比較少在同一個空間內同時 try ... catch 又同時 throw Exception。
• 開發 function 的人可以根據異常狀況拋出各種 Exception,幫助使用 function 的人
處理例外流程。
• 使用 function 的人可以用 try ... catch 捕獲 function 的異常,然後處理可能的錯誤
修復。
• 是否是【異常】非常重要,既定的可預期流程,都應該用 if else 處理。但是異常
的處理可以放心用 Exception 作流程跳轉。
防禦型程式設計
public function __construct($string, $int, $array)
{
// 最基本的檢查,型別不對就丟錯
if (!is_string($string))
{
throw new InvalidArgumentException('Argument 1 should be string.');
}
// 這個檢查比較鬆一點,只要是數字都可以過,不一定要 int 型態
if (!is_numeric($int))
{
throw new InvalidArgumentException('Argument 2 should be a number.');
}
// 這個檢查比較特別,如果是 Iterator 物件也能夠接受,因為同樣可以 foreach
if (!is_array($array) && !($array instanceof Traversable))
{
throw new InvalidArgumentException('Argument 3 should be Traversable.');
}
// Do some stuff
}
願各位的程式都能自動修復錯誤
--- Thank You

Weitere ähnliche Inhalte

Was ist angesagt?

SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)
SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)
SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)makopi 23
 
用十分鐘搞懂《離散數學》
用十分鐘搞懂《離散數學》用十分鐘搞懂《離散數學》
用十分鐘搞懂《離散數學》鍾誠 陳鍾誠
 
Laravel の paginate は一体何をやっているのか
Laravel の paginate は一体何をやっているのかLaravel の paginate は一体何をやっているのか
Laravel の paginate は一体何をやっているのかShohei Okada
 
例外處理實務
例外處理實務例外處理實務
例外處理實務Jeff Chu
 
Java SE 8 技術手冊第 12 章 - Lambda
Java SE 8 技術手冊第 12 章 - LambdaJava SE 8 技術手冊第 12 章 - Lambda
Java SE 8 技術手冊第 12 章 - LambdaJustin Lin
 
生產與作業管理
生產與作業管理生產與作業管理
生產與作業管理5045033
 
社内のマニュアルをSphinxで作ってみた
社内のマニュアルをSphinxで作ってみた社内のマニュアルをSphinxで作ってみた
社内のマニュアルをSphinxで作ってみたIosif Takakura
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計Yoshinori Matsunobu
 
LINEのMySQL運用について 修正版
LINEのMySQL運用について 修正版LINEのMySQL運用について 修正版
LINEのMySQL運用について 修正版LINE Corporation
 
SolrのAtomicUpdateを50000倍速くした話
SolrのAtomicUpdateを50000倍速くした話SolrのAtomicUpdateを50000倍速くした話
SolrのAtomicUpdateを50000倍速くした話Takahiro Ishikawa
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話ichirin2501
 
Where狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキーWhere狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキーyoku0825
 
捉住觀眾注意力的簡報技巧
捉住觀眾注意力的簡報技巧捉住觀眾注意力的簡報技巧
捉住觀眾注意力的簡報技巧滄碩 劉
 
チーム開発をうまく行うためのコーディング規約論
チーム開発をうまく行うためのコーディング規約論チーム開発をうまく行うためのコーディング規約論
チーム開発をうまく行うためのコーディング規約論Kentaro Matsui
 
Become A Security Master
Become A Security MasterBecome A Security Master
Become A Security MasterChong-Kuan Chen
 
Web時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメWeb時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメHideyuki Takeuchi
 

Was ist angesagt? (20)

SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)
SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)
SQLアンチパターン読書会 4章 キーレスエンエントリ(外部キー嫌い)
 
用十分鐘搞懂《離散數學》
用十分鐘搞懂《離散數學》用十分鐘搞懂《離散數學》
用十分鐘搞懂《離散數學》
 
OOAD
OOADOOAD
OOAD
 
Laravel の paginate は一体何をやっているのか
Laravel の paginate は一体何をやっているのかLaravel の paginate は一体何をやっているのか
Laravel の paginate は一体何をやっているのか
 
例外處理實務
例外處理實務例外處理實務
例外處理實務
 
Java SE 8 技術手冊第 12 章 - Lambda
Java SE 8 技術手冊第 12 章 - LambdaJava SE 8 技術手冊第 12 章 - Lambda
Java SE 8 技術手冊第 12 章 - Lambda
 
生產與作業管理
生產與作業管理生產與作業管理
生產與作業管理
 
社内のマニュアルをSphinxで作ってみた
社内のマニュアルをSphinxで作ってみた社内のマニュアルをSphinxで作ってみた
社内のマニュアルをSphinxで作ってみた
 
ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計ソーシャルゲームのためのデータベース設計
ソーシャルゲームのためのデータベース設計
 
使用者認證
使用者認證使用者認證
使用者認證
 
LINEのMySQL運用について 修正版
LINEのMySQL運用について 修正版LINEのMySQL運用について 修正版
LINEのMySQL運用について 修正版
 
SolrのAtomicUpdateを50000倍速くした話
SolrのAtomicUpdateを50000倍速くした話SolrのAtomicUpdateを50000倍速くした話
SolrのAtomicUpdateを50000倍速くした話
 
外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話外部キー制約に伴うロックの小話
外部キー制約に伴うロックの小話
 
Where狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキーWhere狙いのキー、order by狙いのキー
Where狙いのキー、order by狙いのキー
 
捉住觀眾注意力的簡報技巧
捉住觀眾注意力的簡報技巧捉住觀眾注意力的簡報技巧
捉住觀眾注意力的簡報技巧
 
チーム開発をうまく行うためのコーディング規約論
チーム開発をうまく行うためのコーディング規約論チーム開発をうまく行うためのコーディング規約論
チーム開発をうまく行うためのコーディング規約論
 
JSON SchemaとPHP
JSON SchemaとPHPJSON SchemaとPHP
JSON SchemaとPHP
 
Klee and angr
Klee and angrKlee and angr
Klee and angr
 
Become A Security Master
Become A Security MasterBecome A Security Master
Become A Security Master
 
Web時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメWeb時代の大富豪的プログラミングのススメ
Web時代の大富豪的プログラミングのススメ
 

Ähnlich wie PHP 也有 Day #35 - 精通 PHP 錯誤處理,讓除錯更自在

Php调试技术手册发布(1.0.0 pdf)
Php调试技术手册发布(1.0.0 pdf)Php调试技术手册发布(1.0.0 pdf)
Php调试技术手册发布(1.0.0 pdf)lookforlk
 
PHP & MySQL 教學
PHP & MySQL 教學PHP & MySQL 教學
PHP & MySQL 教學Bo-Yi Wu
 
11, exceptions
11, exceptions11, exceptions
11, exceptionsted-xu
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source CodeBo-Yi Wu
 
JavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsJavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsHo Kim
 
Erlang jiacheng
Erlang jiachengErlang jiacheng
Erlang jiachengAir-Smile
 
3. java basics
3. java basics3. java basics
3. java basicsnetdbncku
 
PHP Coding Standard and 50+ Programming Skills
PHP Coding Standard and 50+ Programming SkillsPHP Coding Standard and 50+ Programming Skills
PHP Coding Standard and 50+ Programming SkillsHo Kim
 
Erlang Practice
Erlang PracticeErlang Practice
Erlang Practicelitaocheng
 
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘Liu Allen
 
Expect中文版教程
Expect中文版教程Expect中文版教程
Expect中文版教程Da Zhao
 
模块一-Go语言特性.pdf
模块一-Go语言特性.pdf模块一-Go语言特性.pdf
模块一-Go语言特性.pdfczzz1
 
1 C入門教學
1  C入門教學1  C入門教學
1 C入門教學Sita Liu
 
第12回AS讀書會
第12回AS讀書會第12回AS讀書會
第12回AS讀書會Etrex Kuo
 
Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Kris Mok
 
Python 编程艺术
Python 编程艺术Python 编程艺术
Python 编程艺术wilhelmshen
 
OpenWebSchool - 02 - PHP Part I
OpenWebSchool - 02 - PHP Part IOpenWebSchool - 02 - PHP Part I
OpenWebSchool - 02 - PHP Part IHung-yu Lin
 
2009 CSBB LAB 新生訓練
2009 CSBB LAB 新生訓練2009 CSBB LAB 新生訓練
2009 CSBB LAB 新生訓練Abner Huang
 
Introduction to Parse JavaScript SDK
Introduction to Parse JavaScript SDKIntroduction to Parse JavaScript SDK
Introduction to Parse JavaScript SDK維佋 唐
 

Ähnlich wie PHP 也有 Day #35 - 精通 PHP 錯誤處理,讓除錯更自在 (20)

Php调试技术手册发布(1.0.0 pdf)
Php调试技术手册发布(1.0.0 pdf)Php调试技术手册发布(1.0.0 pdf)
Php调试技术手册发布(1.0.0 pdf)
 
PHP & MySQL 教學
PHP & MySQL 教學PHP & MySQL 教學
PHP & MySQL 教學
 
11, exceptions
11, exceptions11, exceptions
11, exceptions
 
SCJP ch14
SCJP ch14SCJP ch14
SCJP ch14
 
Maintainable PHP Source Code
Maintainable PHP Source CodeMaintainable PHP Source Code
Maintainable PHP Source Code
 
JavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization SkillsJavaScript 80+ Programming and Optimization Skills
JavaScript 80+ Programming and Optimization Skills
 
Erlang jiacheng
Erlang jiachengErlang jiacheng
Erlang jiacheng
 
3. java basics
3. java basics3. java basics
3. java basics
 
PHP Coding Standard and 50+ Programming Skills
PHP Coding Standard and 50+ Programming SkillsPHP Coding Standard and 50+ Programming Skills
PHP Coding Standard and 50+ Programming Skills
 
Erlang Practice
Erlang PracticeErlang Practice
Erlang Practice
 
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
课题一:PHP5.3、PHP5.4的特性介绍与深度挖掘
 
Expect中文版教程
Expect中文版教程Expect中文版教程
Expect中文版教程
 
模块一-Go语言特性.pdf
模块一-Go语言特性.pdf模块一-Go语言特性.pdf
模块一-Go语言特性.pdf
 
1 C入門教學
1  C入門教學1  C入門教學
1 C入門教學
 
第12回AS讀書會
第12回AS讀書會第12回AS讀書會
第12回AS讀書會
 
Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)Java Crash分析(2012-05-10)
Java Crash分析(2012-05-10)
 
Python 编程艺术
Python 编程艺术Python 编程艺术
Python 编程艺术
 
OpenWebSchool - 02 - PHP Part I
OpenWebSchool - 02 - PHP Part IOpenWebSchool - 02 - PHP Part I
OpenWebSchool - 02 - PHP Part I
 
2009 CSBB LAB 新生訓練
2009 CSBB LAB 新生訓練2009 CSBB LAB 新生訓練
2009 CSBB LAB 新生訓練
 
Introduction to Parse JavaScript SDK
Introduction to Parse JavaScript SDKIntroduction to Parse JavaScript SDK
Introduction to Parse JavaScript SDK
 

Mehr von Asika Simon

優秀的網站該具備什麼內容 - 超人教你做網站 #1
優秀的網站該具備什麼內容 - 超人教你做網站 #1優秀的網站該具備什麼內容 - 超人教你做網站 #1
優秀的網站該具備什麼內容 - 超人教你做網站 #1Asika Simon
 
Joomla CMS 效能調校
Joomla CMS 效能調校Joomla CMS 效能調校
Joomla CMS 效能調校Asika Simon
 
一週工作40小時? 談個人與團隊的時間管理
一週工作40小時? 談個人與團隊的時間管理一週工作40小時? 談個人與團隊的時間管理
一週工作40小時? 談個人與團隊的時間管理Asika Simon
 
關於I love joomla 0222
關於I love joomla 0222關於I love joomla 0222
關於I love joomla 0222Asika Simon
 
Joomla! 網站規劃 簡報-i love joomla! 5月小聚
Joomla! 網站規劃 簡報-i love joomla! 5月小聚Joomla! 網站規劃 簡報-i love joomla! 5月小聚
Joomla! 網站規劃 簡報-i love joomla! 5月小聚Asika Simon
 
I Love Joomla! 佈景製作教學 0212
I Love Joomla! 佈景製作教學 0212I Love Joomla! 佈景製作教學 0212
I Love Joomla! 佈景製作教學 0212Asika Simon
 
簡易電子商務網站 Joomla 實務工作坊 ii
簡易電子商務網站 Joomla 實務工作坊 ii簡易電子商務網站 Joomla 實務工作坊 ii
簡易電子商務網站 Joomla 實務工作坊 iiAsika Simon
 
簡易電子商務網站 Joomla 實務工作坊
簡易電子商務網站 Joomla 實務工作坊簡易電子商務網站 Joomla 實務工作坊
簡易電子商務網站 Joomla 實務工作坊Asika Simon
 
運用 Joomla! 整合社群媒體行銷力量
運用 Joomla! 整合社群媒體行銷力量運用 Joomla! 整合社群媒體行銷力量
運用 Joomla! 整合社群媒體行銷力量Asika Simon
 
Joomla! CMS簡介與商業應用 - online版
Joomla! CMS簡介與商業應用 - online版Joomla! CMS簡介與商業應用 - online版
Joomla! CMS簡介與商業應用 - online版Asika Simon
 
漢語形近字中的字詞識別優勢效應
漢語形近字中的字詞識別優勢效應漢語形近字中的字詞識別優勢效應
漢語形近字中的字詞識別優勢效應Asika Simon
 
《彩虹計畫》 Small2
《彩虹計畫》 Small2《彩虹計畫》 Small2
《彩虹計畫》 Small2Asika Simon
 
《彩虹計畫》 Small
《彩虹計畫》 Small《彩虹計畫》 Small
《彩虹計畫》 SmallAsika Simon
 

Mehr von Asika Simon (14)

優秀的網站該具備什麼內容 - 超人教你做網站 #1
優秀的網站該具備什麼內容 - 超人教你做網站 #1優秀的網站該具備什麼內容 - 超人教你做網站 #1
優秀的網站該具備什麼內容 - 超人教你做網站 #1
 
Joomla CMS 效能調校
Joomla CMS 效能調校Joomla CMS 效能調校
Joomla CMS 效能調校
 
談變化
談變化談變化
談變化
 
一週工作40小時? 談個人與團隊的時間管理
一週工作40小時? 談個人與團隊的時間管理一週工作40小時? 談個人與團隊的時間管理
一週工作40小時? 談個人與團隊的時間管理
 
關於I love joomla 0222
關於I love joomla 0222關於I love joomla 0222
關於I love joomla 0222
 
Joomla! 網站規劃 簡報-i love joomla! 5月小聚
Joomla! 網站規劃 簡報-i love joomla! 5月小聚Joomla! 網站規劃 簡報-i love joomla! 5月小聚
Joomla! 網站規劃 簡報-i love joomla! 5月小聚
 
I Love Joomla! 佈景製作教學 0212
I Love Joomla! 佈景製作教學 0212I Love Joomla! 佈景製作教學 0212
I Love Joomla! 佈景製作教學 0212
 
簡易電子商務網站 Joomla 實務工作坊 ii
簡易電子商務網站 Joomla 實務工作坊 ii簡易電子商務網站 Joomla 實務工作坊 ii
簡易電子商務網站 Joomla 實務工作坊 ii
 
簡易電子商務網站 Joomla 實務工作坊
簡易電子商務網站 Joomla 實務工作坊簡易電子商務網站 Joomla 實務工作坊
簡易電子商務網站 Joomla 實務工作坊
 
運用 Joomla! 整合社群媒體行銷力量
運用 Joomla! 整合社群媒體行銷力量運用 Joomla! 整合社群媒體行銷力量
運用 Joomla! 整合社群媒體行銷力量
 
Joomla! CMS簡介與商業應用 - online版
Joomla! CMS簡介與商業應用 - online版Joomla! CMS簡介與商業應用 - online版
Joomla! CMS簡介與商業應用 - online版
 
漢語形近字中的字詞識別優勢效應
漢語形近字中的字詞識別優勢效應漢語形近字中的字詞識別優勢效應
漢語形近字中的字詞識別優勢效應
 
《彩虹計畫》 Small2
《彩虹計畫》 Small2《彩虹計畫》 Small2
《彩虹計畫》 Small2
 
《彩虹計畫》 Small
《彩虹計畫》 Small《彩虹計畫》 Small
《彩虹計畫》 Small
 

PHP 也有 Day #35 - 精通 PHP 錯誤處理,讓除錯更自在

  • 1. 精通 PHP 錯誤處理 讓除錯更自在 PHP 也有 Day #35 2018.05.29 Simon Asika (飛鳥)
  • 2.
  • 4. PHP 的常見錯誤種類 • E_ERROR 執行期的 Fatal Error,無法進行錯誤修復,程式會直接停止。 • E_WARNING 警告,但不會停止程式。 • E_NOTICE 不屬於錯誤,但可能會發生錯誤,因此提示你。 • E_STRICT 更嚴格的 PHP 規範提示。 • E_DEPRECATED 被棄用的 function 等,提示你趕快換掉成新的用法。 • 其它  E_PARCE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, E_COMPILE_ERROR, E_RECOVERABLE_ERROR, E_ALL 等  See http://php.net/manual/en/errorfunc.constants.php
  • 5. E_ERROR • 產生以下錯誤 Fatal error: Uncaught Error: Call to undefined function bar() in D:wwwslimpublicindex.php:7 • 因為 function 根本不存在,系統無從猜測可能的行為,也無法修復錯誤,故為 Fatal Error,強制停止程式運作。 function foo() { echo 'foo'; } bar(); // 呼叫了不存在的 function
  • 6. E_WARNING • 產生以下錯誤畫面: Warning: A non-numeric value encountered in D:wwwslimpublicindex.php on line 3 123 • 不正確的資料操作與計算,可能造成程式 BUG,但是系統可以修復其行為,所以 程式不會中斷,可以被隱藏。 $result = 123 + 'ABC'; // 數字加字串 echo sprintf('<span style="color: red;">%s</span>', $result);
  • 7. E_NOTICE • 產生以下畫面 Notice: Undefined variable: b in D:wwwslimpublicindex.php on line 6 A • $b 沒有被預先宣告,這種寫法在 PHP 中是允許的,但是這樣子有很大機率出現 BUG ,故 PHP 會用 Notice 提示你最好預先宣告。 $a = 'A'; $ab = $a . $b; // $b 不存在 echo $ab;
  • 8. E_STRICT • 產生以下錯誤訊息 Warning: Declaration of B::foo() should be compatible with A::foo($a = 123) in D:wwwslimpublicindex.php on line 17 對PHP開發過程更嚴謹的要求。 class A { public function foo($a = 123) { } } class B extends A { // 與 parent class 介面不一樣 public function foo() { } }
  • 9. E_DEPRECATED • 產生以下畫面 Deprecated: Function mcrypt_create_iv() is deprecated in D:wwwslimpublicindex.php on line 6 �Ի��eD��IN� • 若使用了已被棄用的語言功能就會發出此提示,提醒你趕快改用新功能。 error_reporting(E_ALL); // PHP 7.2 deprecated mcrypt $v = mcrypt_create_iv(16); echo $v;
  • 10. 你也可以產生屬於自己的錯誤訊息 • 用 trigger_error() 來立即觸發使用者定義的錯誤訊息 • 預設值是 E_USER_NOTICE,所以程式會繼續執行下去,只是跳 Notice 訊息。 Notice: $a is not A in D:wwwslimpublicindex.php on line 6 B $a = 'B'; if ($a !== 'A') { trigger_error('$a is not A', E_USER_NOTICE); } echo $a;
  • 11. 改用 E_USER_ERROR • 這次用的是 ERROR type,程式就會終止執行了: Fatal error: $a is not A in D:wwwslimpublicindex.php on line 6 • 可用的種類有:  E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED 等  大多數 E_* 的錯誤訊息,都有 E_USER_* 的對應 if ($a !== 'A') { trigger_error('$a is not A', E_USER_ERROR); }
  • 12. 看不到 Notice 或 Deprecated 怎麼辦 • 可以在 php.ini 修改 error_reporting 直接用 E_ALL  error_reporting = E_ALL • 或是直接 runtime 時用 function 設定: // 回報所有錯誤, php 5.4 以後 E_STRICT 包含在內 error_reporting(E_ALL); // 回報特定錯誤 error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE); // 回報所有錯誤,Notice 除外 error_reporting(E_ALL & ~E_NOTICE); // 設為 -1 是最大值,所有可能的錯誤全部顯示,無關版本 error_reporting(-1); // 關閉錯誤訊息,不顯示。 error_reporting(0);
  • 13. 用 @ 隱藏錯誤訊息 • 可放在一行的最開頭,或是 function call 前面。通常用在極度不確定外部輸入格式時。 • 將這一行的錯誤訊息隱藏,這個案例中原本會產生下面的訊息,但實際上被屏蔽了: Warning: substr() expects parameter 1 to be string, array given • 其原理是在這一行開始執行時,背景將 error_reporting 改成 0,然後下一行執行前再改 回來。 • 所以如果有特別寫錯誤處理器的話,依然抓的到此錯誤。 @$a = substr([], 0, 5); var_dump($a);
  • 14. 建議的設定 • PHP 的預設設定是 E_ALL & ~E_NOTICE ,意思是顯示所有錯誤,Notice 除外。 • 開發過程,建議強制設成 E_ALL 或 -1,開發者應該要清空所有可能的 Warning & Notice 不要讓任何可能的 BUG 有機會出現。 • 老舊系統運作時,考慮設成 0,不要讓使用者看見大量的 Notice 訊息。 • 網站正式運作時,也可以設定成 0。但最好確保重要的錯誤有被 log 記錄下來。 • 從每個字敲出來就用最高標準對待自己的程式碼,未來的錯誤才會少。
  • 18. 使用 set_error_handler() 捕獲錯誤 • 會印出: 錯誤[2]: substr() expects parameter 1 to be string, array given - 位置: D:wwwslimpublicindex.php (8) • 變數說明  $code 錯誤碼,例如 E_WARNING 就是 2  $msg 錯誤訊息  $file/$line 錯誤發生的檔案與行  $context 錯誤當下的環境相關資訊 (php7.2 deprecated) set_error_handler(function ($code, $msg, $file, $line, $context) { echo sprintf('錯誤[%d]: %s - 位置: %s (%s)', $code, $msg, $file, $line); die; }); $a = substr([], 0, 5);
  • 19. • 加上一點改變,現在我們可以根據錯誤碼加上不同的提示 (這裡只抓前四個示範) • 本範例會印出 警告: substr() expects parameter 1 to be string, array given - 位置: D:wwwslimpublicindex.php (23) set_error_handler(function ($code, $msg, $file, $line) { $errormaps = [ E_ERROR => '錯誤', // 1 E_WARNING => '警告', // 2 E_PARSE => '語法錯誤', // 4 E_NOTICE => '提醒', // 8 ]; echo sprintf( '%s: %s - 位置: %s (%s)', $errormaps[$code] ?? '錯誤', // 用 code 取出錯誤說明 $msg, $file, $line ); die; }); $a = substr([], 0, 5);
  • 20. • 但是你會發現,現在加上 @ 或關閉錯誤訊息都沒用。為內建的錯誤處理已經被我 們強制覆蓋了。 • 照樣印出錯誤訊息 警告: substr() expects parameter 1 to be string, array given - 位置: D:wwwslimpublicindex.php (23) error_reporting(0); // 這個沒用了 set_error_handler(function ($code, $msg, $file, $line) { $errormaps = [ // ... 略 ]; echo sprintf( '%s: %s - 位置: %s (%s)', $errormaps[$code] ?? '錯誤', // 用 code 取出錯誤說明 $msg, $file, $line ); die; }); @$a = substr([], 0, 5); // 加上 @ 也沒用了
  • 21. • 之前有說過,@ 的作用就是當下即時把 error_reporting 改成 0,所以我們加上 error_reporting 的判斷,如果是 0,就直接略過。 • 現在不會再出現錯誤訊息了。 set_error_handler(function ($code, $msg, $file, $line) { if (error_reporting() === 0) { return; } $errormaps = [ // ... 略 ]; echo sprintf( '%s: %s - 位置: %s (%s)', $errormaps[$code] ?? '錯誤', $msg, $file, $line ); die; }); @$a = substr([], 0, 5);
  • 22. • 如果你希望錯誤的顯示與否與之前設定 error_reporting 的內容相同,則可以用位 元運算子的 & 符號來做比對 • 詳情請見  http://bit.ly/2LyEcbC  https://stackoverflow.com/questions/4705838/when-should-i-use-a-bitwise-operator  http://php.net/manual/en/language.operators.bitwise.php set_error_handler(function ($code, $msg, $file, $line) { if ((error_reporting() & $code) === 0) { return; } // ... }); $a = substr([], 0, 5);
  • 23. 搭配 log 紀錄錯誤訊息 • 隱蔽錯誤是有風險的,我們可以嘗試把錯誤都記錄在 log 內,至少發生不明錯誤時 還有紀錄可以查詢 • error_log() 的參數說明請見 http://php.net/manual/en/function.error-log.php set_error_handler(function ($code, $msg, $file, $line) { // ... error_log($msg . PHP_EOL, 3, __DIR__ . '/logs/error.log'); // ... });
  • 24. • 左邊是完整的範例 • 先組好messsage,然後立即 log • 接著判斷 error_reporting 有必要才 echo 錯誤訊息 • error_log() 不會幫你換行,記得加上 換行符號。 set_error_handler(function ($code, $msg, $file, $line) { $errormaps = [ E_ERROR => '錯誤', // 1 E_WARNING => '警告', // 2 E_PARSE => '語法錯誤', // 4 E_NOTICE => '提醒', // 8 ]; $msg = sprintf( '%s: %s - 位置: %s (%s)', $errormaps[$code] ?? '錯誤', $msg, $file, $line ); error_log($msg . PHP_EOL, 3, __DIR__ . '/logs/error.log'); if ((error_reporting() & $code) === 0) { return; } echo $msg; die; });
  • 25. log 紀錄結果 • 這只是一個簡單的範例,實際的網站開發請另外使用 Monolog 之類的套件來處理 log檔,並記得做 rotating 免得log塞爆。 • 有 DevOps 人員或採用 microservice 的團隊,可以考慮把 log 服務拉出去成為一台 獨立伺服器,所有訊息都往遠端打出去,就不用擔心 log 爆量問題。 • 主流框架大多都幫你處理好這些工作了,感謝上天,感謝 Opensource。
  • 26. 別忘了做個美美的錯誤畫面 • 送出 500 HTTP 錯誤碼,然後 render 錯誤畫面,畫上可愛的插圖,大功告成。 set_error_handler(function ($code, $msg, $file, $line) { // ...略 if ((error_reporting() & $code) === 0) { return; } http_response_code(500); echo view('error.default', compact(['msg', 'code', 'file', 'line'])); die; });
  • 27. 可以把錯誤當作 Exception 丟出喔 • Warning 可以當作 Exception 一樣 catch 到,很神奇吧。這樣就可以把所有錯誤一致 性的交給 exception handler 處理了。 • 注意: Fatal Error 不能 catch set_error_handler(function ($code, $msg, $file, $line) { // ...略 if ((error_reporting() & $code) === 0) { return; } throw new ErrorException($msg, 500, $code, $file, $line); }); try { $a = substr([], 0, 5); } catch (ErrorException $e) { echo $e; }
  • 29. 如何使用 Exception • 任何開發者自己認為是錯誤的地方,都可以丟出 Exception 中斷程式流程。 • 丟出 Exception 後,下方的程式就不會再執行,但此時整個程序沒有終止 (不像 Fatal Error 會直接終 止) • 我們可以 catch 丟出去的 Exception,然後轉而執行其它程式或流程。 $a = 'B'; if ($a !== 'A') { throw new RuntimeException('$a is not A', 500); // 從這裡中斷執行 } echo $a; // 這裡不會再執行了...
  • 30. 捕獲 Exception • 用 try ... catch 包住 Exception,就能自訂中斷流程,做額外的錯誤處理 • 在 try 區塊裡面,只要丟出 Exception 的話,後方程式就不會再執行,但會跳到 catch 的 區塊,所以可以另外執行除錯工作。 • 如果在 catch 內沒有 die 掉程式的話,try ... catch 後面的程式可以繼續執行,不會終止 程序。 $a = 'B'; try { if ($a !== 'A') { throw new RuntimeException('$a is not A'); // 直接跳出 } echo $a; // 這裡不會執行 } catch (Exception $e) { error_log($e->getMessage(), 3, 'logs/error.log'); // '$a is not A’ } echo 123; // 這裡又可以繼續執行了
  • 31. 更複雜的案例 • Exception 不一定是自己丟出的,也可能是 核心獲第三方函市庫丟出來的,範例中丟 出的 PDOExcception 通常是 SQL 有誤時會丟 出。 • Exception 可以分多個種類,用不同的 catch 來補獲。 • 越下面的 catch 包含範圍越廣大。 • 最後可以用一個 finally 來執行出現錯誤後一 定要做的任何處理。 $pdo = new PDO('mysql:...'); try { $pdo->prepare($sql)->execute(); } catch (PDOException $e) { // 處理 PDO 本身的錯誤 } catch (Exception $e) { // 處理其它可能的錯誤 } catch (Throwable $e) { // 處理 php7 error } finally { unset($pdo); // 終止連線 } echo '這裡會繼續執行';
  • 32. 善用 Code 判斷錯誤種類 • 就算是相同的 Exception 類型,也可以用 code 來區隔其錯誤狀態。 try { if (!$user->isLogin()) { throw new RuntimeException('Access denied.', 401); } // ...略 } catch (RuntimeException $e) { if ($e->getCode() === 401) { // 未登入 } elseif ($e->getCode() === 404) { // 找不到頁面 } else { // 其它錯誤 } }
  • 33. 自定義 Exception • 你可以繼承 Exception 或 RuntimeException 建立自己的 Exception • 拋出之後,用 catch (IDontFeelSoGood $e) 就可以針對這個 Exception 做自訂 義錯誤處理。 class IDontFeelSoGoodException extends Exception { } if (count($infinityGems) === 6) { throw new IDontFeelSoGoodException('Tony I'm sorry', 404); }
  • 34. Catch 到 Exception 之後怎麼辦 • 範例中根據捕獲的 Exception 種類有不 同的操作。 • 有的做 redirect,有的直接 404 或 403。 • 善用自訂義 Exceptions 搭配多層 catch 可以做到很靈活的錯誤處理。 try { User::save($userData); } catch (UserNotLoginException $e) { header('Location: /login'); } catch (UserNotFoundException $e) { http_response_code(404); die('Sorry, this user not found.'); } catch (UnauthorisedExceotion $e) { http_response_code(403); die('Forbidden'); }
  • 35. Exception 可以無視層次跳躍 • 範例中 Exception 是在 function 內拋出,但是外部的 try ... catch 可以抓到。 • Exception 是無視層次的,會向上一直跳到有 try ... catch 的地方才停止。 • 如果沒有 try ... catch,則會跳到最上層,成為 Fatal Error 終止程序。 function foo($a) { if ($a !== 'A') { throw new RuntimeException('$a is not A'); } echo $a; } try { foo('B'); } catch (RuntimeException $e) { echo $e->getMessage(); }
  • 36. 沒有捕獲 Exception 的結果 • 會顯示 Uncaught Exception 然後終止程序 Fatal error: Uncaught RuntimeException: $a is not A in D:wwwslimpublicindex.php:5 Stack trace: #0 D:wwwslimpublicindex.php(11): foo('B') #1 {main} thrown in D:wwwslimpublicindex.php on line 5 function foo($a) { if ($a !== 'A') { throw new RuntimeException('$a is not A'); } echo $a; } foo('B');
  • 37. 但我們一樣可以捕獲最上層 Exception • 還記得前面的 set_error_handler() 嗎? • 我們也可以用 set_exception_handler(); 來抓取拋到最外層的 Exception。 set_exception_handler(function (Throwable $e) { http_response_code($e->getCode()); echo <<<HTML <h1>{$e->getMessage()}</h1> <strong>Code:</strong> {$e->getCode()} <br/> <strong>File:</strong> <code>{$e->getFile()} ({$e->getLine()})</code> <h3>Call Stack</h3> <pre>{$e->getTraceAsString()}</pre> HTML; die; }); throw new RuntimeException('Oops, something went wrong.', 403);
  • 40. 把雙劍客合起來用 • 前面提到 error handler 可以把錯 誤當做 Exception 丟出去。 • 所以搭配 exception handler 就可 以集中處理所有可能的錯誤訊息。 • 這裡開始就複雜多了,沒關係, 框架們都幫你搞定了。 • 還有很多靈活用法,請參考:  http://www.w3school.com.cn/php/p hp_exception.asp  https://code.tutsplus.com/tutorials/p hp-exceptions--net-22274 // 把所有 Error 也當作 Exception 丟出去 set_error_handler(function ($code, $msg, $file, $line) { // ... throw new ErrorException($msg, 500, $code, $file, $line); }); // 所有的 Error, Warning, Notice & Exceptions 通通集中在這邊處理 set_exception_handler(function (Throwable $e) { http_response_code($e->getCode()); echo <<<HTML <h1>{$e->getMessage()}</h1> <strong>Code:</strong> {$e->getCode()} <br/> <strong>File:</strong> <code>{$e->getFile()} ({$e- >getLine()})</code> <h3>Call Stack</h3> <pre>{$e->getTraceAsString()}</pre> HTML; die; });
  • 44. 異常狀況的處理 • 在一個大量迴圈的任務中,我們希望即便少數的 job 失敗了,還是要繼續跑完後面 的 jobs • 且我們希望每一個 Job 失敗時,會 mail 通知管理員 • 因此在這邊,Exception 做為異常狀況處理器,會控制流程去寄送通知信,但又不 中斷程序,使得迴圈繼續跑下去。 foreach ($jobs as $job) { try { Queue::process($job); } catch (QueueException $e) { Mailer::send('A queue job error', $message); } }
  • 45. 但Exception也不是流程控制 可以用 if else 解決的問題,就不要用 Exception
  • 46. 不要這樣用 • 如果你只是想讓未登入 user 轉過去登入頁面,在這個案例中,用 if 就能處理了 try { if ($user->group === 'guest') { throw new UnauthorisedException('Please login', 401); } elseif (...) { } elseif (...) { } else { } } catch (UnauthorisedException $e) { header('Location: /login'); } if ($user->group === 'guest') { header('Location: /login'); }
  • 47. 但可以這樣用 • 這個案例中,user group 如果出現預定義的格式以外的值,肯定屬於異常狀況,就 直接拋出 Exception 吧。 switch ($user->group) { case 'guest': // Please login first. case 'member': // Welcome Back case 'manager': // Sir, yes sir. default: throw new UnauthorisedException('Uh... Who are you?', 403); }
  • 48. 或是這樣用 • 將多個 function 呼叫的 Exception 做集中處理,可以讓異常處理流程更乾淨易讀。 try { $obj->methodA(); $obj->methodB(); $obj->methodC(); } catch (BadRouteException $e) { } catch (PDOException $e) { } catch (RuntimeException $e) { } catch (Throwable $e) { }
  • 49. Exception 一定要處理 不要隱蔽錯誤 try { foo(); } catch (RuntimeException $e) { // No action }
  • 50. 使用 Exception 的情境 • 通常比較少在同一個空間內同時 try ... catch 又同時 throw Exception。 • 開發 function 的人可以根據異常狀況拋出各種 Exception,幫助使用 function 的人 處理例外流程。 • 使用 function 的人可以用 try ... catch 捕獲 function 的異常,然後處理可能的錯誤 修復。 • 是否是【異常】非常重要,既定的可預期流程,都應該用 if else 處理。但是異常 的處理可以放心用 Exception 作流程跳轉。
  • 51. 防禦型程式設計 public function __construct($string, $int, $array) { // 最基本的檢查,型別不對就丟錯 if (!is_string($string)) { throw new InvalidArgumentException('Argument 1 should be string.'); } // 這個檢查比較鬆一點,只要是數字都可以過,不一定要 int 型態 if (!is_numeric($int)) { throw new InvalidArgumentException('Argument 2 should be a number.'); } // 這個檢查比較特別,如果是 Iterator 物件也能夠接受,因為同樣可以 foreach if (!is_array($array) && !($array instanceof Traversable)) { throw new InvalidArgumentException('Argument 3 should be Traversable.'); } // Do some stuff }