每種程式語言都有屬於它自己的特性,良好的撰寫習慣可能程式具有可閱讀性,幫助理解方便維護。能夠充分熟悉它的人在撰寫時越容易避免預期外的錯誤,以減少不必要的除錯動作,增加工作效率,因此此篇文章主要用來整理在學習 PHP 過程中應培養的撰寫習慣及應注意的細節觀念…等等的心得。
基本的撰寫習慣
- 檔案開頭以
<?php
做為 PHP 的起始標籤,盡量避免使用縮寫<?
。 - 檔案若為單純的 PHP 程式碼,則應省略 PHP 的結尾標籤
?>
,僅於結尾處加上註解表示檔案結尾。?>
結尾標籤在 PHP 編譯器中是非必要的。- 可避免他人在結尾標籤之後加上不可見的字元(空白、換行、TAB等等),可能會破壞頁面輸出的字元。
- 本網誌的範例程式碼會使用
?>
做為結尾符號,以保持程式碼一致性。
123<?phpecho 'Hello World';/* End of File */
- 習慣為程式碼加上必要的註解。
- 養成加上註解的習慣,不但能幫助自己記憶,也能讓別人更容易看懂程式碼的用途。
- 手動對變數做初始化的動作。
- 在 PHP 中使用變數時,若該變數尚未被定義,PHP 編譯器會丟出 E_NOTICE 訊息,並自動替變數做初始化的動作。
- 雖然 E_NOTICE 訊息並不會影響程式執行,卻容易使程式碼變得不嚴謹且產生模糊地帶。
- 建議習慣手動對變數做初始化的動作,以增加程式嚴謹性。
123456789<?php$array = array();if (5 > 2) {$array[] = 5;} else {$array[] = 2}print_r($array);?>
- 純字串使用單引號 (Single Quote) 為主。
- 若字串僅為純文字,不需要經過轉換特殊字元的動作,建議使用單引號即可,避免 PHP 編譯器進行多餘的動作可增加效能。
- 使用單引號僅會對字串進行少數幾個特殊字元轉換的動作,如
'
。 - 雙引號包覆的字串內容則會經過完整的轉換,例如變數內容置換、特殊字元轉換等操作。
1234567<?php$name = 'Eden';echo 'Hello $name'; // $name 會被當作純文字,不做任何變數替換的動作echo 'Hello $namen'; // n 也是被當作純文字,不會被轉換成換行字元echo "Hello $name"; // $name 會被替換為 Edenecho "Hello $namen"; // n 會被轉換成換行字元?>
- 使用陣列時,其陣列索引應盡量以單引號或雙引號括起來。
- 若以嚴謹標準來看
$array[ID]
的寫法是錯誤的,其陣列索引應該要被引號括起來為$array['ID']
較為正確。 - PHP 編譯器會將沒有括起來的字串索引當作「祼字符」,先將它解釋成
Constant
後找不到其定義時,才再將它重新解釋成字串,因此$array[ID]
是在這項特性之下被正常執行的。- 這項特性所提供的彈性是用執行效能換來的 :
$array['ID']
的執行速度是$array[ID]
的七倍。 - 即使陣列索引本身是數值,其
$array[0]
與 `$array[‘0’] 都是指向同一個陣列元素。
- 這項特性所提供的彈性是用執行效能換來的 :
- 因此建議在非必要的情況下,使用陣列時務必將索引括起來。
12345678910<?php$prices = array('Milk' => 30, 'Tea' => 15, 'Coffee' = 35);echo $prices['Milk']; # 索引以單引號括住// 使用 Constant 存取陣列define('EDEN', 'Tea'); # 將 EDEN 定義為祼字符echo $prices[EDEN];// 使用 Variable 存取陣列$coffee = 'Coffee';echo $prices[$coffee];?>
- 若以嚴謹標準來看
- 使用外來變數時,先利用
isset()
或array_key_exists()
等相關機制來檢查變數是否存在。- 直接使用未被定義的變數時,PHP 會丟出 E_NOTICE 訊息。
- E_NOTICE 訊息不影響程式執行,且可透過設定 ERROR_REPORTING | DISPLAY_ERROR 的機制來忽略這類訊息。
- 但為避免非預期性的錯誤,建議在使用 $GET, $POST, $REQUEST 等外來變數,或無法確保變數是否已存在的情況時,記得先檢查該變數是否存在。
12345678910111213141516171819<?php/* 不夠嚴謹的寫法 */// 1. 當 $_POST 不存在或送出的表單沒有 password/account 欄位時,PHP 會丟出 ENOTICE (undefined index)if ($_POST['password'] == 'PASSWORD') {$admin = $POST['account'];}// 2. 當上述 if 不成立時,$admin 就不會被定義,因此 PHP 會丟出 E_NOTICE (undefined variable)echo $admin . PHP_EOL;/* 較嚴謹且正確的寫法:變數先給預設值,且使用外來變數前先檢查是否存在 */$admin = null;if (isset($POST['password']) && ($POST['password'] == 'PASSWORD')) {$admin = (isset($_POST['account'])) ? $_POST['account'] : $admin;}echo $admin . PHPEOL;// 除了利用 isset() 以外,也能透過 array_key_exists() 來檢查。if (array_key_exists('password', $_POST)) {// Do something here.}?>
- 直接使用未被定義的變數時,PHP 會丟出 E_NOTICE 訊息。
- 在使用
==
,!=
,===
,!==
比較運算子時,必須多加注意以避免預期外的錯誤。- 例如比較
Boolean
布林值時應盡量使用===
,!==
比較運算子以增加程式嚴謹度。 - 基於 PHP 自動轉換變數型態的特性,因此需注意比較運算子的差異。
==
,!=
比較的只有變數內容,當型態不同時會自動轉換成相同型態。===
,!==
除變數內容以外,還會比較變數的型態。- 更多詳細內容請參考官方手冊: PHP type comparison tables
- 例如比較
增加程式可讀性的撰寫習慣
- 找一個多數人認同的程式碼撰寫規範來遵守。
- 遵守一個多數人認同的程式碼規範,不僅可提高程式碼的可讀性,亦能讓團隊成員共同撰寫出具有相同規則的程式碼。
- PSR Coding Style for PHP (待補充)
優化效能的撰寫習慣
- 使用迴圈時,非必要應避免在迴圈條件式中直接使用函數。
- 若函式被寫在迴圈條件式中,在每次迴圈檢查條件式時都會執行該函式一次。
- 若函式回傳值在迴圈的過程中不會改變,則應避免將函式寫在迴圈條件式中。 (反覆執行函式卻取得同樣的結果,只是浪費系統資源及降低程式效能。)
12345678910111213141516<?php$array = range(0, 100000); # 產生陣列// 不適當的寫法,因為 count($array) 反覆執行 N 次卻都是取得同樣的結果for ($i = 0; $i < count($array); $i++) {echo $array($i);}// 較適當的寫法,count($array) 僅會執行一次$arrayCount = count($array);for ($i = 0; $i < $arrayCount; $i++) {echo $array($i);}// [例外狀況] 將陣列元素增加至 11000 個 : count($array) 的回傳值會因迴圈過程中的動作而被改變for ($i = 0; count($array) < 11000; $i++) {$array[] = $i;}?>
- 盡量避免使用正規表達式 (Regular Expression) 。
- 在進行字串操作時,非必要應盡量避免使用正規表達式。
- 例如
str_replace()
的執行效率會比preg_replace()
快,
- 例如
- 在必要時仍以使用正規表達式達到簡化工作,容易維護為主。
- 例如檢查 Email 格式若以其他方式來完成,除了得費一番功夫以外,也不易維護。
- 在進行字串操作時,非必要應盡量避免使用正規表達式。
- 使用完變數之後,必要時手動釋放變數資源。
- 當變數被用以儲存龐大資料時,建應於使用完畢後立即使用
unset
以釋放該變數所佔有的資源。
123456<?php$article = '假設這是一篇文字超長,佔記憶體至少 100MB 的文章...';// Do something...以下數百行(略)unset($article); // 釋放變數資源// Do something else 以下數百行(略)?>
- 當變數被用以儲存龐大資料時,建應於使用完畢後立即使用
其他程式相關的細節
- 陣列宣告時,最後一個元素之後可以有多餘的逗點。
- 雖然有彈性但不建議用,因為在程式撰寫時還是保有一定的嚴謹度,以減少不可預期的錯誤。
123456<?php// 多數程式語言的寫法,最後一個元素之後有不能多餘的逗點$array = array(1,2,3);// 由於 PHP 本身的彈性,最後一個元素之後可接受多餘的逗點$array = array(1,2,3,); // 不會出現錯誤。?>
- 雖然有彈性但不建議用,因為在程式撰寫時還是保有一定的嚴謹度,以減少不可預期的錯誤。
- 陣列
$array[0]
與$array['0']
是指向同一個陣列元素。- 由於 PHP 具有自動轉換變數型態的特性,透過變數操作陣列時就產生了一個疑問:
- 若
$index = '5'
時,$array[$index]
究竟是 $array[‘5’] 還是 $array[5]?
- 若
- 因此就撰寫了簡單的測試碼,得出以下結果:
1234567<?php$array = array(0 => 'Number : 0','0' => 'String : Zero');print_r($array); // 輸出結果只有 [0] => 'String : Zero'?>
- 由於 PHP 具有自動轉換變數型態的特性,透過變數操作陣列時就產生了一個疑問:
- 使用函式傳遞參數時,若傳遞的參數是物件時為 Call-Time Pass By Reference 。
- 陣列及一般變數為 Pass By Value 的方式,可在函式定義時加上
&
符號強制改以 Pass By Reference 傳入。- PHP 5.3 時對 Call-Time Pass By Reference 做了些改變,當有 Pass By Reference 的需求時,必須在定義函式時明確指定該引數的傳入方式,且不可在呼叫函式時使用
&
符號。
- PHP 5.3 時對 Call-Time Pass By Reference 做了些改變,當有 Pass By Reference 的需求時,必須在定義函式時明確指定該引數的傳入方式,且不可在呼叫函式時使用
- 請參考以下範例:
12345678910111213141516171819202122232425262728293031323334353637383940414243<?php// 定義一個簡單的類別Class MyObject{public $name = null;public function setName($name){$this->name = $name;}}// 定義測試用的一般函式function demoBasics($object, $array, $var){$object->setName('Basics');$array = array('Basics');$var = 'Basics';}// 定義測試用的函式 (引數前加上 & 表示強制以 Pass By Reference 方式傳入)function demoReference(&$object, &$array, &$var){$object->setName('Reference');$array = array('Reference');$var = 'Reference';}// 測試範例$myObject = new MyObject();$myObject->setName('Default');$myArray = array('Default');$myVar = 'Default';// 正常情況下為 Pass By Value 傳遞參數demoBasics($myObject, $myArray, $myVar);print_r($myObject); // 輸出 name => 'Basics',以 Pass by Reference 傳遞物件參數print_r($myArray); // 輸出 [0] => 'Default'print_r($myVar); // 輸出 'Default';// 強制以 Pass By Reference 傳遞參數demoReference($myObject, $myArray, $myVar);print_r($myObject); // 輸出 name => 'Reference'print_r($myArray); // 輸出 [0] => 'Reference'print_r($myVar); // 輸出 'Reference';// 註:下列這行程式碼在 PHP 5.3 以上版本時無法通過編譯// 必須在定義函式時就明確指定變數以 Pass By Reference 的方式傳入。demoReference(&$myObject, &$myArray, &$myVar);?>
- 陣列及一般變數為 Pass By Value 的方式,可在函式定義時加上
現在好像大家都推薦PSR-2的Coding style。版主也是嗎?
是,我也滿推薦PSR-2的