關聯關系不只是我之前記錄的一對一,一對多,多對多這些相對簡單的關系,在實際開發中我們會遇到比較復雜的關系。
遠程一對多
遠程一對多聽着比較花哨 舉個栗子就很清楚了,比如用戶和文章是一對多的關系,國家和用戶也是一對多的關系,這樣看來 用戶是可以作為中間關聯對象來為國家和文章間建立一對多的關系,如果還是雲里霧里 就直接看代碼:
我們創建一個國家表:
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); });