[TOC] ## 简介 在这一节我给大家介绍一下本教程里的开发规范,这些也是笔者使用 ThinkPHP 框架开发项目时遵循的开发规范。本节主要介绍 ThinkPHP 框架开发过程中和 PHP 开发相关的规范,**不涉及** 前端 JavaScript 、 CSS 和资源文件规范,另外因为所介绍内容比较多篇幅较长,但希望大家耐心看完。笔者也希望这些信息可以做为大家在项目开发中的参考和借鉴。 ## 初衷 和相比 Laravel 或其它 PHP 框架相比 ThinkPHP 框架设计过于灵活,在团队开发过程中如果不能统一开发规范那对项目管理而言将是一种灾难!仅 ThinkPHP 官方文档里对一个功能的实现或一个属性的设置就教授给我们好几种方法。例如我们有个『CDN 域名』的变量,通过阅读官方文档里 [配置](https://www.kancloud.cn/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 官方 [开发规范](https://www.kancloud.cn/manual/thinkphp6_0/1037482) ; - 遵循 **约定大于配置** 原则。 ## 目录结构 ThinkPHP6.0 支持多应用和单应用开发(见 [目录结构](https://www.kancloud.cn/manual/thinkphp6_0/1037483) ),我们创建项目时 **应该** 选择 **多模块** 开发模式。 **对于为什么不选择框架默认的单应用模式,而选择多应用模式开发,我们在后继章节会详细介绍。** 在这里,目录结构除选择多应用模式外还应遵循: - **必须** 使用默认项目目录名 `app` ,**绝不** 使用自定义项目目录名; - 应用命名 **应该** 遵循:用户前台应用—— `index` , 系统后台应用—— `admin` , 共用应用—— `common`。 ## 配置 - 项目配置 **必须** 存储在 `.env` 文件和 `config` 目录中,然后使用 `env()` 和 `config()` 函数来读取; - 应用配置 **必须** 存储在模块 `config` 目录中,如:`app/index/config`; - 配置参数名 **必须** 使用小写定义配置参数的规范, 如:`app_name`; - 配置文件 **必须** 以PHP数组格式存储; - `.env` 文件 **必须** 采用 `.ini` 格式存储。 - **必须** 开启 [自动时间戳](https://www.kancloud.cn/manual/thinkphp6_0/1037592) 。 ## 数据库 - **必须** 遵循官方文档的数据表和字段采用小写加下划线方式命名规范; - **绝不** 使用命令行或者 PHPMyAdmin 直接创建索引或表。**必须** 使用 [数据库迁移工具](https://www.kancloud.cn/manual/thinkphp6_0/1118028) 去创建表结构,并提交版本控制器中; - **绝不** 为了共享对数据库更改就直接导出 SQL,所有修改都 **必须** 使用 [数据库迁移工具](https://www.kancloud.cn/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)) - **应该** 为数据表常用查询添加索引; - 使用 [数据库迁移工具](https://www.kancloud.cn/manual/thinkphp6_0/1118028) 创建或更新表结构时,创建的迁移文件名 **应该** 清楚表达出迁移文件的用途,如创建用户表迁移文件名为 `CreateTableUser` , 给用户表添加头像字段时命名为 `AddColumnAvatarToUser` ; ## 验证器 [验证器](https://www.kancloud.cn/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` ; - 在数据模型保存用户表单提交数据前,**应该** 使用 [验证器](https://www.kancloud.cn/manual/thinkphp6_0/1037624) 来验证表单数据是否有效; - 当数据模型的代码臃肿时,**应该** 利用 Trait 来精简逻辑代码量,提高可读性; - **绝不** 「过度设计(Over Designed)」,极大降低了编码愉悦感。 ## 控制器 - **必须** 优先使用 [Restful 资源控制器](https://www.kancloud.cn/manual/thinkphp6_0/1037514) ; - 控制器类 **必须** 放在模块的 `controller` 目录里,如:`app/index/controller`; - 控制器类名 **必须** 为「单数」驼峰法命名, **绝不** 以 `Controller` 为后辍, 如:`app\index\controller\Topic` ; - 类文件名 **必须** 遵循驼峰法命名规范, 并且 **必须** 与类名保持大小写一致,如 `app/index/controller/Topic.php`; - **不应该** 为「方法」书写注释,这要求方法取名要足够合理,不需要过多注释; - **应该** 为一些复杂的逻辑代码块书写注释,主要介绍产品逻辑 - `为什么要这么做` ; - **不应该** 在控制器中书写「私有方法」,控制器里 **应该** 只存放「路由动作方法」; - **绝不** 遗留「死方法」,就是没有用到的方法,控制器里的所有方法,都应该被使用到,否则应该删除; - **绝不** 把业务逻辑代码写在控制器里,**应该** 写在对应数据模型类里; - **绝不** 在控制器里批量注释掉代码,无用的逻辑代码就必须清除掉。 ## 路由 - **绝不** 在路由配置文件里书写『闭包路由』或者其他业务逻辑代码; - 路由器要保持干净整洁,**绝不** 放置除路由配置以外的其他程序逻辑; - **必须** 优先使用 [Restful](https://www.kancloud.cn/manual/thinkphp6_0/1037501) 路由,配合资源控制器使用; - **应该** 使用 `name` 方法为路由指定生成标识,[路由标识](https://www.kancloud.cn/manual/thinkphp6_0/1037495) **必须** 优先使用『资源前缀』作为命名规范,如 `topic.index` 的资源前缀是 `topic.` ; - 当路由有标识时,获取 URL 时 **必须** 优先使用标识路由获取, 如话题详情页对应的资源路由是 `topic/index` 而它的标识是 `topic.index` , 那么我们应该优先使用 `url('[topic.index]')` 获取页面路由。 ## 助手函数 ThinkPHP 提供了很多 [助手函数](https://www.kancloud.cn/manual/thinkphp6_0/1037653),但有时候我们也需要创建自己的助手函数。我们在创建自己的助手函数时应该遵循: - 公共助手函数 **必须** 创建在 `app/common.php` 里; - 模块独有助手函数 **必须** 创建在模块的 `common.php` 里,如 `app/index/common.php`; - 助手函数命名 **必须** 遵循 ThinkPHP 官方 [命名规范](https://www.kancloud.cn/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` ; - 在视图模板文件里 **应该** 优先使用 [内置标签](https://www.kancloud.cn/manual/think-template/1286416) , **不应该** 使用原生 PHP 语法。 ## 代码版本控制 - **应该** 使用 Git、SVN 或其它版本管理工具控制代码版本; - **应该** 每完成一个功能开发提交一次代码; - **绝不** 让代码在自己的电脑上过夜; - **绝不** 把本地化配置文件版本到版本控制里,**应该** 上传一个示例配置文件,如不要把 `.env` 文件纳入版本控制; - **绝不** 把项目运行缓存目录和文件纳入版本控制,如 `runtime` 目录里; - **绝不** 把第三方扩展包纳入版本控制,如 `vendor` 目录里的文件和子目录; - **绝不** 把用户上传文件纳入版本控制,如用户上传的话题图片和头像图片。