SlideShare a Scribd company logo
1 of 52
Download to read offline
精通 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

More Related Content

What's hot

Ch12 Spring 起步走
Ch12 Spring 起步走Ch12 Spring 起步走
Ch12 Spring 起步走Justin Lin
 
Nginx Unitを試してみた話
Nginx Unitを試してみた話Nginx Unitを試してみた話
Nginx Unitを試してみた話Takehiro Torigaki
 
削除フラグのはなし
削除フラグのはなし削除フラグのはなし
削除フラグのはなしShigetaka Yachi
 
Nodejs Explained with Examples
Nodejs Explained with ExamplesNodejs Explained with Examples
Nodejs Explained with ExamplesGabriele Lana
 
Introduction to Node JS.pdf
Introduction to Node JS.pdfIntroduction to Node JS.pdf
Introduction to Node JS.pdfBareen Shaikh
 
漫談重構
漫談重構漫談重構
漫談重構teddysoft
 
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologies
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologiesOWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologies
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologiesOWASP
 
Hibernate presentation
Hibernate presentationHibernate presentation
Hibernate presentationManav Prasad
 
Subject and Behavior Subject in Angular
Subject and Behavior Subject in AngularSubject and Behavior Subject in Angular
Subject and Behavior Subject in AngularKnoldus Inc.
 
A Forgotten HTTP Invisibility Cloak
A Forgotten HTTP Invisibility CloakA Forgotten HTTP Invisibility Cloak
A Forgotten HTTP Invisibility CloakSoroush Dalili
 
PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門kwatch
 
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制Tomohisa Ishikawa, CISSP, CSSLP, CISA, CISM, CFE
 
ColdFusion for Penetration Testers
ColdFusion for Penetration TestersColdFusion for Penetration Testers
ColdFusion for Penetration TestersChris Gates
 
Deploying PHP applications with Phing
Deploying PHP applications with PhingDeploying PHP applications with Phing
Deploying PHP applications with PhingMichiel Rook
 
さくらのVPSに来る悪い人を観察する その2
さくらのVPSに来る悪い人を観察する その2さくらのVPSに来る悪い人を観察する その2
さくらのVPSに来る悪い人を観察する その2ozuma5119
 
CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式Justin Lin
 

What's hot (20)

Ch12 Spring 起步走
Ch12 Spring 起步走Ch12 Spring 起步走
Ch12 Spring 起步走
 
Nginx Unitを試してみた話
Nginx Unitを試してみた話Nginx Unitを試してみた話
Nginx Unitを試してみた話
 
削除フラグのはなし
削除フラグのはなし削除フラグのはなし
削除フラグのはなし
 
Nodejs Explained with Examples
Nodejs Explained with ExamplesNodejs Explained with Examples
Nodejs Explained with Examples
 
Introduction to Node JS.pdf
Introduction to Node JS.pdfIntroduction to Node JS.pdf
Introduction to Node JS.pdf
 
OpenStack Glance
OpenStack GlanceOpenStack Glance
OpenStack Glance
 
漫談重構
漫談重構漫談重構
漫談重構
 
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologies
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologiesOWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologies
OWASP Poland Day 2018 - Frans Rosen - Attacking modern web technologies
 
Spring Data JPA
Spring Data JPASpring Data JPA
Spring Data JPA
 
Hibernate presentation
Hibernate presentationHibernate presentation
Hibernate presentation
 
Subject and Behavior Subject in Angular
Subject and Behavior Subject in AngularSubject and Behavior Subject in Angular
Subject and Behavior Subject in Angular
 
A Forgotten HTTP Invisibility Cloak
A Forgotten HTTP Invisibility CloakA Forgotten HTTP Invisibility Cloak
A Forgotten HTTP Invisibility Cloak
 
Spring User Guide
Spring User GuideSpring User Guide
Spring User Guide
 
PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門
 
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制
ニューヨーク州金融サービス局 金融サービス企業に対するサイバーセキュリティ規制
 
Angular.pdf
Angular.pdfAngular.pdf
Angular.pdf
 
ColdFusion for Penetration Testers
ColdFusion for Penetration TestersColdFusion for Penetration Testers
ColdFusion for Penetration Testers
 
Deploying PHP applications with Phing
Deploying PHP applications with PhingDeploying PHP applications with Phing
Deploying PHP applications with Phing
 
さくらのVPSに来る悪い人を観察する その2
さくらのVPSに来る悪い人を観察する その2さくらのVPSに来る悪い人を観察する その2
さくらのVPSに来る悪い人を観察する その2
 
CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式CH1. 簡介 Web 應用程式
CH1. 簡介 Web 應用程式
 

Similar to 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
 
例外處理實務
例外處理實務例外處理實務
例外處理實務Jeff Chu
 
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
 

Similar to 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 新生訓練
 

More from 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
 

More from 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 }