### 2018 年 12 月 15 日 发布 >[info] ThinkPHP作为坚持十多年的老牌框架,始终秉承着「大道至简,开发由我」的开发理念,逐渐形成了一套自己的开发规范,在历史版本的迭代过程中,也曾多次完善并趋于规范。 本文就来总结下目前最新版本的开发规范,但可能由于篇幅原因无法涵盖所有的PHP开发规范,而仅仅是框架层面的使用规范。建议阅读官方开发者周刊第3期发布过的「[PHP版的代码整洁之道 中文翻译](https://github.com/php-cpm/clean-code-php)」以及「[PHP之道](https://www.kancloud.cn/thinkphp/php-the-right-way/3126)」作为本文很好的代码规范补充,相信一定会获益良多。 ## 选择合适的版本 ### 框架版本 ThinkPHP目前主要存在三个版本,很多人容易困惑到底应该选择哪个版本开发。如果你是新接触ThinkPHP,那么毫无疑问`5.1`版本是当然之选,时代在进步,技术永远用新不用旧,何况没有任何的包袱。 如果你之前接触过`3.2`或者`5.0`版本,有线上运营产品想升级到最新的版本,那我会建议你谨慎考虑,因为升级并非无缝,而且单纯的升级代码没法真正使用和发挥新版的特性,如果开发思想不升级的话。最合适的选择应该是关注官方的安全更新,进行小版本升级。 如果你的产品已经被用户广泛使用,出于品牌战略发展考虑,那我建议你在保留老版本运营的前提下,尽快把基于`5.1 LTS`版本重构纳入下一代产品计划,也许对一些新客户有更好的说服力。 >[info] 任何时候,当你开始规划商业产品的开发工作,出于安全性和稳定性考虑,应该首先选择最新的`LTS`版本。 下面给出官方主要版本的生命周期,供版本选择参考: |ThinkPHP版本|发布时间|BUG修复|安全更新| |---|---|---|---| |3.2(PHP5.3+)|2013年12月18日|结束服务|结束服务| |5.0(PHP5.4+)|2016年9月15日|2019年1月1日|2020年1月1日| |5.1 LTS(PHP5.6+)|2018年1月1日|2020年1月1日|2021年1月1日| 最后,如果你仅仅是因为兴趣而非工作,并且乐于接受新技术和一定的探索精神,愿意给官方进行测试反馈,那么`5.2`版本(目前还是`Beta`版本)不失为一个不错的选择。欢迎有更多的朋友参与ThinkPHP下一代产品的建设和贡献中。 ### PHP版本 如果是新的项目,目前应该尽量选择PHP`7.1+`作为你的PHP版本(ThinkPHP`5.1`版本可以很好的支持`7.1+`),可以拥有更好的性能,不再建议使用低于`7.0`的版本,官方已经不再提供技术支持服务,意味着不会有任何的安全更新。 >[danger] 有些PHP扩展可能不支持PHP的高版本,这个时候你要做出选择,使用低版本还是寻求更好的扩展解决方案。 ## 保持测试环境和部署环境的一致性 在开发过程中,应该尽量保持你的测试环境和正式部署环境的一致性,包括运行环境和版本,无论在本地测试环境还是部署环境,都应当统一使用域名方式访问,本地可以使用测试域名,例如你的正式部署域名为`thinkphp.cn`,那么本地测试环境可以使用`thinkphp`或者`thinkphp.test`作为测试域名,避免使用`localhost`或者`127.0.0.1`这种测试地址。对于有多个域名的部署应用,本地也要尽量模拟多个域名。 ## 配置部署忽略清单 项目根目录下面有一个`.gitignore`文件,用于定义提交版本库的时候哪些文件或者目录需要忽略,设置忽略的文件不会被同步到远程服务器,只是用于本地开发。 该文件默认内容如下,你可以根据项目的目录和规范进行调整。 ``` /.idea /.vscode /vendor *.log thinkphp .env ``` 项目使用的核心框架以及`composer`安装的扩展,不应当被同步到版本库中,只需要同步`composer.json`以及`composer.lock`文件。然后在服务器端进行`composer`更新。 ## 使用统一的IDE以及代码规范配置或者插件 项目团队应当尽量使用统一的IDE作为开发工具,并规范一致的代码规范配置项,如果使用的第三方代码规范及自动完成插件。如果团队成员较多而无法完全统一,最低限度,项目代码风格必须遵循`PSR-1`和`PSR-2`规范。 ## 基本命名规范 ThinkPHP遵循`PSR-2`命名规范以及`PSR-4`自动加载规范,并注意如下规范: ### 类和文件命名 * 类(包括接口、Trait)文件名和类名保持一致,并且使用首字母大写的驼峰命名; * 函数文件、配置文件、路由定义文件等文件名使用小写规范; * 无论类还是普通文件都使用`.php`后缀; * 目录名统一使用小写规范,并且使用单数规范; * 模板文件使用小写规范; ### 配置和变量命名 * 配置参数名统一使用小写规范; * 常量定义统一使用大写规范; * 环境变量定义统一使用大写规范; ### 函数和类、属性命名 * 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 `get_client_ip`; * 方法的命名使用驼峰法(首字母小写),例如 `getUserName`; * 属性的命名使用驼峰法(首字母小写),例如 `tableName`、`instance`; * 特例:以双下划线`__`打头的函数或方法作为魔术方法,例如` __call` 和 `__callStatic`; ### 数据表命名 数据表和字段采用小写加下划线方式命名,例如 `think_user `表和 `user_name`字段,禁止使用驼峰、中文或者拼音作为数据表及字段命名。 ## 配置规范化 线上环境和本地测试环境应该使用一致的配置文件,差异化的配置使用环境变量方式处理。本地环境可以通过定义`.env`文件(注意添加到忽略文件列表)来模拟环境变量。 在你需要差异化配置的参数中使用`env`函数定义,例如: ``` 'db_host' => env('db_host', '127.0.0.1'), ``` 然后在环境变量中或者本地`.env`中定义 ``` DB_HOST = 192.168.0.12 ``` 尽量不要在配置文件以外使用`env`函数获取配置参数。统一使用`config`函数获取配置参数。 除了定义配置文件之外,避免使用动态配置功能,保持仅读取配置参数的良好习惯。 如果需要提高配置文件的性能,可以考虑使用`Yaconf`扩展。ThinkPHP核心已经支持`Yaconf`配置定义,参考:[在ThinkPHP使用Yaconf](https://blog.thinkphp.cn/783762)。 ## 是否应当使用助手函数 助手函数的初衷是为了简化代码和更方便记忆,但如果不是很清楚助手函数的内部实现原理,很容易导致滥用,例如`db`助手函数就是一个非常典型的例子。 `db`助手函数设计的时候,因为`Db`类是一个静态单例对象,而且`5.1`版本的数据查询设计为每次查询后不会清空当前对象的查询条件,为了避免下面的代码查询条件产生混淆,`db`函数被设计成每次重新连接。 ``` db('user')->where('status', 1)->select(); db('user')->where('name', 'thinkphp')->select(); ``` 因此下面的代码中,循环中的每次查询都会重新连接一次数据库,从而造成超过数据库的最大连接数错误。 ``` foreach($users as $name) { db('user')->where('name', $name)->update(['status' => 1]); } ``` 上面的代码建议使用`Db`类直接操作 ``` foreach($users as $name) { Db::name('user')->where('name', $name)->update(['status' => 1]); } ``` 由于现代的IDE提示和自动完成功能之强大,助手函数的作用非常有限,而且只会用助手函数对于框架的原理认识较浅,因此建议是掌握助手函数的内部实现原理后再来决定在项目规范中是否需要使用助手函数,以及如何使用。 毕竟有些场景下,助手函数是非常简单实用的,例如: ``` public function getUser($id) { $user = User::getOrEmpty($id); return json($user); } ``` 产品交付给客户的时候,有些时候助手函数能够让客户自定义模板的时候更方便。 如果你需要额外定义或者覆盖原有的助手函数,可以直接在应用的`common.php`公共文件中定义。 ## 路由规范 统一使用路由方法注册路由而不要再使用返回数组配置,可以清晰直观的看到每个路由规则的详情,也便于开启路由缓存。路由规则不区分大小写,因此统一使用小写定义。 尽量避免使用闭包定义路由规则(注意不要和分组路由的闭包定义搞混淆),否则无法使用路由缓存功能。 优先使用资源路由定义,在不适用资源路由的情况下也要多使用路由分组,不仅可以简化路由定义和提高性能,也更加规范。 大部分情况下,建议开启全局路由完整匹配,个别不需要完整匹配的路由规则可以在定义的时候使用`completeMatch`方法单独关闭。 ``` Route::get('user/<name>', 'user/info')->completeMatch(false); ``` 如果需要使用伪静态地址,可以全局配置URL访问后缀,对于个别特殊后缀的路由可以在路由定义的时候单独指定。 ``` Route::get('hello/<name>', 'index/hello')->ext('htm'); ``` 明确你的路由变量规则,不要忽略路由变量的规则定义,避免可能的解析错误。例如,当你的路由变量中使用了小数点或者斜线的情况,必须严格定义你的变量规则。 优先在路由定义的时候指定中间件、进行数据验证和请求缓存等操作,原则就是在路由里面能做的事情尽量提前不要等到控制器里面才执行。 为了方便查看当前项目定义的路由规则,可以使用下面的指令生成路由规则查看文件。 ``` php think route:list ``` 然后可以在`runtime`目录下的`route_list.php`可以查看所有的路由列表,可以参考这篇:[命令行的表格输出](https://blog.thinkphp.cn/754434)。 更多的路由使用技巧可以参考:[路由使用心得技巧](https://blog.thinkphp.cn/868952)。 ## 控制器规范 为了避免命名冲突,可以统一开启类库后缀。 优先使用资源控制器,可以通过命令行快速生成一个资源控制器类 ``` php think make:controller index/Blog ``` 控制器建议继承一个公共的控制器类,便于统一调整和增加通用逻辑。并建议继承系统的控制器类,以便于使用控制器中间件功能。 对于控制器操作方法的拦截以及统一处理应当使用中间件独立操作。 控制器的代码应当尽量少,以确保逻辑清晰和可读性。始终保持`controller`层作为访问控制器层的名称。 请求数据的验证操作统一使用验证器进行验证。 操作方法中的对象使用依赖注入,其它的必要参数使用参数自动绑定。 不要在操作方法中输出除了调试信息之外的任何内容,而是通过`return`返回需要输出的内容。 操作方法中始终明确响应输出的类型,而不要依赖全局配置。 ## 数据库和模型规范 ### 基本规范 * 主键统一使用`id`; * 外键统一使用`resource_id`形式(例如`user_id`); * 模型数据字段统一使用小写+下划线命名,和数据表字段规范一致; * 数据表统一添加系统时间字段(`create_time`和`update_time`),并使用`datetime`类型; * 使用软删除并添加时间字段`delete_time`,类型和系统时间字段保持一致; * 模型类应该继承一个统一的公共类,便于调整和统一设置; * 模型类应当通过定义`autoWriteTimestamp`属性明确时间字段类型; ### 查询规范 不要在数据库配置文件以外的地方配置或者动态设置数据库连接信息,包括模型内部也应该仅使用配置参数名定义不同的数据库连接。 尽量不使用原生SQL查询,而应当使用查询构造器。 不要使用任何数据库工具创建、修改数据表和填充数据,应当使用[数据迁移](https://www.kancloud.cn/thinkphp/master-database-and-model/265553)并同步版本库给所有成员。 每次数据查询都用`Db`类或者模型类的静态方法。 避免在模型方法中直接写复杂的查询条件,而应当使用查询范围或者搜索器统一定义后调用。用查询表达式方式替代传统的数组查询。 查询数据的处理统一使用获取器定义,而不要直接处理数据。 对写入数据需要额外处理的话统一使用修改器。 对于使用了SQL函数的用法,使用`fieldRaw`、`orderRaw`和`whereRaw`/`whereExp`替代`field`、`order`和`where`用法。 仅在使用字符串查询条件,以及调用`whereExp`和`whereRaw`方法的时候需要使用手动参数绑定,其余情况下都会自动进行参数绑定,也不要手动调用`bind`方法。 不要在WEB访问的时候进行大量数据操作,容易超时的数据处理应当在命令行下通过创建指令完成。 更多的使用技巧可以参考:[提高开发效率的查询技巧](https://blog.thinkphp.cn/848639)以及[模型关联查询不完全指南](https://blog.thinkphp.cn/852701)。 ## 是否需要模型分层 一般情况下,仅仅使用Model层已经够用,但如果项目比较大,建议对模型进行分层,例如使用数据层、逻辑层和服务层等等,视项目需求而定,原则就是避免某一层过大导致结构杂乱,尽量让各个层分工明确,各司其职。 ## 模板规范 * 模板文件应当使用操作方法转换为小写+下划线方式命名; * 当你需要跨模块调用的时候,尽量在控制器操作方法中明确要渲染的模板; * 避免在模板文件中添加逻辑代码,应当只是数据的输出; * 尽量避免在模板文件中添加样式和JS代码,不得已的时候必须使用`{literal} {/literal}`包含起来; * 使用`static`目录统一存放静态资源文件; ## 日志规范 确保设置日志的最大数量限制,避免日志空间过大。 ``` 'max_files' => 30 ``` 超过设置的数量后,最早的日志将会被自动清理。 如果需要把日志接入阿里云,可以设置为单一日志文件,具体可以参考:[thinkphp日志接入阿里云日志系统](https://www.kancloud.cn/xieyongfa123/thinkphp_note/852704) 更多日志使用建议参考:[如何更有效的记录和管理日志](https://blog.thinkphp.cn/817547)。 ## 命令行规范 * 相关缓存指令应当在部署到服务器后执行; * 项目的指令应当添加独立的指令空间,例如 `php think app:command_name`; * 在`command.php`定义命令行指令列表的时候使用 “指令名 => 完整的类名”方式,提高性能; ## 写更健壮的代码 安全问题不容忽视,参考官方发布的[安全规范指引](https://blog.thinkphp.cn/789333),提前做好安全防范,让你的项目更健壮。同时也要关注官方的[开发者周刊](https://www.kancloud.cn/thinkphp/weekly/content)和微信公众号,及时获取官方的安全更新通告。 ## 做好应用优化工作 参考[如何有效提高ThinkPHP的应用性能](https://blog.thinkphp.cn/843679)一文做好相关优化工作。 ## 使用持续集成/持续部署构建你的项目 如果条件允许,请使用[持续集成/持续部署](https://blog.csdn.net/sinat_35930259/article/details/79429743),并添加自动化测试。[Travis CI](https://travis-ci.org/)或者[PHPCI](https://www.phptesting.org/)都是不错的选择。 ## 编写项目文档 每个项目都应该在根目录添加`readme.md` 文件,并遵循`Markdown`规范写作,对项目做简要的说明(尤其是目录和代码规范),如果项目比较复杂,可以附上一个项目详细说明或者规范的文档地址(托管到[看云文档平台](https://www.kancloud.cn)上是一个很好的选择),如果你的项目是前后端完全分离开发的话,应该事先规划好后台的API接口,然后在看云上创建一个**API文档**,便于指导前端开发人员进行接口调用,以及方便在线调试。