介绍 Eloquent 关联中的多对多多态关联(Many To Many Polymorphic Relations)

上一篇 里,讲到一张评论表(comments)可以给文章表(posts)用,也可以给视频表(videos)用。如果你足够细心就会发现,文章和评论、视频和评论都是一对多的,这其实就是一对多多态关系

但是有没有多对多的多态关联呢?这是废话,题目中就已经暴露了——确实有的,比如:标签。一篇文章的标签是 JavaScript,但是一个视频的标签也可能是 JavaScript 啊。

文章和标签、视频和标签都是多对多的,这就是多对多多态关系,定义在它们 Model 中的关联就是多对多多态关联

下面就来实现。

创建表

创建 tags 表和 taggables 表。

php artisan make:model Models/Tag -m -c
/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('tags', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name')->unique();
        $table->string('slug')->unique();
    });

    DB::table('tags')->insert([
        ['name' => 'Laravel', 'slug' => 'laravel'],
        ['name' => 'Lumen', 'slug' => 'lumen'],
        ['name' => 'Spark', 'slug' => 'spark'],
        ['name' => 'Forge', 'slug' => 'Forge'],
        ['name' => 'Envoyer', 'slug' => 'envoyer'],
        ['name' => 'Homestead', 'slug' => 'homestead'],
        ['name' => 'Valet', 'slug' => 'valet'],
        ['name' => 'Socialite', 'slug' => 'socialite'],
        ['name' => 'Mix', 'slug' => 'mix'],
        ['name' => 'Dusk', 'slug' => 'dusk'],
    ]);
}
php artisan make:migration create_taggables_table --create=taggables
Schema::create('taggables', function (Blueprint $table) {
    $table->increments('id');
    $table->unsignedInteger('taggable_id');
    $table->string('taggable_type');
    $table->timestamps();
});

taggables 是多对多关系中的中间表/关系表。taggable_idtaggable_type上一篇 中的 commentable_idcommentable_type 两个字段的作用是一样一样的。

php artisan migrate

设定关联

Tag Model

class Tag extends Model
{
    protected $fillable = ['name', 'slug'];

    public $timestamps = false;

    /**
     * 获取该标签下的文章
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
     */
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    /**
     * 获取该标签下的视频
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
     */
    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

Post Model

/**
 * 取得文章标签
 *
 * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
 */
public function tags()
{
    return $this->morphToMany(Tag::class, 'taggable');
}

Video Model

/**
 * 取得视频标签
 *
 * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
 */
public function tags()
{
    return $this->morphToMany(Tag::class, 'taggable');
}

你会发现 Post Model 和 Video Model 中定义的 tags 方法是一样的,没错,包括后来的内容类型,在其 Model 中定义这个方法都是一样的。

所以,我们就把这个方法写成一个 trait。

<?php

namespace App\Helpers;

use App\Models\Tag;

trait HasTags
{
    /**
     * 取得爱谁谁的标签
     *
     * @return \Illuminate\Database\Eloquent\Relations\MorphToMany
     */
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

然后改写前面 Post Model 和 Video Model 的写法。

use App\Helpers\HasTags;

class Post extends Model
{
    use HasTags;

    ...
}

class Video extends Model
{
    use HasTags;

    ...
}

使用

php artisan tinker

>>> namespace App\Models;
>>> Post::find(1)->tags()->sync([1,2,3]);
=> [
     "attached" => [
       1,
       2,
       3,
     ],
     "detached" => [],
     "updated" => [],
   ]
>>> Post::find(1)->tags;
=> Illuminate\Database\Eloquent\Collection {#784
     all: [
       App\Models\Tag {#785
         id: 1,
         name: "Laravel",
         slug: "laravel",
         pivot: Illuminate\Database\Eloquent\Relations\MorphPivot {#782
           taggable_id: 1,
           tag_id: 1,
         },
       },
       App\Models\Tag {#786
         id: 2,
         name: "Lumen",
         slug: "lumen",
         pivot: Illuminate\Database\Eloquent\Relations\MorphPivot {#783
           taggable_id: 1,
           tag_id: 2,
         },
       },
       App\Models\Tag {#789
         id: 3,
         name: "Spark",
         slug: "spark",
         pivot: Illuminate\Database\Eloquent\Relations\MorphPivot {#778
           taggable_id: 1,
           tag_id: 3,
         },
       },
     ],
   }
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 5

创建taggables表时,主键应该是tag_id

6年前 评论

@mokecc 我照着试了下,taggables表里没有内容?而且从Tag::find(1)->posts, 没法看到内容。能帮忙解惑么

5年前 评论

请问
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
中taggable是神马含义,随便定义还是有什么约定。起什么作用?

5年前 评论
carlojie 3年前
passport4jd 3年前

弱弱的问一下:
php artisan make:model Models/Tag -m -c
中的"-c"表示什么意思?

4年前 评论
Jea 4年前

请问怎么能限制tag 显示的字段呢,

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!