[TOC]
# 前言
如果您已經決定向加入`山西創泰`并貢獻代碼,請詳細閱讀以下規范,并嚴格遵守。本規范由編程原則組成,融合并提煉了開發人員長時間積累下來的成熟經驗,意在幫助開發者養成良好一致的編程風格。如果有需要,本文檔會不定期更新。
# PHP編碼規范與原則
文件編碼及編輯器格式
請在開始編輯山西創泰代碼之前調整你的編輯器設置。
本條規范同樣適用于 `PHP`、`HTML`、`CSS`、`JavaScript`
## 編碼
請調整您的編輯器文件編碼為 `UTF-8`,并關閉 `UTF-8 BOM((Byte Order Mark))`的功能。我們要求使用`PHPStome` 等集成化的`IDE`。最好使用`PHPSTROM`。
切記請不要使用windows自帶的記事本編輯項目文件。
注意:請確認你的編輯器不會有意或無意的保存文件為 `UTF-8 BOM` 格式, 否則可能造成山西創泰系統通信不正常。
## 縮進
每個縮進的單位約定是一個`Tab`(禁止設置為空格替代,Tab寬度應表示為4個空白字符寬度),需每個參與項目的開發人員在編輯器(`PHPSTROM`)中進行強制設定,以防在編寫代碼時遺忘而造成格式上的不規范。
## 換行
山西創泰項目中使用`Unix`風格的換行符,即只有換行( `LF` 或 `“\n”` )沒有回車( `CR` 或 `“\r”` ),請在你的編輯器內調整
## 代碼標記
PHP程序需使用 `<?php ?>` 來界定 `PHP` 代碼,在`HTML`頁面中嵌入純變量時,可以使用 `<?php echo $variablename;?>` 這樣的形式。
注意:為了使代碼規范化和標準化,山西創泰開發中禁止使用 `<? ?>` 和 `<?=$variablename?>` 這種速記形式。
## 注釋
注釋是對于那些容易忘記作用的代碼添加簡短的介紹性內容。請使用`“/* */”`和`“//”`。
盡量使用變量名,整體邏輯結構來表達程序中的意圖,對于特別難懂的邏輯或是算法可以添加注釋以便于其他人員更方便理解意圖,注釋應該是解釋 “為什么這么做” 而不是 “這是什么東西” 。
代碼中不得包含任何調試性的代碼(注釋后也不可以),如果有一些未完成或是待完善的功能必須添加統一的注釋標記“//debug”并后跟完整的注釋信息
例如:
```
/**
* 多行注釋請用這種格式
* @param string $memo 備注
* @return array
*/
$num = 1;
$flag = TRUE; //debug 這里不能確定是否需要對$flag進行賦值
if(empty($flag)) {
//Statements
}
```
# 書寫規則
## 大括號{}、if和switch
首括號與關鍵詞同行,尾括號與關鍵字同列;
`if` 結構中,`if`、`else` 和 `elseif` 與前后兩個大括號同行,左右各一個空格。另外,即便 `if` 后只有一行語句,仍然需要加入大括號,以保證結構清晰;
`switch` 結構中,通常當一個 `case` 塊處理后,將跳過之后的 `case` 塊處理,因此大多數情況下需要添加 `break`。`break` 的位置視程序邏輯,與 `case` 同在一行,或新起一行均可,但同一 `switch` 體中,`break` 的位置格式應當保持一致。
以下是符合上述規范的例子:
```
if ($condition) {
//Statements
} else {
switch ($str) {
case 'abc':
$result = 'abc';
break;
default:
$result = 'unknown';
break;
}
}
```
## 運算符、小括號、空格、關鍵詞和函數
每個運算符與兩邊參與運算的值或表達式中間要有一個空格;
左括號“(” 應和函數關鍵詞緊貼在一起,除此以外應當使用空格將“(”同前面內容分開;
右括號“)”除后面是“)”,其他一律用空格隔開它們;
除字符串中特意需要,一般情況下,在程序以及HTML中不出現兩個連續的空格;
任何情況下,PHP程序中不能出現空白的帶有TAB或空格的行,即:這類空白行應當不包含任何TAB或空格。同時,任何程序行尾也不能出現多余的TAB或空格;
每段較大的程序體,上、下應當加入空白行,兩個程序塊之間只使用1個空行,禁止使用多行。
程序塊劃分盡量合理,過大或者過小的分割都會影響他人對代碼的閱讀和理解。一般可以以較大函數定義、邏輯結構、功能結構來進行劃分。少于15行的程序塊,可不加上下空白行;
說明或顯示部分中,內容如含有中文、數字、英文單詞混雜,應當在數字或者英文單詞的前后加入空格。
根據上述原則,以下舉例說明正確的書寫格式:
```
$result = (($a + 1) * 3 / 2 + $num)) . 'Test';
$condition ? func1($var) : func2($var);
$condition ? $long_statement : $another_long_statement;
if ($flag) {
//Statements
//More than 15 lines
}
showmessage('請使用 restore.php 工具恢復數據。');
```
## 函數定義
參數的名字和變量的命名規范一致;
函數定義中的左小括號,與函數名緊挨,中間無需空格;
開始的左大括號與函數定義為同一行,中間加一個空格,不要另起一行;
具有默認值的參數應該位于參數列表的后面;
函數調用與定義的時候參數與參數之間加入一個空格;
必須仔細檢查并切實杜絕函數起始縮進位置與結束縮進位置不同的現象;
例如,符合標準的定義:
```
function message($string, $operation, $key = '') {
if ($flag) {
//Statement
}
//函數體
}
```
不符合標準的定義:
```
function authcode($string,$operation,$key = '') {
//函數體
}
```
## 引號
由于PHP中單引號和雙引號具有不同的含義,因此在使用時有如下原則:
在能使用單引號的情況下,禁止使用雙引號。
字符串為固定值,不包含換號、制表等特殊轉義時,需使用單引號。
字符串作為數據索引時,需使用單引號。
字符串不需要帶入變量,需使用單引號。
數據庫SQL語句中,所有數據必須加單引號,無論數值還是字串,以避免可能的注入漏洞和SQL錯誤。
```
$sql = "UPDATE " . tablename('members') . " SET adminid='1' WHERE AND adminid='2'";
```
## 數據庫操作
為保證數據操作安全,數據庫操作有以下處理及書寫原則:
所有數據庫查詢時,盡量使用封裝的 `pdo_getXXX` 系列函數,如果無法滿足再考慮書寫`SQL`語句使用 `pdo_fetchXXX` 系列函數
所有`SQL查詢`關鍵字大寫,方便代碼審查
所有`SQL對象`(表名,字段名,索引名等)必須用反引號包括
所有編碼參數查詢,必須使用PDO的參數綁定機制處理
不能綁定參數處理的查詢,必須處理好變量檢測及字符串轉義
這是一個完整的數據庫操作示例:
```
//pdo_getXXX 系列函數,推薦使用
$trades = pdo_getall('trades', array('username' => $_GPC['username'], 'tid' => $_GPC['select']));
//直接書寫SQL,需要自行處理參數轉義等問題
$tids = array();
if(!empty($_GPC['select'])) {
foreach($_GPC['select'] as $t) {
$tids[] = intval($t); //---- 必須將輸入參數轉換為無安全隱患的格式,數字列必須轉換為數字列,字符串列必須使用 addslashes
}
}
if(!empty($tids)) {
$sql = 'SELECT * FROM ' . tablename('trades') . ' WHERE `username`=:username AND `tid` IN (' . implode($tids) . ')';
$pars = array();
$pars[':username'] = $_GPC['username'];
$trades = pdo_fetchall($sql, $pars);
}
```
# 命名原則
命名是程序規劃的核心。古人相信只要知道一個人真正的名字就會獲得凌駕于那個人之上的不可思議的力量。只要你給事物想到正確的名字,就會給你以及后來的人帶來比代碼更強的力量。
名字就是事物在它所處的生態環境中一個長久而深遠的結果。總的來說,只有了解系統的程序員才能為系統取出最合適的名字。如果所有的命名都與其自然相適合,則關系清晰,含義可以推導得出,一般人的推想也能在意料之中。
就一般約定而言,類、函數和變量的名字應該總是能夠描述讓代碼閱讀者能夠容易的知道這些代碼的作用。形式越簡單、越有規則,就越容易讓人感知和理解。應該避免使用模棱兩可,晦澀不標準的命名。
## 變量、函數名
變量、函數名一律為`小寫格式`;
變量與函數命名時一切使用單數形式。如果需要表達“多”的概念,可以使用 $goods_list 等數量詞
以標準計算機英文為藍本,杜絕一切拼音、或拼音英文混雜的命名方式;
## 變量名命名方式
變量名要完全、準確地描述出該變量所代表的事物。這種名字很容易閱讀,沒有晦澀的縮寫,同時也沒有歧義。
一個好記的名字反映的通常都是問題,而不是解決方案。一個好的名字通常表達的是“什么”,而不是“如何”。
比如:文章推薦列表應該命名成 `$article_recommend_list` 而且 `$data_list`
## 單詞分隔
在函數中一定是使用下劃線 `_` 分隔多個詞
變量中比較短少的詞組合盡量使用分隔符,比如:`商品id`可以直接寫成 `$goods_id`,`$multi_id`,`$create_time`
變量中一些較長的,或是有兩個以上含義的用下劃線`_`分隔,比如:`$default_groupid`,`$today_recharge`,`$yestday_recharge`
## 循環中變量定義
循環內的局部變量也應該避免使用無意義的`$i`、`$j`等變量,比如循環商品 `$goods as $i => $goods_row`,如果鍵值不是存放著自增索引,也可以使用 `$goods as $goods_id => $goods_row`,比如循環文件 `$files as $i => $filename`。
## 常用縮寫列表
除下方列表外,一切縮寫皆不可使用
```
Argument -> arg
Command -> cmd
Configuration -> config
Identification -> id
Previous -> prev
Synchronize -> sync
Initialize -> init
Minimum -> min
Maximum -> max
```
## 類和接口名
類和接口的命名采用混合大小寫字母的Pascal命名法
接口的命名與類相似,但需要以大寫字母“I”開頭,以作區分。
對象成員的命名則采用混合大小寫字母的Camel命名法
```
class Loader {
function func($function_name) {
}
function fileCache() {
}
}
```
## JavaScript代碼
`JavaScript`中類和全局對象應使用混合大小寫字母的`Pascal`命名法
`JavaScript`中變量、函數名應采用混合大小寫字母的`Camel`命名法。
`JavaScript`中與 `PHP` 類中書寫規范一致
## 常量
常量是增強代碼語義重要的一個手段,大多數我們在保存類型或是其它數據時會采用`int`數字來表示,這些數字是沒有意義的,這時候我們可以用常量的方式來處理這些數據。
常量應該總是全部使用`大寫字母`命名,必要的情況下,可使用劃線來分隔單詞;
`PHP` 的內建值 `true`、`false` 和`null`必須全部采用小寫字母書寫。
# 封裝
增加語義上的理解,使結構更清晰。
增加復用,減少冗余,方便更改。
封裝功能并不是簡單的把代碼復制到函數里,要進行功能的抽象和拆解,比如:上傳微信圖文素材和本地圖文素材轉換成微信素材,這兩個功能中“將某些數據保存至微信”是他們兩個共有的功能,則會封裝 `material_local_news_upload()` ,封裝的時候,盡量的不太過多的參與到業務中,外部是負責把數據結構拼接好,函數內部只要處理自己的功能就好,而且要遵循“單一原則”規則。寫好函數后,自己思考一下,這個函數如果脫離這個業務是否還可以在其它地方使用?
## 封裝時機
獲取整體信息時
獲取目標對象的信息時,一定要考慮是否還關聯著其它信息。比如獲取公眾號時,公眾號除主體信息外,還會關聯著可用模塊、所有者、權限等數據,在獲取時應該一并獲取出來。
## 重復使用的代碼
如果相同的代碼在不同位置出現兩次及以上,就要考慮封裝。封裝時要把公共的功能抽象出來進行封裝,而不是單純的復制+粘帖。比如:切換公眾號和切換小程序,會將`uni_account_save_switch()`和 `uni_account_last_switch()`進行封裝
## 晦澀難理解的代碼
在寫代碼時提倡優雅的書寫代碼,實現功能。但是某些時候可能還是會有一些難以理解的代碼,就要考慮封裝,封裝時一定要注意函數的命名。
## 大段的功能操作
一個功能如果需要大量的代碼,比如添加公眾號,需要先處理主體信息,還要根據不同的類型處理不同的表,還要處理用戶權限,模塊等等,此時就要考慮封裝。
## 其它功能可能會用到
功能相互間不可能是獨立的,總是會在一些功能里用到額外的一些功能或是數據。我們在書寫功能時,要長遠的考慮一下,比如一些基本信息,基本的操作外部可能會調用,就要考慮封裝。比如,會員數據的獲取,更新等等
# 參數傳遞
無論是在功能函數中還是控制中,我們都需要接收外部傳來的某一些參數來實現功能,我們可以理解成這些參數是我們內部提供給外部的一些訪問接口。
外部提供過來的數據是不可靠也不可信的,外部過來的數據我們必須要進行驗證,傳遞的數據越多,要驗證的數據就越多,代碼也就越復雜。
通常我們要盡量減少外部傳遞的參數,以最小設計(最小傳遞)來做,能傳一個不傳兩個,更多的值通過內部來獲取
# 變量的初始化與邏輯檢查
任何變量在進行累加、直接顯示或存儲前必需進行初使化 ,例如:
```
$number = 0; // 數值型初始化
$string = ''; // 字符串初始化
$array = array(); // 數組初始化
```
判斷一個無法確定(不知道是否已被賦值)的變量時,可用 `empty()` 或 `isset()`,而不要直接使用`if($switch)`的形式。
`empty()`和`isset()`的區別為:請參閱`PHP手冊`。
如果已經使用 `unset()` 釋放了一個變量之后,它將不再是 `isset()`。若使用 `isset()` 測試一個被設置成 `NULL` 的變量,將返回 `FALSE`。同時要注意的是一個 `NULL` 字節 `"\0"` 并不等同于 `PHP` 的 `NULL` 常數。
判斷一個變量是否為數組,請使用`is_array()`,這種判斷尤其適用于對數組進行遍歷的操作,例如`foreach()`,因為如果不事先判斷,`foreach()`會對非數組類型的變量報錯;
判斷一個數組元素是否存在,可使用`isset($array['key'])`,也可使用`empty()`。
# 安全性
PHP中的變量不并不像C語言那樣需要事先聲明,解釋器會在第一次使用時自動創建他們,同樣類型也不需要指定,解釋器會根據上下文環境自動確定。從開發人員的角度來看,這無疑是一種極其方便的處理方法。
一個變量被創建了,就可以在程序中的任何地方使用。這導致的結果就是開發人員工經常不注意初始化變量。因此,為了提高程序的安全性,我們不能相信任何沒有明確定義的變量。所有的變量在定義使用前要初使化以防止惡意構造提交的變量覆蓋程序中使用的變量。
不要相信任何客戶端提交的數據是安全的。(包括:`$_GET`、`$_POST`、`$_COOKIE`、`$_FILES`、`$_SERVER`、`$_REQUEST`),獲取數據時必須使用安全相關函數獲取值,請查看
# 其他細節問題
山西創泰中每個文件的結構如下,必須嚴格按照此規范
```
<?php
/**
* 當前文件功能說明
* [WeEngine System] Copyright (c) 2013 - 2017 WE7.CC
*/
define('IN_SYS', true); //入口常量定義
require '../framework/bootstrap.inc.php'; //加載引導文件
require IA_ROOT . '/web/common/bootstrap.sys.inc.php';
load()->web('common'); //各種載加函數
load()->func('communication');
load()->model('cloud');
load()->classs('coupon');
$dos = array('post', 'delete', 'display'); //文件中操作訪問控制
$do = in_array($do, $dos) ? $do : 'post';
$_W['page']['title'] = '頁面標題'; //頁面標題及一些公共代碼
$id = intval($_GPC['id']);
if ($do == 'post') { //各分支代碼
} elseif ($do == 'delete') {
}
```
# 其他注意要點
正則表達式操作請使用`perl`兼容正則表達式,即 `preg_` 系列的函數。以提升效率。
盡量使用高版本的函數。
本項目不使用命名空間,因此需要按照傳統的方式使用文件名和文件夾來規劃代碼結構。