laravel 基礎(chǔ)教程 —— 關(guān)聯(lián)模型

Eloquent: 關(guān)聯(lián)模型

簡(jiǎn)介

數(shù)據(jù)庫(kù)中的表經(jīng)常性的關(guān)聯(lián)其它的表。比如,一個(gè)博客文章可以有很多的評(píng)論,或者一個(gè)訂單會(huì)關(guān)聯(lián)一個(gè)用戶(hù)。Eloquent 使管理和協(xié)作這些關(guān)系變的非常的容易,并且支持多種不同類(lèi)型的關(guān)聯(lián):

  • 一對(duì)一
  • 一對(duì)多
  • 多對(duì)多
  • 遠(yuǎn)程一對(duì)多
  • 多態(tài)關(guān)聯(lián)
  • 多態(tài)多對(duì)多關(guān)聯(lián)

定義關(guān)聯(lián)

Eloquent 關(guān)聯(lián)可以像定義方法一樣在 Eloquent 模型類(lèi)中進(jìn)行定義。同時(shí),它就像 Eloquent 模型自身一樣也提供了強(qiáng)大的查詢(xún)生成器。這允許關(guān)聯(lián)模型可以鏈?zhǔn)降膱?zhí)行查詢(xún)能力。比如:

$user->posts()->where('active', 1)->get();

但是,在更深入的使用關(guān)聯(lián)之前,讓我們先來(lái)學(xué)習(xí)一下如何定義各種類(lèi)型的關(guān)聯(lián)。

一對(duì)一

一對(duì)一的關(guān)聯(lián)是最基礎(chǔ)的關(guān)聯(lián)。比如,一個(gè) User 模型可能關(guān)聯(lián)一個(gè) Phone。我們需要在 User 模型上放置一個(gè) phone 方法來(lái)定義這種關(guān)聯(lián)。phone 方法應(yīng)該返回一個(gè)基類(lèi) Eloquent 模型上 hasOne 方法的結(jié)果:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * Get the phone record associated with the user.
   */
  public function phone()
  {
    return $this->hasOne('App\Phone');
  }
}

傳遞到 hasOne 方法的第一個(gè)參數(shù)應(yīng)該是關(guān)聯(lián)模型的名稱(chēng)。一旦關(guān)聯(lián)被定義完成,我們可以使用 Eloquent 的動(dòng)態(tài)屬性來(lái)訪(fǎng)問(wèn)關(guān)聯(lián)模型的記錄。動(dòng)態(tài)屬性允許你訪(fǎng)問(wèn)關(guān)聯(lián)函數(shù),就像是它們是定義在模型中的屬性一樣:

$phone = User::find(1)->phone;

Eloquent 假定所關(guān)聯(lián)的外鍵是基于模型的名稱(chēng)的。在這個(gè)前提下,Phone 模型會(huì)自動(dòng)的假定其擁有一個(gè) user_id 外鍵。如果你希望修改這個(gè)慣例,你可以傳遞第二個(gè)參數(shù)到 hasOne 方法中:

return $this->hasOne('App\Phone', 'foreign_key');

另外,Eloquent 也會(huì)假定外鍵應(yīng)該在其上層模型上擁有一個(gè)匹配的 id(或者自定義的 $primaryKey)值。換句話(huà)說(shuō),Eloquent 會(huì)查詢(xún) Phone 記錄中的 user_id 列所對(duì)應(yīng)的用戶(hù)的 id 列的記錄。如果你希望關(guān)聯(lián)使用 id 以外的值,你可以傳遞第三個(gè)參數(shù)到 hasOne 方法來(lái)指定自定義的鍵:

return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

定義相對(duì)的關(guān)聯(lián)

那么,我們可以從我們的 User 中訪(fǎng)問(wèn) Phone 模型。現(xiàn)在,讓我們?cè)?Phone 模型上定義一個(gè)關(guān)聯(lián),讓我們可以從 Phone 模型中訪(fǎng)問(wèn)其所屬的 User。我們使用 belongsTo 方法來(lái)定義 hasOne 相對(duì)的關(guān)聯(lián):

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Phone extends Model
{
  /**
   * Get the user that owns the phone.
   */
  public function user()
  {
    return $this->belongsTo('App\User');
  }
}

在上面的例子中,Eloquent 將會(huì)嘗試從 Phone 模型中的 user_id 字段中匹配查找 id 相同的 User。Eloquent 會(huì)依據(jù)所關(guān)聯(lián)的模型的蛇形命名和 _id 來(lái)假定默認(rèn)的外鍵名。事實(shí)上,如果在 Phone 模型上的外鍵不是 user_id,那么你可以傳遞自定義的外鍵名到 belongsTo 方法的第二個(gè)參數(shù):

/**
 * Get the user that owns the phone.
 */
public function user()
{
  return $this->belongsTo('App\User', 'foreign_key');
}

如果你的上級(jí)模型并沒(méi)有使用 id 作為主鍵名,或者你希望下級(jí)模型關(guān)聯(lián)一個(gè)不同的列。你可以傳遞第三個(gè)參數(shù)到 belongsTo 方法來(lái)指定上級(jí)模型表中的自定義鍵:

/**
 * Get the user that owns the phone.
 */
public function user()
{
  return $this->belongsTo('App\User', 'foreign_key', 'other_key');
}

一對(duì)多

一個(gè)一對(duì)多的關(guān)聯(lián)常常用來(lái)定義一個(gè)模型擁有其他任意數(shù)目的模型。比如,一個(gè)博客文章可以擁有很多條評(píng)論。就像其他的 Eloquent 關(guān)聯(lián)一樣,一對(duì)多關(guān)聯(lián)在 Eloquent 模型中通過(guò)方法來(lái)進(jìn)行定義:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  /**
   * Get the comments for the blog post.
   */
  public function comments()
  {
    return $this->hasMany('App\Comment');
  }
}

記住,Eloquent 會(huì)自動(dòng)的根據(jù) Comment 模型來(lái)判斷合適的外鍵。依據(jù)慣例,Eloquent 會(huì)使用自身模型的蛇形命名和 _id 來(lái)作為外鍵。所以,在這個(gè)例子中,Eloquent 會(huì)假定 Comment 模型的外鍵是 post_id

一旦關(guān)聯(lián)定義完成之后,我們可以通過(guò) comments 屬性來(lái)訪(fǎng)問(wèn)所有關(guān)聯(lián)的評(píng)論的集合。記住,由于 Eloquent 提供了動(dòng)態(tài)屬性,我們可以對(duì)關(guān)聯(lián)函數(shù)進(jìn)行訪(fǎng)問(wèn),就像他們是在模型中定義的屬性一樣:

$comments = App\Post::find(1)->comments;

foreach ($comments as $comment) {
  //
}

當(dāng)然,由于所有的關(guān)聯(lián)都提供了查詢(xún)生成器的功能,所以你可以在調(diào)用 comments 方法時(shí)繼續(xù)的添加一些限制條件,你可以通過(guò)鏈?zhǔn)降恼{(diào)用進(jìn)行查詢(xún)條件的添加:

$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();

就像 hasOne 方法,你可以通過(guò)添加額外的參數(shù)到 hasMany 方法中來(lái)重置外鍵和主鍵:

return $this->hasMany('App\Comment', 'foreign_key');

return $this->hasMany('App\Comment', 'foreign_key', 'local_key');

定義相對(duì)的關(guān)聯(lián)

現(xiàn)在我們可以訪(fǎng)問(wèn)文章中所有的評(píng)論了,讓我們?yōu)樵u(píng)論定義一個(gè)關(guān)聯(lián)使其可以訪(fǎng)問(wèn)它的上層文章模型。為了定義一個(gè) hasMany 相對(duì)的關(guān)聯(lián),你需要在下層模型中定義一個(gè)關(guān)聯(lián)方法并調(diào)用 belongsTo 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
  /**
   * Get the post that owns the comment.
   */
  public function post()
  {
    return $this->belongsTo('App\Post');
  }
}

一旦關(guān)聯(lián)被定義完成,我們就可以通過(guò) Comment 模型的 post 動(dòng)態(tài)屬性來(lái)檢索到其對(duì)應(yīng)的 Post 模型:

$comment = App\Comment::find(1);

echo $comment->post->title;

在上面的例子中,Eloquent 會(huì)嘗試從 Comment 模型中的 post_id 字段檢索與其相對(duì)應(yīng) idPost 模型。Eloquent 會(huì)使用關(guān)聯(lián)模型的蛇形命名和 _id 來(lái)作為默認(rèn)的外鍵。如果 Comment 模型的外鍵不是 post_id,你可以傳遞一個(gè)自定義的鍵名到 belongsTo 方法的第二個(gè)參數(shù):

/**
 * Get the post that owns the comment.
 */
public function post()
{
  return $this->belongsTo('App\Post', 'foreign_key');
}

如果上層模型并沒(méi)有使用 id 作為主鍵,或者你想在下層模型中關(guān)聯(lián)其他的列,你可以傳遞第三個(gè)參數(shù)到 belongsTo 方法中:

/**
 * Get the post that owns the comment.
 */
public function post()
{
  return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}

多對(duì)多

多對(duì)多的關(guān)聯(lián)比 hasOnehasMany 關(guān)聯(lián)要稍微復(fù)雜一些。假如一個(gè)用戶(hù)擁有多個(gè)角色,而角色又可以被其他的用戶(hù)所共享。比如,多個(gè)用戶(hù)可以擁有管理員的角色。如果定義這種關(guān)聯(lián),我們需要定義三個(gè)數(shù)據(jù)庫(kù)表:usersroles,和 role_userrole_user 表的命名是以相關(guān)聯(lián)的兩個(gè)模型數(shù)據(jù)表來(lái)依照字母順序命名,并且表中包含了 user_idrole_id 列。

多對(duì)多關(guān)聯(lián)需要編寫(xiě)一個(gè)方法調(diào)用基礎(chǔ) Eloquent 類(lèi) belongsToMany 方法。比如,讓我們?cè)?User 模型中定義一個(gè) roles 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * The roles that belong to the user.
   */
  public function roles()
  {
    return $this->belongsToMany('App\Role');
  }
}

一旦關(guān)聯(lián)被定義,你可以通過(guò) roles 動(dòng)態(tài)屬性來(lái)訪(fǎng)問(wèn)用戶(hù)的角色:

$user = App\User::find(1);

foreach ($user->roles as $role) {
  //
}

當(dāng)然,就像其他類(lèi)型的關(guān)聯(lián),你可以調(diào)用 roles 方法并且鏈?zhǔn)秸{(diào)用查詢(xún)條件:

$roles = App\User::find(1)->roles()->orderBy('name')->get();

就如先前所提到的,Eloquent 會(huì)合并兩個(gè)關(guān)聯(lián)模型并依照字母順序進(jìn)行命名。當(dāng)然你也可以隨意的重寫(xiě)這個(gè)約定,你可以傳遞第二個(gè)參數(shù)到 belongsToMany 方法:

return $this->belongsToMany('App\Role', 'role_user');

除了自定義合并數(shù)據(jù)表的名稱(chēng)之外,你也可以通過(guò)往 belongsToMany 方法傳傳遞額外參數(shù)來(lái)自定義數(shù)據(jù)表里的鍵的字段名稱(chēng)。第三個(gè)參數(shù)是你定義在關(guān)聯(lián)中模型外鍵的名稱(chēng)。第四個(gè)參數(shù)則是你要合并的模型外鍵的名稱(chēng):

return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');

定義相對(duì)關(guān)聯(lián)

你只需要在相對(duì)應(yīng)的關(guān)聯(lián)模型里放置其他的方法來(lái)調(diào)用 belongsToMany 方法就可以定義相對(duì)關(guān)聯(lián)。繼續(xù)我們上面的用戶(hù)角色示例,讓我們?cè)?Role 模型中定義一個(gè) users 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
  /**
   * The users that belongs to the role.
   */
  public function users()
  {
    return $this->belongsToMany('App\User');
  }
}

就如你所看到的,這個(gè)關(guān)聯(lián)的定義與用戶(hù)的關(guān)聯(lián)定義完全相同。因?yàn)槲覀冎貜?fù)的使用了 belongsToMany 方法,當(dāng)定義相對(duì)于多對(duì)多的關(guān)聯(lián)時(shí),所有常用的自定義數(shù)據(jù)表和鍵的選項(xiàng)都是可用的。

檢索中間表字段

正如你已經(jīng)了解到的。定義多對(duì)多的關(guān)聯(lián)需要引入一個(gè)中間表。Eloquent 提供了幾種非常有幫助的方式來(lái)與這個(gè)表進(jìn)行交互。比如,讓我們假定我們的 User 對(duì)象關(guān)聯(lián)到了很多 Role 對(duì)象。在訪(fǎng)問(wèn)這些關(guān)聯(lián)對(duì)象時(shí),我們可以通過(guò)在模型上使用 pivot 屬性來(lái)訪(fǎng)問(wèn)中間表:

$user = App\User::find(1);

foreach ($user->roles as $role) {
  echo $role->pivot->created_at;
}

注意我們?nèi)〕龅拿總€(gè) Role 對(duì)象,都會(huì)被自動(dòng)的分配 pivot 屬性。這個(gè)屬性包含了一個(gè)代表中間表的模型,并且可以像其他 Eloquent 模型一樣被使用。

默認(rèn)的,只有模型的鍵會(huì)被 pivot 對(duì)象提供,如果你的中間表包含了額外的屬性,你必須在定義關(guān)聯(lián)時(shí)指定它們:

return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

如果你想要中間表自動(dòng)維護(hù) created_atupdated_at 時(shí)間戳,你可以在定義關(guān)聯(lián)時(shí)使用 withTimestamps 方法:

return $this->belongsToMany('App\Role')->withTimestamps();

通過(guò)中間表字段過(guò)濾關(guān)系

你可以通過(guò)在定義關(guān)聯(lián)時(shí)使用 wherePrivotwherePivotIn 方法來(lái)在返回的結(jié)果中進(jìn)行過(guò)濾:

return $this->belongsToMany('App\Role')->wherePivot('approved', 1);

return $this->belongsToMany('App\Role')->wherePivotIn('approved', [1, 2]);

遠(yuǎn)程一對(duì)多

遠(yuǎn)程一對(duì)多關(guān)聯(lián)提供了簡(jiǎn)短便捷的方法通過(guò)中間關(guān)聯(lián)件來(lái)訪(fǎng)問(wèn)遠(yuǎn)端的關(guān)聯(lián)。比如,一個(gè) Country 模型應(yīng)該通過(guò) User 模型可以擁有很多的 Post 模型。在這個(gè)例子中,你可以非常容易的就檢索出一個(gè)國(guó)家中的所有的文章。讓我們來(lái)看一下定義這些關(guān)聯(lián)所需要的表:

countries
  id - integer
  name - string

users
  id - integer
  country_id - integer
  name - string

posts
  id - integer
  user_id - integer
  title - string

遠(yuǎn)端的 posts 并沒(méi)有包含 country_id 列,hasManyThrough 關(guān)聯(lián)可以通過(guò) $country->posts 來(lái)訪(fǎng)問(wèn)一個(gè)國(guó)家的文章。為了執(zhí)行這個(gè)查詢(xún),Eloquent 會(huì)通過(guò)中間表 userscountry_id 來(lái)檢索 posts 表中用戶(hù) ID 相匹配的記錄。

現(xiàn)在我們已經(jīng)明確了關(guān)聯(lián)表的結(jié)構(gòu),那么讓我們來(lái)在 Country 模型上定義關(guān)聯(lián):

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Country extends Model
{
  /**
   * Get all of the posts for the country.
   */
  public function posts()
  {
    return $this->hasManyThrough('App\Post', 'App\User');
  }
}

傳遞到 hasManyThrough 方法的第一個(gè)參數(shù)是我們最終想要訪(fǎng)問(wèn)到的模型,而第二個(gè)參數(shù)則是中間層的模型名稱(chēng)。

當(dāng)使用關(guān)聯(lián)查詢(xún)時(shí),通常 Eloquent 會(huì)遵循外鍵約定。如果你希望對(duì)關(guān)聯(lián)的鍵進(jìn)行自定義,你可以傳遞第三和第四個(gè)參數(shù)到 hasManyThrough 方法。第三個(gè)參數(shù)是中間層模型的外鍵名稱(chēng),第四個(gè)參數(shù)是最終想要獲取的模型中的所對(duì)應(yīng)的中間層的外鍵, 而第五個(gè)參數(shù)則是當(dāng)前模型的主鍵:

class Country extends Model
{
  public function posts()
  {
    return $this->hasManyThrough(
      'App\Post', 'App\User',
      'country_id', 'user_id', 'id'
    );
  }
}

多態(tài)關(guān)聯(lián)

表結(jié)構(gòu)

多態(tài)關(guān)聯(lián)允許一個(gè)模型在單個(gè)關(guān)聯(lián)中從屬一個(gè)或多個(gè)其它模型。比如,想象一下應(yīng)用中的用戶(hù)可以喜歡文章及其評(píng)論。如果使用多態(tài)關(guān)聯(lián),那么你就可以使用一個(gè)單獨(dú)的 likes 表來(lái)關(guān)聯(lián)這兩個(gè)場(chǎng)景。首先,讓我們確定定義這種關(guān)聯(lián)所需要的表結(jié)構(gòu):

posts
  id - integer
  title - string
  body - text

comments
  id - integer
  post_id - integer
  body - text

likes
  id - integer
  likeable_id - integer
  likeable_type - string

你需要注意到的兩個(gè)在 likes 表中重要的字段 likeable_idlikeable_typelikeable_id 字段會(huì)包含文章或者評(píng)論的 ID 值,而 likeable_type 字段會(huì)包含其所屬的模型的類(lèi)名。likeable_type 就是當(dāng)訪(fǎng)問(wèn) likeable 關(guān)聯(lián)時(shí) ORM 用來(lái)判斷所屬的模型是哪個(gè)類(lèi)型。

模型結(jié)構(gòu)

接著,讓我們檢查一下這個(gè)關(guān)聯(lián)所需要的模型定義:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class like extends Model
{
  /**
   * Get all of the owning likeable models.
   */
  public function likeable()
  {
    return $this->morphTo();
  }
}

class Post extends Model
{
  /**
   * Get all of the post's likes.
   */
  public function likes()
  {
    return $this->morphMany('App\Like', 'likeable');
  }
}

class Comment extends Model
{
  /**
   * Get all of the comment's likes.
   */
  public function likes()
  {
    return $this->morphMany('App\Like', 'likeable');
  }
}

獲取多態(tài)關(guān)聯(lián)

一旦數(shù)據(jù)庫(kù)表和模型都定義完成,你就可以在你的模型中訪(fǎng)問(wèn)這些關(guān)聯(lián)。比如,你可以使用 likes 動(dòng)態(tài)屬性來(lái)訪(fǎng)問(wèn)文章中所有關(guān)聯(lián)的 likes 模型:

$post = App\Post::find(1);

foreach ($post->likes as $like) {
  //
}

你也可以通過(guò)在模型上調(diào)用提供 morphTo 的方法來(lái)獲取多態(tài)模型其關(guān)系所有者。在上面的例子中,指的就是 Like 模型中的 likeable 方法。所以,我們可以像使用動(dòng)態(tài)屬性一樣使用方法來(lái)進(jìn)行訪(fǎng)問(wèn):

$like = App\Like::find(1);

$likeable = $like->likeable;

Like 模型的 likeable 關(guān)聯(lián)將會(huì)返回一個(gè) Post 或者 Comment 實(shí)例,這取決于其所屬者的類(lèi)型。

自定義多態(tài)類(lèi)型

默認(rèn)的,Laravel 會(huì)使用包完全限定類(lèi)名來(lái)存儲(chǔ)所關(guān)聯(lián)模型的類(lèi)型。比如,上面的例子中 Like 可以屬于 Post 或者 Comment。默認(rèn)的 likeable_type 應(yīng)該是 App\Post 或者 App\Comment。事實(shí)上,你可能希望從你的應(yīng)用程序的內(nèi)部結(jié)構(gòu)分離數(shù)據(jù)庫(kù)。在這個(gè)例子中,你可以定義一個(gè)關(guān)聯(lián)的多態(tài)映射來(lái)指導(dǎo) Eloquent 使用模型關(guān)聯(lián)的表名稱(chēng)來(lái)替代類(lèi)名:

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
  App\Post::class,
  App\Comment::class,
]);

或者,你可以指定一個(gè)自定的字符串與每個(gè)模型進(jìn)行關(guān)聯(lián):

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
  'posts' => App\Post::class,
  'likes' => App\Like::class,
]);

你可以在你的 AppServiceProvider 或者一個(gè)分離的服務(wù)提供者的 boot 方法中注冊(cè)你的 morphMap

多態(tài)多對(duì)多關(guān)聯(lián)

表結(jié)構(gòu)

除了傳統(tǒng)的多態(tài)關(guān)聯(lián),你也可以定義多對(duì)多的多態(tài)關(guān)聯(lián)。比如,一個(gè)博客的 PostVideo 模型應(yīng)該可以共享一個(gè)多態(tài)關(guān)聯(lián)的 Tag 模型。使用多對(duì)多的多態(tài)關(guān)聯(lián)可以允許你的博客文章和視頻能夠共享獨(dú)特標(biāo)簽的單個(gè)列表。首先,讓我們來(lái)看一下表結(jié)構(gòu):

posts
  id - integer
  name - string

videos
  id - integer
  name - string

tags
  id - integer
  name - string

taggables
  tag_id - integer
  taggable_id - integer
  taggable_type - string

模型結(jié)構(gòu)

接著,我們來(lái)定義模型中的關(guān)聯(lián)。PostVideo 模型將都會(huì)包含調(diào)用基礎(chǔ) Eloquent 類(lèi)的 morphToMany 方法的 tags 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
  /**
   * Get all of the tags for the post.
   */
  public function tags()
  {
    return $this->morphToMany('App\Tag', 'taggable');
  }
}

定義相對(duì)的關(guān)聯(lián)

接著,在 Tag 模型中,你應(yīng)該為所有關(guān)聯(lián)模型定義相應(yīng)的方法。所以,在這個(gè)例子中,我們將定義 posts 方法和 videos 方法:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Tag extends Model
{
  /**
   * Get all of the posts that are assigned this tag.
   */
  public function posts()
  {
    return $this->morphedByMany('App\Post', 'taggable');
  }

  /**
   * Get all of the videos that are assigned this tag.
   */
  public function videos()
  {
    return $this->morphedByMany('App\Video', 'taggable');
  }
}

獲取關(guān)聯(lián)

當(dāng)定義完成數(shù)據(jù)表和模型之后,你就可以通過(guò)模型來(lái)訪(fǎng)問(wèn)其關(guān)聯(lián)。比如,你可以簡(jiǎn)單的使用 tags 動(dòng)態(tài)屬性來(lái)訪(fǎng)問(wèn)文章的所有標(biāo)簽?zāi)P停?/p>

$post = App\Post::find(1);

foreach ($post->tags as $tag) {
  //
}

你也可以通過(guò)訪(fǎng)問(wèn)模型中提供執(zhí)行 morphedByMany 方法的方法來(lái)獲取關(guān)聯(lián)模型的所屬模型。在上面的例子中,就是 Tag 模型上的 posts 或者 videos 方法。所以,你可以像動(dòng)態(tài)屬性一樣訪(fǎng)問(wèn)這些方法:

$tab = App\Tag::find(1);

foreach ($tag->videos as $video) {
  //
}

關(guān)聯(lián)查詢(xún)

由于所有的 Eloquent 關(guān)聯(lián)類(lèi)型都是通過(guò)方法定義的,所以你可以調(diào)用這些方法來(lái)獲取所關(guān)聯(lián)的模型的實(shí)例而無(wú)需實(shí)際的執(zhí)行關(guān)聯(lián)查詢(xún)。另外,所有的 Eloquent 關(guān)聯(lián)也都提供了查詢(xún)生成器服務(wù),這允許你可以繼續(xù)的鏈?zhǔn)綀?zhí)行查詢(xún)操作。

比如,想象一下博客系統(tǒng)中 User 模型擁有很多 Post 關(guān)聯(lián)的模型:

<?ph

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  /**
   * Get all of the posts for the user.
   */
  public function posts()
  {
    return $this->hasMany('App\Post');
  }
}

你可以查詢(xún) posts 關(guān)聯(lián)的同時(shí)添加一些額外的查詢(xún)約束:

$user = App\User::find(1);

$user->posts()->where('active', 1)->get();

你應(yīng)該注意到了,你可以在關(guān)聯(lián)中使用任何的查詢(xún)生成器的方法。

關(guān)聯(lián)方法 Vs. 動(dòng)態(tài)屬性

如果你不需要在進(jìn)行 Eloquent 關(guān)聯(lián)查詢(xún)時(shí)添加額外的約束,你可以簡(jiǎn)單的像它的屬性一樣進(jìn)行訪(fǎng)問(wèn)。比如,我們繼續(xù)使用 UserPost 示例模型。我們可以像這樣來(lái)訪(fǎng)問(wèn)用戶(hù)的所有文章:

$user = App\User::find(1);

foreach ($user->posts as $post) {
  //
}

動(dòng)態(tài)屬性是惰性加載的,這意味著在你實(shí)際訪(fǎng)問(wèn)他們之前,其關(guān)聯(lián)數(shù)據(jù)是不會(huì)加載的。正因?yàn)槿绱耍_(kāi)發(fā)的時(shí)候通常使用預(yù)加載來(lái)進(jìn)行加載一些即將用到的關(guān)聯(lián)模型。預(yù)加載要求必須加載一個(gè)模型的關(guān)系,這有效的減少了查詢(xún)的次數(shù)。

查詢(xún)關(guān)聯(lián)是否存在

當(dāng)訪(fǎng)問(wèn)一個(gè)模型的記錄時(shí),你可能會(huì)希望基于關(guān)聯(lián)的記錄是否存在來(lái)對(duì)結(jié)果進(jìn)行限制。比如,想象一下你希望獲取最少有一條評(píng)論的博客文章。你可以傳遞關(guān)聯(lián)的名稱(chēng)到 has 方法來(lái)做這些:

// Retrieve all posts that have at least one comment...
$posts = App\Post::has('comments')->get();

你也可以指定操作符,和數(shù)量來(lái)進(jìn)一步定制查詢(xún):

// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();

你也可以使用 . 語(yǔ)法來(lái)構(gòu)造嵌套的 has 語(yǔ)句。比如,你可以獲取所有包含至少一條評(píng)論和投票的文章:

// Retrieve all posts that hava at least one comment with votes...
$posts = Post::has('comments.votes')->get();

如果你需要更高的控制,你可以使用 whereHasorWhereHas 方法來(lái)在 has 查詢(xún)中插入 where 子句。這些方法允許你為關(guān)聯(lián)進(jìn)行自定義的約束查詢(xún)。比如檢查評(píng)論的內(nèi)容:

// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
  $query->where('content', 'like', 'foo%'); 
})->get();

統(tǒng)計(jì)關(guān)聯(lián)結(jié)果

如果你希望統(tǒng)計(jì)關(guān)聯(lián)的結(jié)果而不實(shí)際的加載它們,你可以使用 withCount 方法,這將在你的結(jié)果模型中添加 {relation}_count 列。比如:

$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
  echo $post->comments_count;
}

你也可以同時(shí)檢索多個(gè)關(guān)聯(lián)的統(tǒng)計(jì),以及添加查詢(xún)約束:

$posts = Post::withCount(['votes', 'comments' => function ($query) {
  $query->where('content', 'like', 'foo%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

預(yù)加載

當(dāng)通過(guò)屬性訪(fǎng)問(wèn) Eloquent 關(guān)聯(lián)時(shí),該關(guān)聯(lián)的數(shù)據(jù)會(huì)被延遲加載。這意味著該關(guān)聯(lián)數(shù)據(jù)只有在你真實(shí)的訪(fǎng)問(wèn)屬性時(shí)才會(huì)進(jìn)行加載。事實(shí)上,Eloquent 可以在上層模型中一次性預(yù)加載的。預(yù)加載有效避免了 N + 1 的查找問(wèn)題。要說(shuō)明 N + 1 查找問(wèn)題,我們可以來(lái)看一個(gè) Author 關(guān)聯(lián) Book 的示例:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
  /**
   * Get the author that wrote the book.
   */
  public function author()
  {
    return $this->belongsTo('App\Author');
  }
}

現(xiàn)在,讓我們檢索所有的書(shū)籍和他們的作者:

$books = App\Book::all();

foreach ($books as $book) {
  echo $book->author->name;
}

這個(gè)循環(huán)會(huì)執(zhí)行一次查找回所有的書(shū)籍,接著每本書(shū)會(huì)運(yùn)行一次查找作者的操作。所以,如果我們擁有 25 本書(shū),那么循環(huán)將會(huì)進(jìn)行 26 次查詢(xún):1 次查詢(xún)所有的書(shū)籍,25 次查詢(xún)相關(guān)書(shū)籍的作者。

非常幸運(yùn)的,我們可以使用預(yù)加載來(lái)將查詢(xún)有效的控制在 2 次。當(dāng)查詢(xún)時(shí),使用 with 方法來(lái)指定關(guān)聯(lián)的預(yù)加載:

$books = App\Book::with('author')->get();

foreach ($books as $book) {
  echo $book->author->name;
}

對(duì)于這個(gè)操作,只會(huì)執(zhí)行兩個(gè)查詢(xún):

select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)

預(yù)加載多個(gè)關(guān)聯(lián)

有時(shí)候你可能需要在一個(gè)操作中預(yù)加載多個(gè)關(guān)聯(lián),你只需要傳遞額外的參數(shù)到 with 方法中就可以:

$books = App\Book::with('author', 'publisher')->get();

嵌套的預(yù)加載

你可以使用 . 語(yǔ)法來(lái)加載嵌套的關(guān)聯(lián)。比如,讓我們?cè)谝粋€(gè) Eloquent 語(yǔ)句中一次加載所有書(shū)籍的作者以及作者的死人通訊簿:

$books = App\Book::with('author.contacts')->get();

預(yù)加載約束

有時(shí)候你可能希望預(yù)加載一些關(guān)聯(lián),但是也需要對(duì)預(yù)加載查詢(xún)指定額外的約束,這里有個(gè)示例:

$users = App\User::with(['posts' => function ($query) {
  $query->where('title', 'like', '%first%');
}])->get();

在這個(gè)例子中,Eloquent 會(huì)值預(yù)加載文章的 title 列包含 first 單詞的記錄。當(dāng)然,你也可以調(diào)用其他查詢(xún)生成器可用的方法:

$users = App\User::with(['posts' => function ($query) {
  $query->orderBy('created_at', 'desc');
}])->get();

延遲預(yù)加載

有時(shí)候你可能需要在上層模型被獲取后才預(yù)加載其關(guān)聯(lián)。當(dāng)你需要來(lái)動(dòng)態(tài)決定是否加載關(guān)聯(lián)模型時(shí)尤其有用:

$books = App\Book::all();

if ($someCondition) {
  $books->load('author', 'publisher');
}

如果你需要對(duì)預(yù)加載做一些查詢(xún)約束,你可以傳遞 Closureload 方法:

$books->load(['author' => function ($query) {
  $query->orderBy('published_date', 'asc');
}]);

插入關(guān)系模型

Save 方法

Eloquent 提供了方便的方法來(lái)為模型添加一個(gè)關(guān)聯(lián)。比如,也許你需要為 Post 模型新增一個(gè) Comment。除了手動(dòng)的設(shè)置 Commentpost_id 屬性,你也可以直接在關(guān)聯(lián)模型中調(diào)用 save 方法來(lái)插入 Comment:

$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);

注意上面我們并沒(méi)有使用關(guān)聯(lián)模型的動(dòng)態(tài)屬性的方式來(lái)訪(fǎng)問(wèn) comments,而是使用 comments 方法的形式來(lái)獲取關(guān)聯(lián)模型的實(shí)例。save 方法會(huì)自動(dòng)的添加相應(yīng)的 post_id 值到新的 Comment 模型上。

如果你需要一次添加多個(gè)關(guān)聯(lián)模型,你需要使用 saveMany 方法:

$post = App\Post::find(1);

$post->comments()->saveMany([
  new App\Comment(['message' => 'A new comment.']),
  new App\Comment(['message' => 'Another comment.']),
]);

Save & 多對(duì)多關(guān)聯(lián)

當(dāng)與多對(duì)多關(guān)聯(lián)互動(dòng)時(shí),save 方法接收一個(gè)中間層表屬性的額外參數(shù)數(shù)組作為第二個(gè)參數(shù):

App\User::find(1)->roles()->save($role, ['expires' => $expires]);

Create 方法

除了 savesaveMany 方法之外,你也可以使用 create 方法,它可以接收屬性組成的數(shù)組,創(chuàng)建一個(gè)模型并且將其存儲(chǔ)到數(shù)據(jù)庫(kù)。這一次,savecreate 方法的區(qū)別是 save 接收一個(gè)完整的 Eloquent 模型實(shí)例,而 create 接收的是一個(gè)原生的 PHP array:

$post = App\Post::find(1);

$comment = $post->comments()->create([
  'message' => 'A new comment.',
]);

在使用 create 方法之前,你應(yīng)該確保已經(jīng)閱讀了屬性的 批量賦值文檔

更新從屬關(guān)聯(lián)模型

當(dāng)更新一個(gè) belongsTo 關(guān)聯(lián)時(shí),你應(yīng)該使用 associate 方法。這個(gè)方法會(huì)在下層模型中設(shè)置外鍵:

$account = App\Account::find(10);

$user->account()->associate($account);

$user->save();

當(dāng)刪除 belongsTo 關(guān)聯(lián)時(shí),你應(yīng)該使用 dissociate 方法,該方法會(huì)重置下層模型所關(guān)聯(lián)的外鍵:

$user->account()->dissociate();

$user->save();

多對(duì)多關(guān)聯(lián)

附加 / 抽離

當(dāng)使用多對(duì)多關(guān)聯(lián)時(shí),Eloquent 提供了一些額外的幫助方法來(lái)更方便的管理關(guān)聯(lián)模型。比如,讓我們想象一下用戶(hù)可以有很多角色并且角色可以有很多用戶(hù)。你可以使用 attach 方法來(lái)附加一個(gè)角色到用戶(hù)并且在中間表中加入這條記錄:

$user = App\User::find(1);

$user->roles()->attach($roleId);

當(dāng)附加關(guān)聯(lián)到模型時(shí),你也可以傳遞一個(gè)含有額外數(shù)據(jù)的數(shù)組來(lái)將其添加到中間表中:

$user->roles()->attach($roleId, ['expires' => $expires]);

當(dāng)然,有時(shí)候你可能需要從用戶(hù)中刪除一個(gè)角色。你可以使用 detach 方法來(lái)刪除多對(duì)多關(guān)聯(lián)的記錄。datech 方法將從中間表中刪除相應(yīng)的記錄。但是,除了中間表,其它兩個(gè)模型的記錄都還會(huì)被保留:

// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();

為了更加的便捷,attachdetach 也可以接收 IDs 所組成的數(shù)組作為輸入:

$user = App\User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);

更新中間表的記錄
如果你需要更新中間表中存在的行,你可以使用 updateExistingPivot 方法:

$user = App\User::find(1);

$user->roles()->updateExistingPivot($roleId, $attributes);

便利的同步

你也可以使用 sync 方法來(lái)構(gòu)建多對(duì)多的關(guān)聯(lián)。sync 方法接收放置中間表 IDs 所組成的數(shù)組。任意 IDs 如果沒(méi)有在所給定的數(shù)組中,那么其將會(huì)從中間表中進(jìn)行刪除。所以,在操作完成之后,只有存在于給定數(shù)組里的 IDs 才會(huì)存在于中間表中:

$user->roles()->sync([1, 2, 3]);

你也可以同時(shí)傳遞額外的中間表的鍵值對(duì):

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

聯(lián)動(dòng)上層模型時(shí)間戳

當(dāng)一個(gè)模型 belongsTo 或者 belongsToMany 另外一個(gè)模型時(shí),比如 Comment 從屬于 Post,這對(duì)下層模型更新時(shí)同時(shí)要求更新上層模型的時(shí)間戳?xí)r很有幫助。比如,當(dāng) Comment 模型更新了,你想要自動(dòng)的更新其所屬的 Post 模型的 updated_at 時(shí)間戳。Eloquent 使之變的非常容易。你只需要在下層模型中添加一個(gè) touches 屬性來(lái)包含關(guān)聯(lián)的名稱(chēng)就可以了:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
  /**
   * All of the relationships to be touched.
   *
   * @var array
   */
  protected $touches = ['post'];

  /**
   * Get the post that the comment belongs to.
   */
  public function post()
  {
    return $this->belongsTo('App\Post');
  }
}

現(xiàn)在,當(dāng)你更新 Comment 時(shí),其所屬的 Post 將會(huì)同時(shí)更新 updated_at 列:

$comment = App\Comment::find(1);

$comment->text = 'Edit to this comment!';

$comment->save();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,530評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,407評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,981評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,759評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,204評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,415評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,955評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,650評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,892評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,675評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容