¶1. 前言
本规范大量参考 优帆远扬 团队内部 Laravel 工程师践行的开发规范。并由本人基于公司项目环境进行一定程度的修改。
在此感谢 laravel-china 社区和 优帆远扬团队 和 社区站长 @Summer 的无私奉献。
¶1.1. 关于规范
¶1.1.1. 说明
¶1.1.2 优势
- 高效编码 - 避免了过多的选择造成的『决策时间』浪费;
- 风格统一 - 最大程度统一了开发团队成员代码书写风格和思路,代码阅读起来如出一辙;
- 减少错误 - 减小初级工程师的犯错几率。
¶1.2. 开发哲学
因为篇幅原因本规范无法涉及到项目里每一块代码的编写标准,所以此处重点说明下此规范遵循的『开发哲学』,开发中请把其当做指明灯,来指引你做决策:
- DRY –「Don’t Repeat Yourself」不写重复的逻辑代码;
- 约定俗成 - 「Convention Over Configuration」,优先选择框架提倡的做法,不过度配置;
- KISS - 「Keep it Simple, Stupid」提倡简单易读的代码,不写高深、晦涩难懂的代码,不过度设计;
- 主厨精选 - 让有经验的人来为你选择方案,不独创方案;
- 官方提倡 - 优先选择官方推崇的方案。
¶1.3. 关于「能愿动词」的使用
为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:
- 必须(Must) - 只能这样子做,请无条件遵循,没有别的选项;
- 绝不(Must Not)- 严令禁止,在任何情况下都不能这样做;
- 应该(Should) - 强烈建议这样做,但是不强求;
- 不应该(Should Not) - 强烈建议不这样做,但是不强求;
- 可以(May) - 选择性高一点,在这个文档内,此词语使用较少;
参考:RFC 2119
¶1.4. 关于执行
在这份规范里,有些内容里会解释『这样做的理由』,这样做的目的是为了达成共识。
请不要以此『理由』的准确性来怀疑规范的权威性,规范就是规范,可以讨论改正,但在执行的时候 必须 严格遵守。
请把『团队项目开发』想象就是在行军打仗,对于规范要绝对服从。要有大局观,做到团结一致,把个人的喜好放一边,把整个团队的执行效率放在第一位。
¶2. 项目规范
¶2.1. Laravel 版本选择
以下是 Laravel 的版本路线图:
版本 | 发布日期 | 版本类型 | 维护周期 |
---|---|---|---|
Laravel 5.1 | 2015 年 6 月 | LTS 长久支持 | Bug 修复 2017 年 6 月份,安全修复 2018 年 6 月份 |
Laravel 5.2 | 2015 年 12 月 | 一般发行 | 提供 6 个月的 Bug 修复支持,一年的安全修复支持 |
Laravel 5.3 | 2016 年 8 月 | 一般发行 | 提供 6 个月的 Bug 修复支持,一年的安全修复支持 |
Laravel 5.4 | 2017 年 1 月 | 一般发行 | 提供 6 个月的 Bug 修复支持,一年的安全修复支持 |
Laravel 5.5 | 2017 年 8 月 | LTS 长久支持 | Bug 修复 2019 年 8 月份,安全修复 2020 年 8 月份 |
选择 Laravel 版本时,应该 优先考虑 LTS 版本,因为安全性和稳定性考虑,商业项目开发中 不应该 使用最新版本的 『Laravel 一般发行版』。
扩展阅读:如何选择 Laravel 框架版本。
请使用以下命令来创建指定版本的 Laravel 项目:
composer create-project laravel/laravel project-name --prefer-dist "5.5.*" |
¶2.2. 环境说明
一般情况下,一个项目 应该 有以下三个基本的项目环境:
- Local - 开发环境
- Staging - 线上测试环境
- Production - 线上生产环境
¶2.2.1. 服务器系统
Laravel 社区提倡使用 Ubuntu 系统,开发环境 Homestead 中默认也是使用 Ubuntu 系统。所以服务器系统 应该 优先考虑 Ubuntu,并且是 LTS 支持的系统,如 Ubuntu 14.04.5 LTS 或者 Ubuntu 16.04.2 LTS 。
¶2.2.2. PHP 7
PHP 版本 应该 优先考虑 PHP 7,不止因为其运行高效,还因为随着 PHP 7 的广泛应用,PHP 7 以下的版本将会很快停止维护。
¶2.2.3. MySQL 5.7
数据库软件 应该 优先选择 MySQL,因为其使用率最高。MySQL 5.7 与 PHP 7 一样,已经是大势所趋,选择版本时 应该 优先考虑 MySQL 5.7。
¶2.2.4. 其他软件
应该 优先选择 流行 稳定 版本。线上环境 绝不 使用 Beta 或者其他不稳定发行版。
¶2.2.5. Local 开发环境
所有项目 应该 使用 Homestead 作为应用程序运行的环境。请阅读 为什么必须使用 Homestead 来开发 Laravel 应用? 。
¶2.2.6. Production 生产环境
线上环境部署可以参考 Ubuntu 14/16 下的 Laravel LNMP 线上环境自动部署脚本,此脚本参照了 Homestead 环境设置脚本 ,并做了更加适用于生产环境的效率和安全调优。
处于安全考虑,线上环境 必须 只开放以下端口:
- 80 HTTP
- 443 HTTPS
- 22 SSH
¶2.2.7. Staging 线上测试环境
除了域名等其他独立应用配置以外,环境 必须 跟 Production 保持高度一致性,可以的话 应该 与 Production 使用同台机器。
¶2.2.8. 环境变量
在项目中 必须 使用环境变量(.env文件)来区分敏感数据的设置。
在项目中 必须 存在以下 env 文件,分别存放对应环境下的敏感数据配置,如数据库相关配置,缓存相关配置等
- .env.production
- .env.staging
- .env.local
并在对应的环境配置 APP_ENV
环境变量为 local
,staging
或 production
配置环境变量的做法是在 nginx
的配置文件中写下这句代码:
|
¶2.3. 扩展包的引入
Laravel 扩展包的注册会对应用造成消耗。有一些扩展包是开发环境中专用,生产环境中并不会使用到,为了避免无用的负载, 必须严格控制其安装和加载。
¶2.3.1. 安装
安装开发专用扩展包时 必须 使用 --dev
参数,如:
composer require laracasts/generators --dev |
¶2.3.2. 加载
开发专用的 provider
绝不 在 config/app.php
里面注册,必须 在 app/Providers/AppServiceProvider.php
文件中使用如以下方式:
public function register() |
¶2.4. 配置信息与环境变量
假如我们有个『CDN 域名』的变量,在 Laravel 中有以下几种方法:
- 硬代码,直接写死。- ❌ 可维护性低
- 写死在
config/app.php
文件中。 - ❌ 无法区分环境进行配置 - 存储于
.env
文件中,使用env()
方法直接读取。 - ❌ 虽然解决了环境变量问题但是不推荐 - 存储在
.env
和config/app.php
文件中,然后使用config()
函数来读取。- ✅ 最佳实践
第一种方法是最古老的方法,代码可维护性极低,一旦域名变更就只能全局替换。第二种方法无法区分环境,例如本地使用开发环境域名测试,线上才是正式的 CDN 域名。第三种方法虽然解决了环境变量的问题,并且也具备一定的灵活性,但是不够灵活,假如你的网站流量巨大,需要配置几个 CDN 域名,使其在加载静态资源时随机支配域名,这种做法就无法满足需求了。第四种方法即支持环境变量,又具备极高的灵活性,假如遇到同样的 CDN 多域名随机问题,你只需要写一个辅助方法,然后在 config/app.php
中调用即可,不需要动到任何一行业务逻辑代码。
¶2.4.1. 代码示例
.env
文件中设置:
CDN_DOMAIN=cdndomain.com |
config/app.php
文件中设置:
'cdn_domain' => env('CDN_DOMAIN', null), |
程序中两种获取 相同配置
的方法:
env('CDN_DOMAIN')
config('app.cdn_domain')
在此统一规定:所有程序配置信息 必须 通过 config()
来读取,所有的 .env
配置信息 必须 通过 config()
来读取,绝不 在配置文件以外的范围使用 env()
。
¶2.4.2. 有何优势
这样做主要有以下几个优势:
- 定义分明,
config()
是配置信息,env()
只是用来区分不同环境; - 统一放置于
config
中还可以利用框架的 配置信息缓存功能 来提交运行效率; - 代码健壮性,
config()
在env()
之上多出来一个抽象层,会使代码更加健壮,更加灵活。
注: Laravel 5.2 以后的官方文档也提倡用此方法。
¶2.5. 辅助函数
Laravel 提供了很多 辅助函数,有时候我们也需要创建自己的辅助函数。
必须 把所有的『自定义辅助函数』存放于 bootstrap/helpers.php
中。
并在 bootstrap/app.php
文件的最顶部进行加载:
|
¶2.6. 代码风格
代码风格 必须 严格遵守 PSR2 规范
在代码提交前 应该 使用 PHP_CodeSniffer 工具做规范检查
¶2.7. 路由器 todo
¶2.7.1 路由闭包
绝不 在路由配置文件里书写『闭包路由』或者其他业务逻辑代码,因为一旦使用将无法使用 路由缓存 。
路由器要保持干净整洁,绝不 放置除路由配置以外的其他程序逻辑。
¶2.7.2. Restful 路由
必须 优先使用 Restful 路由,配合资源控制器使用,见 文档。
超出 Restful 路由的,应该 模仿上图的方式来定义路由。
¶2.7.3. resource 方法正确使用
一般资源路由定义:
Route::resource('photos', 'PhotosController'); |
等于以下路由定义:
Route::get('/photos', 'PhotosController@index')->name('photos.index'); |
使用 resource
方法时,如果仅使用到部分路由,必须 使用 only
列出所有可用路由:
Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]); |
绝不 使用 except
,因为 only
相当于白名单,相对于 except
更加直观。路由使用白名单有利于养成『安全习惯』。
¶2.7.3. 单数 or 复数?
资源路由路由 URI 必须 使用复数形式,如:
/photos/create
/photos/{photo}
错误的例子如:
/photo/create
/photo/{photo}
¶2.7.4. 路由模型绑定
在允许使用路由 模型绑定 的地方 必须 使用。
模型绑定代码 必须 放置于 app/Providers/RouteServiceProvider.php
文件的 boot
方法中:
public function boot() |
¶2.7.5. 全局路由器参数
出于安全考虑,应该 使用全局路由器参数限制,详见 文档。
必须 在 RouteServiceProvider
文件的 boot 方法里定义模式:
/** |
模式一旦被定义,便会自动应用到所有使用该参数名称的路由上:
Route::get('users/{id}', 'UsersController@show'); |
只有在 id
为数字时,才会路由到控制器方法中,否则 404 错误。
¶2.7.6. 路由命名#
除了 resource
资源路由以外,其他所有路由都 必须 使用 name
方法进行命名。
必须 使用『资源前缀』作为命名规范,如下的 users.follow
,资源前缀的值是 users.
:
Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow'); |
¶2.7.7. 获取 URL
必须 使用 route
来获取 URL:
$url = route('profile', ['id' => 1]); |
无法使用 route
的情况下,可以 使用 url
方法来获取 URL:
url('profile', [1]); |
¶2.8. 数据模型
¶2.8.1. 放置位置
所有的数据模型文件,都 必须 存放在:app/Models/
文件夹中。
命名空间:
namespace App\Models; |
¶2.8.2. User.php
Laravel 5.1 之后默认安装会把 User
模型存放在 app/User.php
,必须 移动到 app/Models
文件夹中,并修改命名空间声明为 App/Models
,同上。
为了不破坏原有的逻辑点,必须 全局搜索 App/User
并替换为 App/Models/User
。
¶2.8.3. 使用基类
所有的 Eloquent 数据模型 都 必须 继承统一的基类 App/Models/BaseModel
,此基类存放位置为 /app/Models/BaseModel.php
,内容参考以下:
|
以 Photo 数据模型作为例子继承 BaseModel 基类:
|
¶2.8.4. 命名规范
数据模型相关的命名规范:
- 数据模型类名
必须
为「单数」, 如:App\Models\Photo
- 类文件名
必须
为「单数」,如:app/Models/Photo.php
- 数据库表名字
必须
为「复数」,多个单词情况下使用「Snake Case」 如:photos
,my_photos
- 数据库表迁移名字
必须
为「复数」,如:2014_08_08_234417_create_photos_table.php
- 数据填充文件名
必须
为「复数」,如:PhotosTableSeeder.php
- 数据库字段名
必须
为「Snake Case」,如:view_count
,is_vip
- 数据库表主键
必须
为「id」 - 数据库表外键
必须
为「resource_id」,如:user_id
,post_id
- 数据模型变量
必须
为「resource_id」,如:$user_id
,$post_id
¶2.8.5. 利用 Trait 来扩展数据模型
有时候数据模型里的代码会变得很臃肿,应该 利用 Trait 来精简逻辑代码量,提高可读性,类似于 Ruby China 源码。
借鉴于 Rails 的设计理念:「Fat Models, Skinny Controllers」。
存放于文件夹:app/Models/Traits
文件夹中。
¶2.8.6. 关于 SQL 文件
- 绝不 使用命令行或者 PHPMyAdmin 直接创建索引或表。必须 使用 数据库迁移 去创建表结构,并提交版本控制器中;
- 绝不 为了共享对数据库更改就直接导出 SQL,所有修改都 必须 使用 数据库迁移 ,并提交版本控制器中;
- 绝不 直接向数据库手动写入伪造的测试数据。必须 使用 数据填充 来插入假数据,并提交版本控制器中。
¶2.8.7. 全局作用域
Laravel 的 Model 全局作用域 允许我们为给定模型的所有查询添加默认的条件约束。
所有的全局作用域都 必须 统一使用 闭包定义全局作用域
,如下:
/** |
¶2.9. 控制器
todo
¶2.10. 视图
todo
¶2.11. Artisan 命令行
所有的自定义命令,都 必须 有项目的命名空间
如:
php artisan ssc:data-manager |