模型规范

未匹配的标注

放置位置

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

命名空间:

namespace App\Models;

继承基类

所有的 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('id', 'desc');
    }

    public function scopeOlder($query)
    {
      return $query->orderBy('id', 'asc');
    }

    public function scopeByUser($query, User $user)
    {
      return $query->where('user_id', $user->id);
    }
}

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

<?php

namespace App\Models;

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

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

命名规范

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

  • 数据模型类名 必须 为「单数」, 如: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

模型关联

数据关联内部 必须 使用「resource_id」,假如 User 模型有 id 和 UUID 两个唯一字段,其他模型关联 User 必须 使用 id 字段。也就是在其他模型的数据表里,使用 user_id 字段。

利用 Trait 来扩展数据模型

模型间,相同的模型逻辑,例如 User 和 Topic 都有一个 settings JSON 字段,用来实现单个模型的设置功能,应该 利用 Trait 来实现逻辑代码。

所有模型 Traits 必须存放于app/Models/Traits 目录下。

注意: 业务逻辑请使用 ModelService 模式来组织代码。

Repository

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

代码的可读性,维护和开发的便捷性,直接关系到程序员开发时的愉悦感,直接影响到项目推进效率和程序 Debug 速度。

关于 SQL 文件

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

作用域

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

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

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

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

数据层无状态

先看一段代码,以下是 Post 模型里创建文章评论的方法:

    public function createComment($content)
    {
        return $this->comments()->create([
            'content' => $content,
            'user_id' => Auth::user()->id
        ]);
    }

注意 Auth::user()->id ,在数据层里使用当前登录用户状态,是默认假设这段代码永远是在 Web 用户请求下执行的。

然而事实并非如此,有时候你可能会在命令行下触发调用这个 createComment() 方法,有时候是管理员在后台触发,有时候是队列里触发。

一个最佳实践的做法是, 绝不 在数据层里使用用户登录状态信息。如果需要用户信息,必须 将其作为依赖进行传参,如以上代码可修改为:

    public function createComment($content, $user)
    {
        return $this->comments()->create([
            'content' => $content,
            'user_id' => $user->id
        ]);
    }

在有需要的地方调用时,以参数传入:

Post::createComment($content, Auth::user())

命令行书写某些特殊逻辑时,例如使用 1 号用户的身份创建评论:

Post::createComment($content, User::find(1))

数据层,也就是模型里,不能跟用户的登录状态挂钩。

目录分层

如果是一个长期维护的项目,必须 为模型文件按业务逻辑做分层。

一个长期维护的项目,很容易就会出现几十上百的表,每个表对应一个 Model 文件。笔者曾维护过一个项目,两百多个 Model 文件,app/models 目录完全没法看。

如果你能预期到 Model 文件会很多,那就 必须 做好目录划分,按照业务逻辑来分,以 LearnKu.com 为例,app/models 的目录结构如下:

├── Book
│   ├── Article.php
│   └── Book.php
├── Community
│   ├── Reply.php
│   └── Topic.php
└── Project
    ├── ProjectAuthor.php
    └── Project.php

模型事件

应该 尽量避免使用 Laravel 的 模型事件

使用模型事件的问题在于,其职能很难界定,所有的业务逻辑都能写到模型事件中。

而我们在项目中,业务逻辑代码规都封装到 Service 层,开发者在书写业务逻辑代码时,就会面临两种选择。

例如说 ReplyService 类的 create 方法,将创建评论时需要的逻辑,如发送通知给话题的作者,或者增加话题的评论数等操作,放置于此方法中,效果跟放在 ReplyObserver 中是一样的。

不一样的是, ReplyService 是显示地书写业务逻辑,代码可读性比模型事件更高。

模型事件另一个缺点就是,模型操作,附带太多逻辑,有太多的 Side Effect,并且是隐性的。模型操作是一个使用频率很高的功能,在有些场景中,你就想创建一个 Reply,但是不想通知到用户,例如说 Seed 时。虽然 Laravel 有提供模型方法让你暂时关闭模型事件,但这在实践中,我见过太多开发者经常会忘记此操作。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 0
发起讨论 查看所有版本


暂无话题~