[TOC]
## 簡介
在這一節我給大家介紹一下本教程里的開發規范,這些也是筆者使用 ThinkPHP 框架開發項目時遵循的開發規范。本節主要介紹 ThinkPHP 框架開發過程中和 PHP 開發相關的規范,**不涉及** 前端 JavaScript 、 CSS 和資源文件規范,另外因為所介紹內容比較多篇幅較長,但希望大家耐心看完。筆者也希望這些信息可以做為大家在項目開發中的參考和借鑒。
## 初衷
和相比 Laravel 或其它 PHP 框架相比 ThinkPHP 框架設計過于靈活,在團隊開發過程中如果不能統一開發規范那對項目管理而言將是一種災難!僅 ThinkPHP 官方文檔里對一個功能的實現或一個屬性的設置就教授給我們好幾種方法。例如我們有個『CDN 域名』的變量,通過閱讀官方文檔里 [配置](http://www.hmoore.net/manual/thinkphp6_0/1037484) 這一節,我們知道在 ThinkPHP 中有以下幾種方法:
1. 硬代碼,直接寫死。- ? 可維護性低
2. 寫死在 `config/app.php` 文件中。 - ? 無法區分環境進行配置
3. 存儲于 `.env` 文件中,使用 `env()` 方法直接讀取。 - ? 雖然解決了環境變量問題但是不推薦
4. 存儲在 `.env` 和 `config/app.php` 文件中,然后使用 `config()` 函數來讀取。- ? 最佳實踐
第四種方式相比前三種方式而言,既支持環境變量,又具備極高的靈活性,假如遇到同樣的 CDN 多域名隨機問題,你只需要寫一個助手函數,然后在 `config/app.php` 中調用即可,不需要動到任何一行業務邏輯代碼。但當我們決定采用第四種方式時又遇到 **另一個問題** —— `.env` 文件支持的配置類型包括 `.ini` 、 `.xml` 、 `.json` 、 `.yaml` 和 `.php` ,我們在開發過程中應該選擇哪種配置格式?
## 能愿動詞
為了避免歧義,本節大量使用了「能愿動詞」,對應的解釋如下:
- 必須(Must) - 只能這樣子做,請無條件遵循,沒有別的選項;
- 絕不(Must Not)- 嚴令禁止,在任何情況下都不能這樣做;
- 應該(Should) - 強烈建議這樣做,但是不強求;
- 不應該(Should Not) - 強烈建議不這樣做,但是不強求;
- 可以(May) - 選擇性高一點,在這個文檔內,此詞語使用較少。
>[info] 參考: [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)
## 規范依據
在制定開發規范時,我們依據以下信條:
- 遵循 MVC 設計模式規范;
- 遵循 ThinkPHP 官方 [開發規范](http://www.hmoore.net/manual/thinkphp6_0/1037482) ;
- 遵循 **約定大于配置** 原則。
## 目錄結構
ThinkPHP6.0 支持多應用和單應用開發(見 [目錄結構](http://www.hmoore.net/manual/thinkphp6_0/1037483) ),我們創建項目時 **應該** 選擇 **多模塊** 開發模式。 **對于為什么不選擇框架默認的單應用模式,而選擇多應用模式開發,我們在后繼章節會詳細介紹。** 在這里,目錄結構除選擇多應用模式外還應遵循:
- **必須** 使用默認項目目錄名 `app` ,**絕不** 使用自定義項目目錄名;
- 應用命名 **應該** 遵循:用戶前臺應用—— `index` , 系統后臺應用—— `admin` , 共用應用—— `common`。
## 配置
- 項目配置 **必須** 存儲在 `.env` 文件和 `config` 目錄中,然后使用 `env()` 和 `config()` 函數來讀取;
- 應用配置 **必須** 存儲在模塊 `config` 目錄中,如:`app/index/config`;
- 配置參數名 **必須** 使用小寫定義配置參數的規范, 如:`app_name`;
- 配置文件 **必須** 以PHP數組格式存儲;
- `.env` 文件 **必須** 采用 `.ini` 格式存儲。
- **必須** 開啟 [自動時間戳](http://www.hmoore.net/manual/thinkphp6_0/1037592) 。
## 數據庫
- **必須** 遵循官方文檔的數據表和字段采用小寫加下劃線方式命名規范;
- **絕不** 使用命令行或者 PHPMyAdmin 直接創建索引或表。**必須** 使用 [數據庫遷移工具](http://www.hmoore.net/manual/thinkphp6_0/1118028) 去創建表結構,并提交版本控制器中;
- **絕不** 為了共享對數據庫更改就直接導出 SQL,所有修改都 **必須** 使用 [數據庫遷移工具](http://www.hmoore.net/manual/thinkphp6_0/1118028) ,并提交版本控制器中;
- **絕不** 直接向數據庫手動寫入偽造的測試數據。**必須** 使用數據填充來插入假數據,并提交版本控制器中;
- 數據表名 **必須** 為「單數」, 多個單詞情況下使用「[Snake Case](https://en.wikipedia.org/wiki/Snake_case)」 如:topic, user_log;
- 數據庫字段名 **必須** 為「[Snake Case](https://en.wikipedia.org/wiki/Snake_case)」,如:`view_count`, `create_time` ;
- 數據表主鍵 **必須** 為「id」;
- 數據表外鍵 **必須** 為「resource_id」,如:`user_id` , `topic_id` ;
- **絕不** 允許數據表沒有時間戳字段,每個表 **必須** 有創建時間( `create_time` ) 和 更新時間 (`update_time`),并且這兩個字段 **必須** 是整型( `int` );
- 和自動時間戳字段規范保持一致,其它時間類型 **必須** 存儲成整型( `int` ) ,并且命名 **應該** 以 `_time` 為后輟;
- **不應該** 所有數據庫字段允許空值,**應該** 為索引、字符串或整數字段指定一個合理的默認值。([數據庫允許空值(null),往往是悲劇的開始](https://mp.weixin.qq.com/s/XRSPITgWWK-2Ee-cSIqw1w))
- **應該** 為數據表常用查詢添加索引;
- 使用 [數據庫遷移工具](http://www.hmoore.net/manual/thinkphp6_0/1118028) 創建或更新表結構時,創建的遷移文件名 **應該** 清楚表達出遷移文件的用途,如創建用戶表遷移文件名為 `CreateTableUser` , 給用戶表添加頭像字段時命名為 `AddColumnAvatarToUser` ;
## 驗證器
[驗證器](http://www.hmoore.net/manual/thinkphp6_0/1037624) 是 ThinkPHP 框架推薦進行數據驗證的方式。
- 驗證器類 **必須** 放在模塊的 `validate` 目錄里, 如:`app/common/validate`;
- 驗證器類名 **必須** 為「單數」駝峰法命名, 如:`app\common\validate\Topic` ;
- 類文件名 **必須** 遵循駝峰法命名規范, 并且 **必須** 與類名保持大小寫一致,如 `app/common/validate/Topic.php`;
- 驗證器類 **應該** 遵循 **約定大于配置** 方式進行命名, 數據模型 `Topic` 對應的驗證器命名也 **應該** 是 `Topic` ;
- 當可以使用場景( `since' )實現對同一數據模型不用場景表單的驗證時 **應該** 使用場景實現,**不應該** 再定義新的驗證器;
## 數據模型
- 數據模型類 **必須** 放在模塊的 `model` 目錄里, 如:`app/common/model`;
- 數據模型類名 **必須** 為「單數」駝峰法命名, 如:`app\common\model\Topic` ;
- 類文件名 **必須** 遵循駝峰法命名規范, 并且 **必須** 與類名保持大小寫一致,如 `app/common/model/Topic.php`;
- 數據模型變量 **必須** 為「resource_id」,如:`$user_id`, `$topic_id` ;
- 在數據模型保存用戶表單提交數據前,**應該** 使用 [驗證器](http://www.hmoore.net/manual/thinkphp6_0/1037624) 來驗證表單數據是否有效;
- 當數據模型的代碼臃腫時,**應該** 利用 Trait 來精簡邏輯代碼量,提高可讀性;
- **絕不** 「過度設計(Over Designed)」,極大降低了編碼愉悅感。
## 控制器
- **必須** 優先使用 [Restful 資源控制器](http://www.hmoore.net/manual/thinkphp6_0/1037514) ;
- 控制器類 **必須** 放在模塊的 `controller` 目錄里,如:`app/index/controller`;
- 控制器類名 **必須** 為「單數」駝峰法命名, **絕不** 以 `Controller` 為后輟, 如:`app\index\controller\Topic` ;
- 類文件名 **必須** 遵循駝峰法命名規范, 并且 **必須** 與類名保持大小寫一致,如 `app/index/controller/Topic.php`;
- **不應該** 為「方法」書寫注釋,這要求方法取名要足夠合理,不需要過多注釋;
- **應該** 為一些復雜的邏輯代碼塊書寫注釋,主要介紹產品邏輯 - `為什么要這么做` ;
- **不應該** 在控制器中書寫「私有方法」,控制器里 **應該** 只存放「路由動作方法」;
- **絕不** 遺留「死方法」,就是沒有用到的方法,控制器里的所有方法,都應該被使用到,否則應該刪除;
- **絕不** 把業務邏輯代碼寫在控制器里,**應該** 寫在對應數據模型類里;
- **絕不** 在控制器里批量注釋掉代碼,無用的邏輯代碼就必須清除掉。
## 路由
- **絕不** 在路由配置文件里書寫『閉包路由』或者其他業務邏輯代碼;
- 路由器要保持干凈整潔,**絕不** 放置除路由配置以外的其他程序邏輯;
- **必須** 優先使用 [Restful](http://www.hmoore.net/manual/thinkphp6_0/1037501) 路由,配合資源控制器使用;
- **應該** 使用 `name` 方法為路由指定生成標識,[路由標識](http://www.hmoore.net/manual/thinkphp6_0/1037495) **必須** 優先使用『資源前綴』作為命名規范,如 `topic.index` 的資源前綴是 `topic.` ;
- 當路由有標識時,獲取 URL 時 **必須** 優先使用標識路由獲取, 如話題詳情頁對應的資源路由是 `topic/index` 而它的標識是 `topic.index` , 那么我們應該優先使用 `url('[topic.index]')` 獲取頁面路由。
## 助手函數
ThinkPHP 提供了很多 [助手函數](http://www.hmoore.net/manual/thinkphp6_0/1037653),但有時候我們也需要創建自己的助手函數。我們在創建自己的助手函數時應該遵循:
- 公共助手函數 **必須** 創建在 `app/common.php` 里;
- 模塊獨有助手函數 **必須** 創建在模塊的 `common.php` 里,如 `app/index/common.php`;
- 助手函數命名 **必須** 遵循 ThinkPHP 官方 [命名規范](http://www.hmoore.net/manual/thinkphp6_0/1037653), 使用小寫字母和下劃線(小寫字母開頭)的方式,例如 `get_client_ip`;
## 視圖模板
>[info] ThinkPHP6.0 把視圖渲染拆解成獨立的擴展包—— `think-template` ,并且框架默認不包含該擴展包,但在本項目開發過程中我們還是使用 `think-template` 來完成視圖頁面渲染。
- 視圖模板文件 **必須** 放在應用的 `view` 目錄里,如:`app/index/view`;
- 為了保持目錄清晰,模板布局 **應該** 放在 `view/layout` 目錄里;
- 局部視圖模板文件 **必須** 使用 `_ `前綴來命名,如:`app/index/view/topic/_sidebar.html`;
- 為了和 Restful 路由器和資源控制器保持一致,視圖模板命名也 **必須** 使用資源視圖的命名方式, 如控制器 `app\index\controller\Topic` 對應的視圖模板文件存放目錄是 `app/index/view/topic` ;
- 在視圖模板文件里 **應該** 優先使用 [內置標簽](http://www.hmoore.net/manual/think-template/1286416) , **不應該** 使用原生 PHP 語法。
## 代碼版本控制
- **應該** 使用 Git、SVN 或其它版本管理工具控制代碼版本;
- **應該** 每完成一個功能開發提交一次代碼;
- **絕不** 讓代碼在自己的電腦上過夜;
- **絕不** 把本地化配置文件版本到版本控制里,**應該** 上傳一個示例配置文件,如不要把 `.env` 文件納入版本控制;
- **絕不** 把項目運行緩存目錄和文件納入版本控制,如 `runtime` 目錄里;
- **絕不** 把第三方擴展包納入版本控制,如 `vendor` 目錄里的文件和子目錄;
- **絕不** 把用戶上傳文件納入版本控制,如用戶上傳的話題圖片和頭像圖片。
- 第一章 基礎信息
- 序言
- 關于作者
- PHP和ThinkPHP
- 如何正確閱讀本書
- 寫作約定
- 開發規范
- 章節體例
- 本書源碼
- 第二章 舞臺布置
- 開發環境
- 產品分解
- Git和GitHub
- 創建項目
- 數據庫視圖管理工具
- 統一代碼風格
- 目錄結構
- 配置信息
- 后臺應用搭建
- 助手函數
- 前臺布局模板
- 基礎控制器
- 小結
- 第三章 注冊登錄
- 數據遷移
- 表單提交
- 表單驗證
- 模型驗證
- 短信提供商
- 發送短信
- 手機驗證
- 注冊提醒
- 登錄與退出
- 重置密碼
- 數據填充
- 小結
- 第四章 用戶相關
- 個人中心
- 編輯個人資料
- 上傳圖片
- 上傳頭像
- 顯示頭像
- 限制頭像分辨率
- 裁剪頭像
- 顯示注冊時間
- 授權訪問
- 小結
- 第五章 帖子列表
- 話題分類
- 話題模型
- 話題列表
- 性能優化
- 分類話題列表
- 話題列表排序
- 用戶發布的話題
- 分頁器美化
- 小結
- 第六章_帖子CURD
- 創建話題
- 生成摘要
- 編輯器優化
- 上傳圖片
- 顯示話題
- 編輯話題
- 刪除話題
- 小結
- 第七章 帖子回復
- 回復模型
- 回復列表
- 發表回復
- 刪除回復
- XSS 安全漏洞
- 小結
- 第八章 角色權限和管理后臺
- 多角色用戶權限
- 用戶管理
- 話題管理
- 回復管理
- 小結
- 第九章 雜項
- 邊欄活躍用戶
- 用戶最后登錄時間
- 邊欄資源推薦
- 站點首頁
- 小結
- 第十章 總結
- 全書總結
- 附錄
- 淺談ThinkPHP6.0 路由