关联关系不只是我之前记录的一对一,一对多,多对多这些相对简单的关系,在实际开发中我们会遇到比较复杂的关系。
远程一对多
远程一对多听着比较花哨 举个栗子就很清楚了,比如用户和文章是一对多的关系,国家和用户也是一对多的关系,这样看来 用户是可以作为中间关联对象来为国家和文章间建立一对多的关系,如果还是云里雾里 就直接看代码:
我们创建一个国家表:
php artisan make:migration create_countries_table --create=countries
public function up() { Schema::create(‘countries‘, function (Blueprint $table) { $table->increments(‘id‘); $table->string(‘name‘); $table->timestamps(); }); }
我们需要在user中在增加一列:
php artisan make:migration insert_country_id_intro_users --table=users
public function up() { Schema::table(‘users‘, function (Blueprint $table) { $table->integer(‘country_id‘)->unsigned(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table(‘users‘, function (Blueprint $table) { $table->dropColumn(‘country_id‘); }); }
生成表后生成模型:
php artisan migrate
php artisan make:model Country
在tinker中生成两条数据:
>>> $country = new App\Country(); => App\Country {#708} >>> $country->name = ‘China‘; => "China" >>> $country->save(); => true >>> $country2 = new App\Country(); => App\Country {#709} >>> $country2->name = ‘America‘; => "America" >>> $country2->save(); => true
post文章的东西在简单关联中已经生成过了,就不在这说了。
现在来搞一搞远程一对多,在Country中添加方法:
class Country extends Model { public function posts() { return $this->hasManyThrough(Post::class,User::class); } }
使用远程一对多方法hasManyThrough(),其中第一个参数是需要关联到的对象类名,第二个参数是中间关联对象类名。
如果users
表中表示用户对应国家的字段不是county_id
(假设为$country_id
),并且posts
表中表示文章所属用户的字段不是user_id
(假设为$user_id
),我们可以传递更多参数到hasManyThrough
方法
public function posts() { return $this->hasManyThrough(‘App\Models\Post‘,‘App\User‘,$country_id,$user_id); }
来看看测试代码:
Route::get(‘/‘, function () { $country = \App\Country::find(1); $posts = $country->posts; echo $country->name . ‘作者的文章有:‘ . ‘<br />‘; foreach ($posts as $post){ echo $post->title . ‘<br />‘; } });
多态关联
多态关联用一个很简单的例子就可以说清楚,比如评论,现在我们不只有文章这一个表了,还有一个视频表,用户可以评论文章也可以评论视频,当然我们还需要一张评论表,但是一条评论可以属于一篇文章 又可以属于一段视频,解决这个关系就需要用到多态关联,在评论表添加item_id字段来存储归属模型的ID,再添加一个item_type来存储归属模型的类型 如:App\Post或App\Video,来吧 上代码:
生成评论表和视频表 并自行添加数据:
Schema::create(‘comments‘, function (Blueprint $table) { $table->increments(‘id‘); $table->text(‘content‘); $table->integer(‘item_id‘)->unsigned(); $table->integer(‘user_id‘)->unsigned(); $table->string(‘item_type‘); $table->timestamps(); });
Schema::create(‘videos‘, function (Blueprint $table) { $table->increments(‘id‘); $table->string(‘title‘); $table->text(‘content‘); $table->text(‘desc‘); $table->integer(‘user_id‘)->unsigned(); $table->timestamps(); });
创建Post和Video模型 并定义这个方法:
public function comments() { return $this->morphMany(Comment::class, ‘item‘); }
其中第一个参数是关联模型类名,第二个参数是关联名称,即$item_id
和$item_type
中的$item
部分。当然也可以传递完整参数到morphMany
方法:
$this->morphMany(‘App\Models\Comment‘,$item,$item_type,$item_id,$id);
如果需要也可以在Comment
模型中定义相对的关联关系获取其所属节点:
public function item() { return $this->morphTo(); }
如果$item
部分不等于item
可以自定义传入参数到morphTo
:
$this->morphTo($item,$item_type,$item_id);
OK,完成 测试代码:
Route::get(‘/‘, function () { $video = \App\Video::find(1); echo $video->title . ‘所有的评论:‘. ‘<br />‘; foreach ($video->comments as $comment){ echo $comment->content . ‘<br />‘; } });
多对多多态关联
多态关联之后还有一个更加复杂的关联——多对多的多态关联,这种关联最常见的应用场景就是标签,比如一篇文章对应多个标签,一个视频也对应多个标签,同时一个标签可能对应多篇文章或多个视频,这就是所谓的“多对多多态关联”。此时仅仅在标签表tags
上定义一个item_id
和item_type
已经不够了,因为这个标签可能对应多个文章或视频,那么如何建立关联关系呢,我们可以通过一张中间表taggables
来实现:该表中定义了文章/视频与标签的对应关系。
我们创建tag表和其对应的模型类:
public function up() { Schema::create(‘tags‘, function (Blueprint $table) { $table->increments(‘id‘); $table->string(‘name‘); $table->timestamps(); }); }
创建taggables表和对应的模型:
public function up() { Schema::create(‘taggables‘, function (Blueprint $table) { $table->increments(‘id‘); // 对应着文章或视频的id $table->integer(‘taggable_id‘)->unsigned(); // 对应是文章类型还是视频类型 $table->string(‘taggable_type‘); // 对应是tag表的id $table->integer(‘tag_id‘)->unsigned(); $table->timestamps(); }); }
我们在tags表添加几条数据后继续。
我们在Post模型和Video模型中定义方法:
public function tags() { return $this->morphToMany(Tag::class,‘taggable‘); }
其中第一个参数是关联模型类名,第二个参数是关联关系名称,完整的参数列表如下:
$this->morphToMany(‘App\Models\Tag‘,‘taggable‘,‘taggable‘,‘taggable_id‘,‘tag_id‘,false);
其中第三个参数是对应关系表名,最后一个值若为true
,则查询的是关联对象本身,若为false
,查询的是关联对象与父模型的对应关系。
在Tag中定义相对应的关系:
public function posts() { return $this->morphedByMany(Post::class, ‘taggable‘); } public function videos() { return $this->morphedByMany(Video::class, ‘taggable‘); }
其中第一个参数是关联对象类名,第二个参数是关联关系名称,同理完整参数列表如下:
$this->morphedByMany(‘App\Models\Video‘,‘taggable‘,‘taggable‘,‘tag_id‘,‘taggable_id‘);
这样关联关系就已经对应好了,现在添加关联表taggable数据:
>>> $tag = App\Tag::find(1); => App\Tag {#718 id: 1, name: "php教程", created_at: "2017-03-30 13:05:07", updated_at: "2017-03-30 13:05:07", } >>> $post = App\Post::find(1); => App\Post {#715 id: 1, title: "Molestiae sit quos ut saepe nam ut itaque eos.", body: "Consequuntur odio dolores iure nihil distinctio. Sed neque eos aut voluptatem est sit quis quia. Inventore sint sint nesciunt libero dolores. Neque blanditiis sequi odio quia distinctio.", views: "0", user_id: 1, created_at: "2017-03-26 17:25:47", updated_at: "2017-03-26 17:25:47", } >>> $post->tags()->save($tag); => App\Tag {#718 id: 1, name: "php教程", created_at: "2017-03-30 13:05:07", updated_at: "2017-03-30 13:05:07", }
测试代码:
Route::get(‘/‘, function () { $post = App\Post::find(1); $tags = $post->tags; dd($tags); });