图片 2

项目开发规范,关联模型由于名称一致性导致的问题

1. 定义关联模型

在Laravel里面,我们可以通过定义以下Model来完成关联查询。

class MyPost extends Eloquent {
    public function myPostInfo () {
        return $this->hasOne('MyPostInfo');
    }
}

class MyPostInfo extends Eloquent {}

环境说明

一般情况下,一个项目 应该 有以下三个基本的项目环境:

  • Local – 开发环境
  • Staging – 线上测试环境, develop 分支
  • Production – 线上生产环境, master 分支

一. 说明

以下内容大部分引用Laravel China社区的文章 – 分享下团队的开发规范
——《Laravel
项目开发规范》。
相对而言,上面引用的文章的规范更加严格,但考虑到目前的情况,会适当地对一些规范进行更改和增删。

2. 使用关联模型

这里myPostInfo()用的是Camel命名规则,但是我们在读取某一个PostInfo的时候可以用Snake规则。如下面代码都是可行的:

$post = MyPost::find(1);
$post_info = $post->myPostInfo; // example 1
$post_info = $post->my_post_info; // example 2

Laravel允许上述两种方法,但是没有合理的处理使用两种命名造成的冲突。

开发专用扩展包

二. 目的

暂无

3. 缓存失效

如果我们同时使用了上述两个例子,就会使其中一个缓存失效。在Model的relations变量中,缓存了已经读取过的关联Model,但是当我们用不同规则的名字去读取的时候,却会使得前一个缓存失效。例如

$post_info = $post->myPostInfo; 
// $post->relations = [‘myPostInfo’ => ..];

$post_info = $post->my_post_info;
// $post->relations = [‘myPostInfo’ => …, ‘my_post_info’ => …];

所以如果不希望缓存失效,得在项目中只使用一种命名方法去读取关系模型。Laravel推荐的是Camel
Case.

安装

安装开发专用扩展包时 必须 使用 --dev 参数,如:

composer require laracasts/generators --dev

三. 优点

规范有一下优点:

  • 高效编码 – 避免了过多的选择造成的『决策时间』浪费;
  • 风格统一 –
    最大程度统一了开发团队成员代码书写风格和思路,代码阅读起来如出一辙;
  • 减少错误 – 减小初级工程师的犯错几率。

4. toArray() 方法失效

如果同时使用了两者,另外一个问题就是导致Model::toArray()失效。因为toArray()方法首先去relations中查找Snake
Case命名的关联模型,没有的话才去看Camel Case的。

所以如果用到了toArray()方法来转换Model,切忌同时使用两者。

加载

开发专用的 provider 绝不在 config/app.php 里面注册,必须在

app/Providers/AppServiceProvider.php 文件中使用如以下方式:

public function register()
{
    if ($this->app->environment() == 'local') {
        $this->app->register('Laracasts\Generators\GeneratorsServiceProvider');
    }
}

四. 开发哲学

  • DRY –「Don’t Repeat Yourself」不写重复的逻辑代码;
  • 约定俗成 – 「Convention Over
    Configuration」,优先选择框架提倡的做法,不过度配置;
  • KISS – 「Keep it Simple,
    Stupid」提倡简单易读的代码,不写高深、晦涩难懂的代码,不过度设计
  • 主厨精选 – 让有经验的人来为你选择方案,不独创方案;
  • 官方提倡 – 优先选择官方推崇的方案。

5. 容易犯错的位置

最容易犯错的代码是这样的:

MyPost::with(‘myPostInfo’)->get();

在使用With去eagerLoad关联模型时,必须使用和定义方法同名的key去读取,那么这样读取出来的方法只能是Camel
Case的key。其他地方就只能用

$my_post->myPostInfo;

来保证不出问题。

配置信息与环境变量

在此统一规定:所有程序配置信息 必须 通过 config() 来读取,所有的
.env 配置信息 必须 通过 config() 来读取,绝不
在配置文件以外的范围使用 env()

Show
Detail

五. 设计理念

以下是一些优秀的『程序设计理念』:

  • MVC – Model, View, Controller ,以 MVC 为核心,严格控制 Controller
    的可读性和代码行数;
  • Restful – 利用『资源化概念』和标准的 HTTP 动词来组织你的程序;

在此规范中,我们会将使用这两套理念作为程序设计基础。这些设计理念为我们设计程序提供了依据,遵循这些理念,能让程序变得清晰易读。

辅助函数

必须 把所有的『自定义辅助函数』存放于 bootstrap 文件夹中。

并在 bootstrap/app.php 文件的最顶部进行加载:

<?php

require __DIR__ . '/helpers.php';

...

六. 能愿动词

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须(Must) – 只能这样子做,请无条件遵循,没有别的选项;
  • 绝不(Must Not)- 严令禁止,在任何情况下都不能这样做;
  • 应该(Should) – 强烈建议这样做,但是不强求;
  • 不应该(Should Not) – 强烈建议不这样做,但是不强求;
  • 可以(May) – 选择性高一点,在这个文档内,此词语使用较少;

代码风格

代码风格 必须 严格遵循 PSR-2
规范。所以使用代码格式化插件要配置为PSR-2

七. 关于Laravel版本的选择

选择Laravel版本时,应该
优先选择LTS版本,除非有特殊原因,如生产服务器的PHP版本不是PHP7以上,而是PHP5.*,且为了稳定不升级到PHP7,那么
可以 考虑使用上一个版本的发行版。
比如Laravel 5.5是最新的LTS但是只支持PHP7以上,那么 可以
考虑使用Laravel 5.4。当然比较可以使用Laravel 5.1
LTS版本,但是该版本比较旧,没有新版本的一些新特性。

请使用以下命令来创建指定版本的 Laravel 项目:

composer create-project laravel/laravel project-name --prefer-dist "5.5.*" 

绝不 也禁止使用复制粘贴项目文件的方式来创建项目。

路由器

八. 环境说明

一般情况下,一个项目 应该 有以下三个基本的项目环境:

  • Local – 开发环境
  • Staging – 线上测试环境,对应git的test分支
  • Production – 线上生产环境,对应git的master分支

路由闭包

绝不
在路由配置文件里书写『闭包路由』或者其他业务逻辑代码,因为一旦使用将无法使用
路由缓存

路由器要保持干净整洁,绝不 放置除路由配置以外的其他程序逻辑。

九. git分支

在创建git仓库后,建议最好分开三个分支

  • 主分支 – master,对应开发环境
  • 测试分支 – test,对应线上测试环境
  • 开发分支 – develop,对应线上生产环境

所有功能都是从develop分支新建分支,按功能模块命名

  • 新的功能模块,使用 features/功能名称 来命名

  • 修复bug,使用 fix/bug名称 来命名

  • 功能开发后,合并到develop

  • 开发测试通过后,将develop合并到test分支

  • 测试环境功能测试通过后,将test合并到master

Restful 路由

必须 优先使用 Restful 路由,配合资源控制器使用,见
文档。

图片 1

Alt text

使用 resource 方法时,如果仅使用到部分路由,必须 使用 only
列出所有可用路由:

Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);

使用 except,对于新增方法没有保护作用,而 only
相当于白名单,相对于 except
更加直观。路由使用白名单有利于养成『安全习惯』。

十. 配置信息与环境变量

在 Laravel 中有以下几种方法:

  1. 硬代码,直接写死。- ❌ 可维护性低
  2. 写死在 config/app.php 文件中。 – ❌ 无法区分环境进行配置
  3. 存储于 .env 文件中,使用 env() 方法直接读取。 – ❌
    虽然解决了环境变量问题但是不推荐
  4. 存储在 .envconfig/app.php 文件中,然后使用 config()
    函数来读取。- ✅ 最佳实践

第一种方法是最古老的方法,代码可维护性极低,一旦域名变更就只能全局替换。
第二种方法无法区分环境,例如本地使用开发环境域名测试,线上才是正式的域名。
第三种方法虽然解决了环境变量的问题,并且也具备一定的灵活性,但是不够灵活,假如你的网站流量巨大,需要配置几个域名,使其在加载静态资源时随机支配域名,这种做法就无法满足需求了。
第四种方法既支持环境变量,又具备极高的灵活性,假如遇到同样的多域名随机问题,你只需要写一个辅助方法,然后在
config/app.php 中调用即可,不需要动到任何一行业务逻辑代码。

代码示例

.env 文件中设置:

DOMAIN=018eighteen.test

config/app.php 文件中设置:

'domain' => env('DOMAIN', '018eighteen.com'),

程序中两种获取 相同配置 的方法:

  1. env('DOMAIN')
  2. config('app.domain')

在此统一规定:所有程序配置信息 必须 通过 config() 来读取,所有的
.env 配置信息 必须 通过 config() 来读取,绝不
在配置文件以外的范围使用 env()

这样做主要有以下几个优势:

  1. 定义分明,config() 是配置信息,env() 只是用来区分不同环境;
  2. 统一放置于 config 中还可以利用框架的
    配置信息缓存功能
    来提高运行效率;
  3. 代码健壮性, config()env()
    之上多出来一个抽象层,会使代码更加健壮,更加灵活。

路由模型绑定

在允许使用路由 模型绑定 的地方 必须 使用。

模型绑定代码 必须 放置于 app/Providers/RouteServiceProvider.php
文件的 boot 方法中:

public function boot()
{
    Route::bind('user_name', function ($value) {
        return User::where('name', $value)->first();
    });

    Route::bind('photo', function ($value) {
        return Photo::find($value);
    });

    parent::boot();
}

十一. 路由器

全局路由器参数

出于安全考虑,应该 使用全局路由器参数限制,详见
文档。

必须RouteServiceProvider 文件的 boot 方法里定义模式:

/**
 * 定义你的路由模型绑定,模式过滤器等。
 *
 * @param  \Illuminate\Routing\Router  $router
 * @return void
 */
public function boot(Router $router)
{
    $router->pattern('id', '[0-9]+');

    parent::boot($router);
}

模式一旦被定义,便会自动应用到所有使用该参数名称的路由上:

Route::get('users/{id}', 'UsersController@show');
Route::get('photos/{id}', 'PhotosController@show');

只有在 id 为数字时,才会路由到控制器方法中,否则 404 错误。

1. 路由闭包

绝不
在路由配置文件里书写『闭包路由』或者其他业务逻辑代码,因为一旦使用将无法使用
路由缓存

路由器要保持干净整洁,绝不 放置除路由配置以外的其他程序逻辑。

路由命名

除了 resource 资源路由以外,其他所有路由都 必须 使用 name
方法进行命名。

Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');

2. Restful 路由

必须 优先使用 Restful 路由,配合资源控制器使用,见
文档。

图片 2

file

超出 Restful 路由的,应该 模仿上图的方式来定义路由。

数据模型

3. resource 方法正确使用

一般资源路由定义:

Route::resource('photos', 'PhotosController');

等于以下路由定义:

Route::get('/photos', 'PhotosController@index')->name('photos.index');
Route::get('/photos/create', 'PhotosController@create')->name('photos.create');
Route::post('/photos', 'PhotosController@store')->name('photos.store');
Route::get('/photos/{photo}', 'PhotosController@show')->name('photos.show');
Route::get('/photos/{photo}/edit', 'PhotosController@edit')->name('photos.edit');
Route::put('/photos/{photo}', 'PhotosController@update')->name('photos.update');
Route::delete('/photos/{photo}', 'PhotosController@destroy')->name('photos.destroy');

使用 resource 方法时,如果仅使用到部分路由,必须 使用 only
列出所有可用路由:

Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);

绝不 使用 except,因为 only 相当于白名单,相对于 except
更加直观。路由使用白名单有利于养成『安全习惯』。

放置位置

所有的数据模型文件,都 必须 存放在:app/Models/ 文件夹中。
命名空间:

namespace App\Models;

4. 单数 or 复数?

资源路由路由 URI 必须 使用复数形式,如:

  • /photos/create
  • /photos/{photo}

错误的例子如:

  • /photo/create
  • /photo/{photo}

忽略此块

User.php 初始化模型处理

Laravel 5.5 默认安装会把 User 模型存放在 app/User.php必须
移动到 app/Models 文件夹中,并修改命名空间声明为 App/Models
为了不破坏原有的逻辑点,必须 全局搜索 App/User 并替换为
App/Models/User

5. 路由命名

除了 resource 资源路由以外,其他所有路由都 必须 使用 name
方法进行命名。
必须 使用『资源前缀』作为命名规范,如下的
users.follow,资源前缀的值是 users.

Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');

使用基类

所有的 Eloquent 数据模型 都 必须 继承统一的基类
App/Models/Model,此基类存放位置为
/app/Models/Model.php,内容参考以下:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class Model extends EloquentModel
{
    public function scopeRecent($query)
    {
        return $query->orderBy('created_at', 'desc');
    }
}

以 Photo 数据模型作为例子继承 Model 基类:

namespace App\Models;

class Photo extends Model
{
    protected $fillable = ['id', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

6. 获取 URL

获取 URL 必须 遵循以下优先级:

  1. $model->link()
  2. route 方法
  3. url 方法

在 Model 中创建 link() 方法:

public function link($params = [])
{
    $params = array_merge([$this->id], $params);
    return route('models.show', $params);
}

所有单个模型数据链接使用:

$model->link();

// 或者添加参数
$model->link($params = ['source' => 'list'])

『单个模型 URI』经常会发生变化,这样做将会让程序更加灵活。

除了『单个模型 URI』,其他路由 必须 使用 route 来获取
URL(这也是目前使用次数最多的方法):

$url = route('profile', ['id' => 1]);

无法使用 route 的情况下,可以 使用 url 方法来获取 URL:

url('profile', [1]);

命名规范

基本命名规范:

  • 变量命名 必须 使用「Snake Case」写法,如:$user_id, $post_id
  • 方法命名 必须
    使用「驼峰」写法,并且首字母小写,如:getUserInfo
  • 类命名 必须 使用「驼峰」写法,并且首字母大写,如:UserInfo

数据模型相关的命名规范:

  • 数据模型类名 必须 为「单数」, 如:App\Models\Photo
  • 数据库表名字 必须 为「单数」,多个单词情况下使用「Snake Case」
    如:photo, my_photo
  • 数据库字段名 必须 为「Snake Case」,如:view_count, is_vip
  • 数据库表主键 必须 为「id」
  • 数据模型变量 必须 为「resource_id」,如:$user_id, $post_id

十二. 数据模型

利用 Trait 来扩展数据模型

有时候数据模型里的代码会变得很臃肿,应该 利用 Trait
来精简逻辑代码量,提高可读性

存放于文件夹:app/Models/Traits 文件夹中。

1. 放置位置

所有的数据模型文件,都 必须 存放在:app/Models/ 文件夹中。

命名空间:

namespace App\Models;

Repository

绝不 使用 Repository,因为我们不是在写 JAVA
代码,太多封装就成了「过度设计(Over
Designed)」,极大降低了编码愉悦感,使用 MVC 够傻够简单。

PS: 那么MVC MC 我们怎么定义呢?M* 写什么 C
写什么,对于一些公共的方法我们该写在哪里(Traits?)*

2. 使用基类

所有的 Eloquent 数据模型必须 继承统一的基类
App/Models/Model,此基类存放位置为
/app/Models/Model.php,内容参考以下:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class Model extends EloquentModel
{
    public function scopeRecent($query)
    {
        return $query->orderBy('created_at', 'desc');
    }
}

以 Photo 数据模型作为例子继承 Model 基类:

<?php

namespace App\Models;

class Photo extends Model
{
    protected $fillable = ['id', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

全局作用域

Laravel 的 Model
全局作用域
允许我们为给定模型的所有查询添加默认的条件约束。

所有的全局作用域都 必须 统一使用 闭包定义全局作用域,如下:

/**
 * 数据模型的启动方法
 *
 * @return void
 */
protected static function boot()
{
    parent::boot();

    static::addGlobalScope('age', function(Builder $builder) {
        $builder->where('age', '>', 200);
    });
}

3. 命名规范[#]

数据模型相关的命名规范:

  • 数据模型类名 必须 为「单数」, 如: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」(注:这条规范一定要严格执行,避免像018server的prouct表一样出现product_id这样的主键)
  • 数据库表外键 必须 为「resource_id」,如:user_id, post_id
  • 数据模型变量 必须 为「resource_id」,如:$user_id, $post_id

控制器

4. 利用 Trait 来扩展数据模型

有时候数据模型里的代码会变得很臃肿,应该 利用 Trait
来精简逻辑代码量,提高可读性,类似于 Ruby China
源码。

借鉴于 Rails 的设计理念:「Fat Models, Skinny Controllers」。

存放于文件夹:app/Models/Traits 文件夹中。

资源控制器

必须 使用资源的复数形式,如:

类名:PhotosController
文件名:PhotosController.php

错误的例子:

类名:PhotoController
文件名:PhotoController.php

5. Repository

在分享下团队的开发规范 ——《Laravel
项目开发规范》
提出不适用 Repository 模式进行开发,但是考虑到随着功能越来越多,不适用
Repository
会使得控制器越来越臃肿,有些代码也会不停地重复写,另外以后有可能需要编写单元测试,所以最后还是决定启用
Repository

具体参照为什么你应该使用
Repository

保持短小精炼

必须 保持控制器文件代码行数最小化,还有可读性。

  • 不应该
    为「方法」书写注释,这要求方法取名要足够合理,不需要过多注释;
  • 应该 为一些复杂的逻辑代码块书写注释,主要介绍产品逻辑 –
    为什么要这么做;
  • 不应该 在控制器中书写「私有方法」,控制器里 应该
    只存放「路由动作方法」;
  • 绝不
    遗留「死方法」,就是没有用到的方法,控制器里的所有方法,都应该被使用到,否则应该删除;
  • 绝不 在控制器里批量注释掉代码,无用的逻辑代码就必须清除掉。

6. 关于 SQL 文件

  • 绝不 使用命令行或者 PHPMyAdmin 直接创建索引或表。必须 使用
    数据库迁移
    去创建表结构,并提交版本控制器中;
  • 绝不 为了共享对数据库更改就直接导出 SQL,所有修改都 必须
    使用
    数据库迁移
    ,并提交版本控制器中;
  • 绝不 直接向数据库手动写入伪造的测试数据。必须 使用
    数据填充
    来插入假数据,并提交版本控制器中。

考虑到可能会和其他团队合作开发,所以具体还是根据团队的协定而定。但是如果是自己团队开发的话,必须严格按照以上标准。

表单验证

十三. 控制器

表单请求验证类

必须 使用 表单请求 – FormRequest 类 来处理控制器里的表单验证。

1. 资源控制器

必须 优先使用 Restful
资源控制器