在開發 PHP 的時候,最麻煩的事情之一就是處理錯誤。一個好的程式除了要將錯誤訊息呈現給使用者知道之外,也要讓該結束的部份正常結束才行。
而在 PHP5 之後,除了以往的 Error Handling 之外,還多了 Exception Handling ,使得程式變得更難去處理錯誤;所以大多數的開發者只能雙手一攤,讓這些錯誤訊息大剌剌地出現在使用者面前。
有沒有什麼好方法可以讓我們好好控制 Error 和 Exception 呢?
傳統的做法
在很多書籍和網路範例裡,當程式出錯時就是讓程序直接死掉,最常見的就是資料庫連線:
$link = mysql_connect('localhost', 'mysql_user', 'mysql_password');
if (!$link) {
die('Could not connect: ' . mysql_error());
}
echo 'Connected successfully';
mysql_close($link);
或是在送出導向 header 後,就直接 exit :
header("Location: http://www.example.com/"); /* Redirect browser */
/* Make sure that code below does not get executed when we redirect. */
exit;
這些都不是好做法,因為有些流量較大的網站裡可能有多個資料庫連線,或是檔案的 handler 仍在開啟中;如果直接讓程序死亡或離開的話,就沒辦法將這些已經開啟的資源給正常關閉掉,進而造成系統的不穩定。
Exception 的處理
PHP5 中,有個 set_exception_handler 這個函式,它可以幫我們處理 Exception :
function exception_handler($exception)
{
echo "Uncaught exception: " , $exception->getMessage(), "\n";
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
echo "Not Executed\n";
不過我個人認為用 try…catch 會讓我們在程式流程上的彈性更大:
function inverse($x)
{
if (!$x) {
throw new Exception('Division by zero.');
}
else return 1/$x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
// Continue execution
echo 'Hello World';
而在我研究過 Zend Framework 的做法後,發現它在處理 Exception 上更加聰明。 Zend Framework 在 Controller 中引入一個 Response 物件,所有對瀏覽器的輸出都要經過它 (例如 Header 、 Content Body 等等) ;而這個 Reponse 物件也同時控管著 Exception 是否要被輸出到瀏覽器端,讓程式開發者能有更大的空間處理 Exception 。
以下我簡單把 Zend Framework 在 Response 物件中處理 Exception 的概念整理成一個自製的 Response 類別:
class Response
{
private $_exceptions = array();
private $_renderExceptions = false;
public function setException(Exception $e)
{
$this->_exceptions[] = $e;
}
public function getExceptions()
{
return $this->_exceptions;
}
public function isException()
{
return !empty($this->_exceptions);
}
public function renderExceptions($flag = null)
{
if (null !== $flag) {
$this->_renderExceptions = $flag ? true : false;
}
return $this->_renderExceptions;
}
public function sendResponse()
{
echo "Header sending...\n";
$exception = '';
if ($this->isException() && $this->renderExceptions()) {
foreach ($this->getExceptions() as $e) {
$exception .= $e->getMessage() . "\n";
}
echo $exception;
}
echo "Body sending...\n";
}
}
主要的概念很簡單,就是 Response 物件先把 Exception 先收集起來,然後再視狀況如何處理,例如:
$response = new Response();
$response->renderExceptions(true); // 讓 Exception 呈現出來
try {
// 這裡處理我們真正要執行的動作
throw new Exception('TEST'); // 丟出一個測試用的例外
} catch (Exception $e) {
$response->setException($e); // 收集例外
}
if ($response->isException()) {
// 可以在這裡記錄 Exception
}
$response->sendResponse(); // 顯示所有結果 (包含 Header, Exception, Body 等)
透過了 Response 物件來管理 Exception ,就可以不必因為 Exception 而中斷我們的程式碼。
PHP Error 的處理
雖然 Exception 可以用 try…catch 控制程式流程,但 PHP Error 卻不行。
因為一般處理 PHP Error 的方法是透過 set_error_handler,而當執行完自訂的 Error Handler 後,我們卻只能選擇繼續執行下一行程式碼或將程式中斷離開,不然就是要利用全域變數來設定錯誤旗標以達到控制的目的。
$error = false;
function exceptionErrorHandler($errno, $errstr, $errfile, $errline)
{
global $error;
$error = true;
echo $errstr, "\n";
return true;
}
set_error_handler("exceptionErrorHandler");
strpos();
if (!$error) {
echo "Do normal process here.\n";
}
echo "end.\n";
不過 PHP5 也幫我們想好了,我們可以在 Error Handler 裡丟出 ErrorException ,就可以配合前面提到的 Response 物件做到更平順的 Exception 處理:
function exceptionErrorHandler($errno, $errstr, $errfile, $errline )
{
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler("exceptionErrorHandler");
$response = new Response();
$response->renderExceptions(true); // 讓 Exception 呈現出來
try {
// 這裡處理我們真正要執行的動作
trigger_error('TEST', E_USER_ERROR); // 改用 trigger_error 來丟出測試用錯誤
} catch (Exception $e) {
$response->setException($e); // 收集例外
}
if ($response->isException()) {
// 可以在這裡記錄 Exception
}
$response->sendResponse(); // 顯示所有結果 (包含 Header, Exception, Body 等)
這邊最棒的是 Error Handler 丟出 ErrorException 後, try…catch 就會發生作用,而不再像 set_error_handler 這樣又返回中斷的地方繼續執行,一切就像行雲流水般那麼自然。
結論
一個運作良好的系統必須要對錯誤的發生有最大的掌控權,而不是放任它讓系統墜毀在五里霧之中。
雖然前面提到的處理方式也許不是最佳的,但希望透過這樣的介紹,讓大家能夠思考自己的程式應該如何去處理錯誤這件事。
就寫到這裡吧,收工~
reference : http://www.jaceju.net/blog/archives/1121
2011年1月17日 星期一
Code commit policy ( using Git )
在上一篇,提到了我其實寫了不少 internal document (自己的一整套 Starter Guide, Code Convention, Commit Policy, Best Practice )。
我設計的 commit policy 其實是這樣的。
1. 所有站台 deploy 到 production 的 code 一率吃 master。
2. 大規模的改版會從 master 裡面 branch 出一個 staging 來持續開發。staging 開發完成沒問題之後才會 deploy。
3. 已經上線的 code 除了改 wording 之類的票,一率不准直接改 master。必須要另外弄一個 branch 出來做,這個 branch 強制吃票號,比如說 ticket 是 1234,則 branch 為 t1234。( 推上 remore)做完 peer review 沒問題之後才能 merge 回 master / staging,然後 deploy。
4. 稍微大一點的功能允許自訂 branch 名稱,比如說整個前台升 rails3,則可以開一個 rails3 branch。
5. 採取 meta commit 原則,可以是一個 method,一個 controller,一個 model 作一次 commit。確保 code review 能夠順利的進行。極度不鼓勵改 10 次檔案才 commit 一次,除非這是一張票,內容就是改這 10 個檔案的某個相同處。
6. 牽扯到 DB schema 變更的 commit / push 或太大規模的改寫,必須在公開頻道喊過務必確保同事知情。
7. * 絕對禁止 push 未完成(同事一拉下來結果爛光光)的 code,必須自己「手動」檢查過 code(如果有寫 test 就要 run test)才可 push。
* 絕對不能用假想的方式修完 code 就 commit push 甚至 deploy。
* 絕對禁止下班前一個小時 push + deploy。
8. (5) 與 (6) 累犯者(超過 3 次者) 踢出團隊,拒絕合作。
有幾點需要特別解釋為甚麼要這樣設計。
1. 曾經有試過 production 與 master 並行,然後從 master 裡 cherry pick 回去 production。後來發現的問題是懶 RD 修 bug 有時候會直接 commit 在 production,而且 cheery pick 回去本來就比較複雜,加上這種情況,事情就會變得很複雜。後來重新思考過,主幹跑 master 再加上 ticket branch 事情就會變得簡單很多。
2. staging 加上 ticket branch 也很好丟上 staging 環境測有沒有問題…
3. RD 寫 code 一率 peer review,除了可以早期發現 junior RD 寫 code 的問題,meta commit 也可以讓 RD 習慣 一次單純只處理一件事,處理太多事一心多用寫出來的 code 會很糟糕。
4. 全域的變更要通知大家「不是一種禮貌」,而是「不通知亂炸大家就叫不禮貌」
5. 嚴格禁止 commit 爛掉的 code 是因為一炸就是浪費 *所有人* 的時間。自己炸自己就算了,炸大家的人最不能夠原諒。
6. 接近下班時間寫出來的 code 品質最糟糕(肚子餓+想回家)。更麻煩的是閃了晚上如果真的爛了就找不到人。所以下班前一小時禁止 push / deploy。
7. 做錯事而且屢勸不聽的人不處理那就等於虐待沒做錯事的人。而且一天到晚炸 code 的人,其實就是一顆對 business 的不定時炸彈。沒事為甚麼有人想養炸彈在家裡…
reference : http://blog.xdite.net/?p=1974
我設計的 commit policy 其實是這樣的。
1. 所有站台 deploy 到 production 的 code 一率吃 master。
2. 大規模的改版會從 master 裡面 branch 出一個 staging 來持續開發。staging 開發完成沒問題之後才會 deploy。
3. 已經上線的 code 除了改 wording 之類的票,一率不准直接改 master。必須要另外弄一個 branch 出來做,這個 branch 強制吃票號,比如說 ticket 是 1234,則 branch 為 t1234。( 推上 remore)做完 peer review 沒問題之後才能 merge 回 master / staging,然後 deploy。
4. 稍微大一點的功能允許自訂 branch 名稱,比如說整個前台升 rails3,則可以開一個 rails3 branch。
5. 採取 meta commit 原則,可以是一個 method,一個 controller,一個 model 作一次 commit。確保 code review 能夠順利的進行。極度不鼓勵改 10 次檔案才 commit 一次,除非這是一張票,內容就是改這 10 個檔案的某個相同處。
6. 牽扯到 DB schema 變更的 commit / push 或太大規模的改寫,必須在公開頻道喊過務必確保同事知情。
7. * 絕對禁止 push 未完成(同事一拉下來結果爛光光)的 code,必須自己「手動」檢查過 code(如果有寫 test 就要 run test)才可 push。
* 絕對不能用假想的方式修完 code 就 commit push 甚至 deploy。
* 絕對禁止下班前一個小時 push + deploy。
8. (5) 與 (6) 累犯者(超過 3 次者) 踢出團隊,拒絕合作。
有幾點需要特別解釋為甚麼要這樣設計。
1. 曾經有試過 production 與 master 並行,然後從 master 裡 cherry pick 回去 production。後來發現的問題是懶 RD 修 bug 有時候會直接 commit 在 production,而且 cheery pick 回去本來就比較複雜,加上這種情況,事情就會變得很複雜。後來重新思考過,主幹跑 master 再加上 ticket branch 事情就會變得簡單很多。
2. staging 加上 ticket branch 也很好丟上 staging 環境測有沒有問題…
3. RD 寫 code 一率 peer review,除了可以早期發現 junior RD 寫 code 的問題,meta commit 也可以讓 RD 習慣 一次單純只處理一件事,處理太多事一心多用寫出來的 code 會很糟糕。
4. 全域的變更要通知大家「不是一種禮貌」,而是「不通知亂炸大家就叫不禮貌」
5. 嚴格禁止 commit 爛掉的 code 是因為一炸就是浪費 *所有人* 的時間。自己炸自己就算了,炸大家的人最不能夠原諒。
6. 接近下班時間寫出來的 code 品質最糟糕(肚子餓+想回家)。更麻煩的是閃了晚上如果真的爛了就找不到人。所以下班前一小時禁止 push / deploy。
7. 做錯事而且屢勸不聽的人不處理那就等於虐待沒做錯事的人。而且一天到晚炸 code 的人,其實就是一顆對 business 的不定時炸彈。沒事為甚麼有人想養炸彈在家裡…
reference : http://blog.xdite.net/?p=1974
2011年1月14日 星期五
Why is my time() off or behind by 8 hour in php?
solution :
date_default_timezone_set('Asia/Taipei');
date_default_timezone_set('Asia/Taipei');
inno n23 mobile手機品質很爛,再好用也沒用,不推薦買,要買請先三思
奉勸各位不要買,
為了滅少跟我一樣的受害者,
我把我的切身之痛寫給各位參考...
這一支手機是我生平第一次讓我如此氣憤的手機。
這支手機我買了NT4000,
手機買來後的第六天,營幕出現龜列,送回原廠維修,收了我NT800(幹)
南機場旁邊的那間遠傳門市人員說這一支他們賣了五十多支,還沒有收到過維修單,我想說就故且信你一次,
五十多支沒有任何維修單,品質應該就如同你當初跟我拍胸口保證的那樣,一極棒(幹,現在證實是你所說的都是謊言)
手機現在才用了1個月左右,充電的接孔出現接觸不良的情況,現在無法充電。
你們要買時,請勿必三思,不要相信門市人員的話,
我會把這一篇文章Po到各大部落格,以免還有人跟我一樣身受其害!
為了滅少跟我一樣的受害者,
我把我的切身之痛寫給各位參考...
這一支手機是我生平第一次讓我如此氣憤的手機。
這支手機我買了NT4000,
手機買來後的第六天,營幕出現龜列,送回原廠維修,收了我NT800(幹)
南機場旁邊的那間遠傳門市人員說這一支他們賣了五十多支,還沒有收到過維修單,我想說就故且信你一次,
五十多支沒有任何維修單,品質應該就如同你當初跟我拍胸口保證的那樣,一極棒(幹,現在證實是你所說的都是謊言)
手機現在才用了1個月左右,充電的接孔出現接觸不良的情況,現在無法充電。
你們要買時,請勿必三思,不要相信門市人員的話,
我會把這一篇文章Po到各大部落格,以免還有人跟我一樣身受其害!
How to get the path of the PHP BIN from PHP?
You can use:
constant(not recommend)
PHP_BINDIR gives the directory where the php executable is found.
method(recommend)
constant(not recommend)
PHP_BINDIR gives the directory where the php executable is found.
method(recommend)
public function getPHPExecutableFromPath(){
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
foreach ($paths as $path) {
$php_executable = $path . DIRECTORY_SEPARATOR . "php" . ("WIN" === strtoupper(substr(PHP_OS, 0, 3)) ? ".exe" : "");
if (file_exists($php_executable) && is_file($php_executable)) {
return $php_executable;
}
}
return false; // not found
}
2011年1月6日 星期四
How to get server UTC timezone offset
$this_tz_str = date_default_timezone_get();
var_dump($this_tz_str);
$this_tz = new DateTimeZone($this_tz_str);
$now = new DateTime("now", $this_tz);
$offset = $this_tz->getOffset($now);
echo $offset/3600;
2011年1月5日 星期三
How to set ZendDebugger PHPUnit in Zend studio 8.0
Predefined:
Apperv2.5.10
PHP5.2
Zend Studio 8.0
(按bug icon in menu右邊的三角形icon) -> 「Debug Configurations」

「PHP Script」->「New launch configuration(左上角的icon)」

Name:「PHPUnitMyDebugger」
Execution environment:「php5」
PHP File:「/path/to/PHPUnit/phpunit.php」

Click Apply!
In Tab PHP Script Arguments
Script Arguments:「${resource_loc}」

In Tab Common(As below)

Click Apply! You have complete first part setting~~
Next part is setting you php.ini
Add below config to bottom of php.ini
[Zend]
zend_extension="C:\Program Files (x86)\Zend\Zend Studio - 8.0.0\plugins\org.zend.php.debug.debugger.win32.x86_5.3.18.v20100905\resources\php53\ZendDebugger.dll"
zend_debugger.allow_hosts=127.0.0.1
zend_debugger.expose_remotely=always
zend_debugger.connector_port = 10013
save php.ini and restart Apache.
我設成功時,是有連同PHPUnit也一起設定好。所以如果您做了以上的設定後,還是無法成功跟ZendDebugger時,
你也設一下PHPUnit吧

在Program上「New launch configuration」
Name:「PHPUnit」
Location:「/path/to/phpunit/phpunit_ori.bat」
Working Directory:「${workspace_loc}」
Arguments:「${workspace_loc}」

In common tab

click Apply. OK~ That is all my setting process~
Apperv2.5.10
PHP5.2
Zend Studio 8.0
(按bug icon in menu右邊的三角形icon) -> 「Debug Configurations」

「PHP Script」->「New launch configuration(左上角的icon)」

Name:「PHPUnitMyDebugger」
Execution environment:「php5」
PHP File:「/path/to/PHPUnit/phpunit.php」

Click Apply!
In Tab PHP Script Arguments
Script Arguments:「${resource_loc}」

In Tab Common(As below)

Click Apply! You have complete first part setting~~
Next part is setting you php.ini
Add below config to bottom of php.ini
[Zend]
zend_extension="C:\Program Files (x86)\Zend\Zend Studio - 8.0.0\plugins\org.zend.php.debug.debugger.win32.x86_5.3.18.v20100905\resources\php53\ZendDebugger.dll"
zend_debugger.allow_hosts=127.0.0.1
zend_debugger.expose_remotely=always
zend_debugger.connector_port = 10013
save php.ini and restart Apache.
我設成功時,是有連同PHPUnit也一起設定好。所以如果您做了以上的設定後,還是無法成功跟ZendDebugger時,
你也設一下PHPUnit吧

在Program上「New launch configuration」
Name:「PHPUnit」
Location:「/path/to/phpunit/phpunit_ori.bat」
Working Directory:「${workspace_loc}」
Arguments:「${workspace_loc}」

In common tab

click Apply. OK~ That is all my setting process~
訂閱:
文章 (Atom)