0%

Laravel Eloquent 模型关联关系(一)

前言

Laravel Eloquent 模型支持的关联关系包括以下其中:

  • 一对一
  • 一对多
  • 多对多
  • 远层一对多
  • 多态关联(一对一)
  • 多态关联(一对多)
  • 多态关联(多对多)

不着急,我们一个一个的看如何使用。

一对一

一对一是最简单的关联关系,一般可用于某个扩展表与主表之间的关联关系。比如大型系统中,我们的用户表通常用于最基本的信息的存储,如:邮箱、用户名、密码等放在主表,然后用户的身份证信息,个性签名放在另外一张扩展表。需要的时候才会去扩展表取数据,从而提高查询性能。

在开始之前,我们先通过数据迁移建立一张 user_profiles 表,并创建对应的 UserProfile,这可以通过 php artisan 命令一次完成:

1
php artisan make:model UserProfile -m

在生成的 create_user_profiles 迁移文件中编写迁移类的 up 方法如下:

1
/**
2
  * Run the migrations.
3
  *
4
  * @return void
5
  */
6
public function up()
7
{
8
    Schema::create('user_profiles', function (Blueprint $table) {
9
        $table->id();
10
        $table->bigInteger("user_id")->comment("用户ID");
11
        $table->string("card_number")->comment("身份证号码");
12
        $table->timestamps();
13
    });
14
}

注意:我们在 user_profiles 表中添加了 user_id 字段用于指向所属用户,从而建立与 users 表的关联。运行 php artisan migrate 在数据库创建这张数据表。

准备好数据表之后,接下来我们创建 users 表和 user_profiles 表之间的关联,首先,我们在 User 模型类中通过 hasOne 方法定义它与 UserProfile 的的一对一关联,User 类中加入 profile 方法

1
public function profile() {
2
    return $this->hasOne(UserProfile::class);
3
}

实际的使用时:

1
$user = User::first();
2
$user->profile;

hasOne 方法的签名

1
public function hasOne($related, $foreignKey = null, $localKey = null)
  • $related 是关联模型的类名
  • $foreignKey 是关联模型所属表的外键
  • $localKey 是关联表的外键关联到当前模型所属表的那个字段

建立相对的关联关系

通常我们都是通过 User 模型获取 UserProfile 模型,但是有时候我们可以需要反过来通过 UserProfile 反查询所属的 User 模型,Eloquent 底层也为我们提供了相应的 belongsTo 方法来建立一对一关联关系,我们在 UserProfile 关联模型定义它与 User 模型的关联如下:

给 UserProfile.php 中加入 user 方法:

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

实际的使用时:

1
$userProfile = UserProfile::first();
2
$userProfile->user;

belongsTo 方法的签名

hasOne 方法一样,belongsTo 方法也是遵循了默认的约定签名如下:

1
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null);
  • $related 是关联模型的类名
  • $foreignKey 是当前模型所属表的外键
  • $owerKey 是关联模型类所属表的主键
  • $relation 是关联关系方法名,也是关联关系动态属性名

一对多

一对多关联是我们日常开发中经常碰到的一种关联关系。以博客系统为例,一个用户可以发布多篇文章,反过来,一篇文章只能归属于一个用户,那么用户和文章之间就是一对多的关系,同样,用户可以发布多条评论,一条评论只能归属于一个用户,用户与评论之间也是一对多的关系。

创建 posts 表以及对应迁移文件

1
php artisan make:model Post -m

对应迁移文件中的 Up 方法:

1
public function up()
2
{
3
    Schema::create('posts', function (Blueprint $table) {
4
        $table->id();
5
        $table->bigInteger("user_id")->comment("所属用户id");
6
        $table->string('title')->comment("文章标题");
7
        $table->longText('content')->comment("文章内容");
8
        $table->bigInteger('view')->comment("查看次数");
9
        $table->timestamps();
10
        $table->softDeletes();
11
    });
12
}

创建 User 模型对应的 posts 方法

要定义用户文章之间的一对多关联,可以在 User 模型类中通过 Eloquent 底层的提供的 hasMany 方法来实现:

1
public function posts()
2
{
3
    return $this->hasMany(Post::class, "user_id", "id")
4
}

实际的使用时:

1
$user = User::first();
2
$user->posts;

hasMany 方法的签名

1
public function hasMany($related, $foreignKey = null, $localKey = null)
  • $related 是关联模型的类名
  • $foreignKey 是关联模型所属表的外键
  • $localKey 是关联表的外键关联到当前模型所属表的那个字段

建立相对的关联的关系

我们将 Post 模型中的关联关系调用方法名为 author,这样,我们就需要手动指定更多的 belongsTo 方法传入参数:

Post.php 中加入 author 方法:

1
public function author()
2
{
3
    return $this->belongsTo(User::class, 'user_id', 'id', 'author');
4
}

实际的使用时:

1
$post = Post::first();
2
$post->author;

多对多

多对多关联在实际开发中很常见,还是以博客系统为例子,我们会为每篇文章设置标签,一篇文章往往有多个标签,反过来,一个标签可能会归属于多篇文章,这时,我们说文章和标签之间是多对多的关联关系。

多对多关联比一对一和一对多关联复杂一些,需要借助一张中间表才能建立关联关系。以文章标签为例,文章表已经存在,还需要创建一张 tags 表和中间表 post_tags 表。首先创建 Tag 模型类及其对应数据表 tags 迁移文件:

1
php artisan make:model Tag -m

编写 create_tags_table 迁移文件对应的 up 方法如下:

1
public function up()
2
{
3
    Schema::create('tags', function (Blueprint $table) {
4
        $table->increments('id');
5
        $table->string('name', 100)->unique()->comment('标签名');
6
        $table->timestamps();
7
    });
8
}

然后创建 post_tags 数据表迁移文件:

1
php artisan make:migration create_post_tags_table --create=post_tags

编写对应迁移类的 up 方法如下:

1
public function up()
2
{
3
    Schema::create('post_tags', function (Blueprint $table) {
4
        $table->increments('id');
5
        $table->integer('post_id')->unsigned()->default(0);
6
        $table->integer('tag_id')->unsigned()->default(0);
7
        $table->unique(['post_id', 'tag_id']);
8
        $table->timestamps();
9
    });
10
}

运行 php artisan migrate 让迁移生效。

接下来,我们在 Post 模型类中定义其与 Tags 模型类的关联关系,通过 Eloquent 提供的 belongsToMany 方法来实现:

1
public function tags()
2
{
3
    return $this->belongsToMany(Tag::class, 'post_tags');
4
}

通过数据填充器填充一些数据到 tags 表和 post_tags 表,这样我们可以通过关联查询查询指定的 Post 模型上的标签信息了:

1
$post = Post::with('tags')->first();
2
$tags = $post->tags;

belongsToMany 方法的签名

可以看到我们在定义多对多关联的时候,也没有指定哪些字段进行关联,这样是遵循 Eloquent 底层默认约定的功劳, belongsToMany 方法签名如下:

1
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null)
  • $related 是关联模型的类名
  • $table 是建立多对多关联的中间表名,该表名默认凭借规则如下:
    • 当前模型+下划线+关联模型,这里是自定义的,默认转化是根据两个表名首字母排序之后,用下划线分割,如果默认的话,这里应该是post_tag,但是为了遵循 Laravel 表明为复数规定,我们自行定义一回。
  • $foreignPivotKey 是中间表中当前模型类的外键,默认拼接规则和前面一对一,一对多一样,所以本例中是 posts 表的 post_id 字段。
  • $relatedPivotKey 是中间表中当前模型类的外键,只不过作用于关联模型,所以本例中是tags表的tag_id字段。
  • $parentKey 表示对应当前模型哪个字段(即 $foreignPivotKey 映射到当前模型所属表的哪个字段),默认是主键 ID,即 posts 表的 id 字段,所以这里不需要额外指定。
  • $relatedKey 表示对应关联模型的哪个字段(即 $relatedPivotKey 映射到关联模型所属表的哪个字段),默认是关联模型的主键 ID,即 tags 表的 id 字段,所以这里也不需要额外指定。
  • $relation 表示关联关系名称,用于设置查询结果中的关联属性,默认是关联方法名。

建立相对的关联联系

与之前的关联关系一样,多对多关联也支持建立相对的关联关系,而且由于多对多的双方是平等的,不存在谁归属谁的问题,所以建立相对关联的方法都是一样的,我们可以在 Tag 模型中通过 belongsToMany 方法建立其与 Post 模型的关联关系:

1
public function posts()
2
{
3
    return $this->belongsToMany(Post::class, 'post_tags');
4
}

实际的使用时:

1
$tag = Tag::with('posts')->first();
2
$posts = $tag->posts;

获取中间表字段

1
$post = Post::first();
2
$post->tags;

Eloquent 还提供了方法允许你获取中间表的字段,你仔细查询结果字段,会发现 relations 字段中有一个 pivot 属性,中间表字段就存放在这个属性对象上。

中间表只有对应的中间表关联 id 没有时间戳,我们可以通过这个方式添加:

1
public function tags()
2
{
3
    return $this->belongsToMany(Tag::class, 'post_tags')->withTimestamps();
4
}

除了这样,你还在中间表定义了额外的字段信息,比如 user_id,通过 with 方法传入字段就将其返回了:

1
public function tags()
2
{
3
    return $this->belongsToMany(Tag::class, 'post_tags')->withPivot('user_id')->withTimestamps();
4
}

自定义中间表模型类

你可以通过自定义中间表对应模型类实现更多自定义操作,中间表模型继承自 Illuminate\Database\Eloquent\Relations\Pivot,Pivot 也是 Eloquent Model 类的子类,只不过为中间表操作定义了很多方法和属性,比如我们创建一个自定义的中间表模型类 PostTag:

1
namespace App;
2
3
use Illuminate\Database\Eloquent\Relations\Pivot;
4
5
class PostTag extends Pivot
6
{
7
    protected $table = 'post_tags';
8
}

这样,我们在定义多对多关联关系的时候指定自定义的模型类了:

1
public function tags()
2
{
3
    return $this->belongsToMany(Tag::class, 'post_tags')->using(PostTag::class);
4
}

更多中间表操作

此外,如果你觉得 pivot 可读性不好,你还可以自定义中间表实例属性名称:

1
$this->belongsToMany(Tag::class, 'post_tags')->as('taggable')->withTimestamps();

这样,就可以通过 $tag->taggable->created_at 访问中间表字段值了。

还可以通过中间表字段值过滤关联数据(支持 where 和 in 查询):

1
return $this->belongsToMany(Tag::class, 'post_tags')->wherePivot('user_id', 1);
2
return $this->belongsToMany(Tag::class, 'post_tags')->wherePivotIn('user_id', [1, 2]);

总结

这里只是说了一个基础,更多高级的在后面呈现给大家。