ホームページ  >  記事  >  バックエンド開発  >  [ Laravel 5.2 ドキュメント ] Eloquent ORM -- リレーションシップ

[ Laravel 5.2 ドキュメント ] Eloquent ORM -- リレーションシップ

WBOY
WBOYオリジナル
2016-06-23 13:19:081095ブラウズ

1. はじめに

たとえば、ブログ投稿には多くのコメントが含まれる場合や、注文が注文を行ったユーザーに関連付けられる場合があります。これらの関係をシンプルにして、さまざまな種類の関連付けをサポートします:

    1 対 1
  • 1 対多
  • 多対多
  • リモート 1 対多
  • ポリモーフィックな関連付け
  • 多対多ポリモーフィズム アソシエーション

2. アソシエーションを定義します

Eloquent 関連は Eloquent モデル クラス メソッドの形式で定義されます。 Eloquent モデル自体と同様、リレーションシップも強力なクエリ ビルダーであり、リレーションシップを関数として定義すると、強力なメソッド チェーンとクエリ機能を提供できます。例:

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

しかし、関連付けを詳しく使用する前に、各関連付けタイプを定義する方法を学びましょう:

1 対 1

1 対 1 関連付けは、非常に単純な関連付けです。ユーザー モデルには、対応する電話モデルがあります。このモデルを定義するには、user モデルにphone メソッドを配置する必要があります。phone メソッドは、Eloquent モデルの基本クラスの hasOne メソッドの結果を返す必要があります:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class User extends Model{    /**     * 获取关联到用户的手机     */    public function phone()    {        return $this->hasOne('App\Phone');    }}

hasOne メソッドに渡される最初のパラメータは、次の名前です。関連するモデル、関連付け 関係が定義されたら、Eloquent の動的プロパティを使用して、関連するレコードを取得できます。動的プロパティを使用すると、モデルで定義されたプロパティであるかのように関係関数にアクセスできます。

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

Eloquent 関係のデフォルトの外部キーはモデル名に基づきます。この場合、Phone モデルには user_id 外部キーがあります。デフォルト、必要に応じて、この規則をオーバーライドするには、2 番目のパラメータを hasOne メソッドに渡すことができます:

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

さらに、Eloquent は、外部キーが親に一致する ID を持つ必要があると想定します。つまり、Eloquent は ID を渡します。ユーザー テーブルの値は電話テーブルに送られ、user_id がそれに一致する電話レコードをクエリします。 ID の代わりに他の値を使用する関連付けが必要な場合は、3 番目のパラメーターを hasOne に渡してカスタム主キーを指定できます:

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

相対関連付けを定義します

それに応じて、User から Phone モデルにアクセスできます。また、電話モデルに関連付け関係を定義して、電話のユーザーを指定することもできます。 belongsTo メソッドを使用して、hasOne 関連付けに関連する関連付けを定義できます:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Phone extends Model{    /**     * 获取手机对应的用户     */    public function user()    {        return $this->belongsTo('App\User');    }}

上記の例では、Eloquent は Phone モデルの user_id を通じて User モデルで一致するレコードを検索しようとします。 Eloquent は、リレーションシップ メソッド名をメソッド名の後の _id サフィックスに関連付けることによって、デフォルトの外部キー名を生成します。ただし、電話モデルの外部キーが user_id ではない場合は、belongsTo メソッドの 2 番目のパラメータとしてカスタム キー名を渡すこともできます。

/** * 获取手机对应的用户 */public function user(){    return $this->belongsTo('App\User', 'foreign_key');}

親モデルが id を主キーとして使用していない場合、または必要な場合別の列を使用して子モデルを接続するには、親テーブルのカスタム キーを、belongsTo メソッドの 3 番目のパラメーターとして渡すことができます:

/** * 获取手机对应的用户 */public function user(){    return $this->belongsTo('App\User', 'foreign_key', 'other_key');}

1 対多

"1 対多" は関連付けを定義するために使用されます。単一のモデルと他の複数のモデルの間。たとえば、ブログ投稿には無数のコメントがあります。他のリレーションシップと同様に、1 対多のリレーションシップは Eloquent モデルでメソッドを定義することによって定義されます。便宜上、Eloquent は所有者のモデル名と ID サフィックスを外部キーとして使用します。したがって、この例では、Eloquent は Comment モデルの外部キーが post_id であると想定します。

関連付けが定義されたら、comments 属性にアクセスしてコメント コレクションにアクセスできます。 Eloquent は「動的プロパティ」を提供するため、モデルのプロパティと同じようにリレーションシップ メソッドにアクセスできることを覚えておいてください:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Post extends Model{    /**     * 获取博客文章的评论     */    public function comments()    {        return $this->hasMany('App\Comment');    }}

もちろん、すべてのリレーションシップはクエリ ビルダーでもあるため、コメント メソッドを呼び出すことで制約を追加できます。コメントメソッドで取得したコメント:

$comments = App\Post::find(1)->comments;foreach ($comments as $comment) {    //}

hasOne メソッドと同じように、追加のパラメータを hasMany メソッドに渡すことで、外部キーとローカル主キーをリセットすることもできます:

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

相対的な関連付けを定義します

これで、アクセスできるようになります。記事のすべてのコメント さて、次に、コメントを通じて記事へのアクセスを許可する関連付けを定義しましょう。 hasMany に関連する関連付けを定義するには、サブモデルで関連付けメソッドを定義して、belongsTo メソッドを呼び出す必要があります:

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

関連付けが定義された後、動的属性 post にアクセスすることで、Comment に対応する Post を取得できます。 :

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Comment extends Model{    /**     * 获取评论对应的博客文章     */    public function post()    {        return $this->belongsTo('App\Post');    }}

上記の例では、Eloquent は、Comment モデルの post_id を Post モデルの ID と照合しようとします。関連付けられたメソッド名に _id サフィックスを追加することで、デフォルトの外部キーを生成することもできます。カスタム外部キー名を 2 番目として渡します。外部キーが post_id ではない場合、またはカスタマイズしたい場合は、パラメーターがbelongsTo メソッドに渡されます。

$comment = App\Comment::find(1);echo $comment->post->title;

親モデルが id を主キーとして使用していない場合、または他の列を介して子モデルを接続したい場合は、カスタマイズできます。キー名は、belongsTo メソッドに 3 番目のパラメーターとして渡されます:

/** * 获取评论对应的博客文章 */public function post(){    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');}

多对多

多对多关系比 hasOne和 hasMany关联关系要稍微复杂一些。这种关联关系的一个例子就是一个用户有多个角色,同时一个角色被多个用户共用。例如,很多用户可能都有一个“Admin”角色。要定义这样的关联关系,需要三个数据表: users、 roles和 role_user, role_user表按照关联模型名的字母顺序命名,并且包含 user_id和 role_id两个列。

多对多关联通过编写一个调用 Eloquent 基类上的 belongsToMany方法的函数来定义:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class User extends Model{    /**     * 用户角色     */    public function roles()    {        return $this->belongsToMany('App\Role');    }}

关联关系被定义之后,可以使用动态属性 roles来访问用户的角色:

$user = App\User::find(1);foreach ($user->roles as $role) {    //}

当然,和所有其它关联关系类型一样,你可以调用 roles方法来添加条件约束到关联查询上:

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

正如前面所提到的,为了决定关联关系连接表的表名,Eloquent以字母顺序连接两个关联模型的名字。然而,你可以重写这种约定——通过传递第二个参数到 belongsToMany方法:

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

除了自定义连接表的表名,你还可以通过传递额外参数到 belongsToMany方法来自定义该表中字段的列名。第三个参数是你定义的关系模型的外键名称,第四个参数你要连接到的模型的外键名称:

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

定义相对的关联关系

要定义与多对多关联相对的关联关系,只需在关联模型中在调用一下 belongsToMany方法即可。让我们在 Role模型中定义 users方法:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Role extends Model{    /**     * 角色用户     */    public function users()    {        return $this->belongsToMany('App\User');    }}

正如你所看到的,定义的关联关系和与其对应的 User中定义的一模一样,只是前者引用 App\Role,后者引用 App\User,由于我们再次使用了 belongsToMany方法,所有的常用表和键自定义选项在定义与多对多相对的关联关系时都是可用的。

获取中间表的列

正如你已经学习到的,处理多对多关联要求一个中间表。Eloquent 提供了一些有用的方法来与其进行交互,例如,我们假设 User对象有很多与之关联的 Role对象,访问这些关联关系之后,我们可以使用模型上的 pivot属性访问中间表:

$user = App\User::find(1);foreach ($user->roles as $role) {    echo $role->pivot->created_at;}

注意我们获取到的每一个 Role模型都被自动赋上了 pivot属性。该属性包含一个代表中间表的模型,并且可以像其它 Eloquent 模型一样使用。

默认情况下,只有模型键才能用在 privot对象上,如果你的 privot表包含额外的属性,必须在定义关联关系时进行指定:

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

如果你想要你的 privot表自动包含 created_at和 updated_at时间戳,在关联关系定义时使用 withTimestamps方法:

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

远层的一对多

“远层一对多”关联为通过中间关联访问远层的关联关系提供了一个便利之道。例如, Country模型通过中间的 User模型可能拥有多个 Post模型。在这个例子中,你可以轻易的聚合给定国家的所有文章,让我们看看定义这个关联关系需要哪些表:

countries    id - integer    name - stringusers    id - integer    country_id - integer    name - stringposts    id - integer    user_id - integer    title - string

尽管 posts表不包含 country_id列, hasManyThrough关联提供了通过 $country->posts来访问一个国家的所有文章。要执行该查询,Eloquent 在中间表 $users上检查 country_id,查找到相匹配的用户ID后,通过用户ID来查询 posts表。

既然我们已经查看了该关联关系的数据表结构,接下来让我们在 Country模型上进行定义:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Country extends Model{    /**     * 获取指定国家的所有文章     */    public function posts()    {        return $this->hasManyThrough('App\Post', 'App\User');    }}

第一个传递到 hasManyThrough方法的参数是最终我们希望访问的模型的名称,第二个参数是中间模型名称。

当执行这种关联查询时通常 Eloquent 外键规则会被使用,如果你想要自定义该关联关系的外键,可以将它们作为第三个、第四个参数传递给 hasManyThrough方法。第三个参数是中间模型的外键名,第四个参数是最终模型的外键名。

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

多态关联

表结构

多态关联允许一个模型在单个关联下属于多个不同模型。例如,假如应用用户可以对文章点赞也可以对评论点赞,使用多态关联,你可以在这两种场景下使用单个 likes表,首先,让我们看看构建这种关联关系需要的表结构:

posts    id - integer    title - string    body - textcomments    id - integer    post_id - integer    body - textlikes    id - integer    likeable_id - integer    likeable_type - string

两个重要的需要注意的列是 likes表上的 likeable_id和 likeable_type。 likeable_id列对应 Post或 Comment的 ID 值,而 imageable_type列对应所属模型的类名。当访问 likeable关联时,ORM 根据 likeable_type列来判断所属模型的类型并返回相应模型实例。

模型结构

接下来,让我们看看构建这种关联关系需要在模型中定义什么:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Like extends Model{    /**     * 获取所属的likeable模型     */    public function likeable()    {        return $this->morphTo();    }}class Post extends Model{    /**     * 获取该文章的所有点赞     */    public function likes()    {        return $this->morphMany('App\Like', 'likeable');    }}class Comment extends Model{    /**     * 获取该评论的所有点赞     */    public function likes()    {        return $this->morphMany('App\Like', 'likeable');    }}

获取多态关联

数据表和模型定义好以后,可以通过模型访问关联关系。例如,要访问一篇文章的所有点赞,可以通过使用动态属性likes :

$post = App\Post::find(1);foreach ($post->likes as $like) {    //}

你还可以通过调用 morphTo方法从多态模型中获取多态关联的所属对象。在本例中,就是 Like模型中的 likeable方法。因此,我们可以用动态属性的方式访问该方法:

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

Like模型的 likeable关联返回 Post或 Comment实例,这取决于哪个类型的模型拥有该点赞。

多对多的多态关联

表结构

除了传统的多态关联,还可以定义“多对多”的多态关联,例如,一个博客的 Post和 Video模型可能共享一个 Tag模型的多态关联。使用对多对的多态关联允许你在博客文章和视频之间有唯一的标签列表。首先,让我们看看表结构:

posts    id - integer    name - stringvideos    id - integer    name - stringtags    id - integer    name - stringtaggables    tag_id - integer    taggable_id - integer    taggable_type - string

模型结构

接下来,我们准备在模型中定义该关联关系。 Post和 Video模型都有一个 tags方法调用 Eloquent 基类的 morphToMany方法:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Post extends Model{    /**     * 获取指定文章所有标签     */    public function tags()    {        return $this->morphToMany('App\Tag', 'taggable');    }}

定义相对的关联关系

接下来,在 Tag模型中,应该为每一个关联模型定义一个方法,例如,我们定义一个 posts方法和 videos方法:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Tag extends Model{    /**     * 获取所有分配该标签的文章     */    public function posts()    {        return $this->morphedByMany('App\Post', 'taggable');    }    /**     * 获取分配该标签的所有视频     */    public function videos()    {        return $this->morphedByMany('App\Video', 'taggable');    }}

获取关联关系

定义好数据库和模型后可以通过模型访问关联关系。例如,要访问一篇文章的所有标签,可以使用动态属性 tags:

$post = App\Post::find(1);foreach ($post->tags as $tag) {    //}

还可以通过访问调用 morphedByMany的方法名从多态模型中获取多态关联的所属对象。在本例中,就是Tag模型中的 posts或者 videos方法:

$tag = App\Tag::find(1);foreach ($tag->videos as $video) {    //}

3、关联查询

由于 Eloquent 所有关联关系都是通过函数定义,你可以调用这些方法来获取关联关系的实例而不需要再去手动执行关联查询。此外,所有 Eloquent 关联关系类型同时也是查询构建器,允许你在最终在数据库执行 SQL 之前继续添加条件约束到关联查询上。

例如,假定在一个博客系统中一个 User 模型有很多相关的 Post 模型:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class User extends Model{    /**     * 获取指定用户的所有文章     */    public function posts()    {        return $this->hasMany('App\Post');    }}

你可以像这样查询 posts关联并添加额外的条件约束到该关联关系上:

$user = App\User::find(1);$user->posts()->where('active', 1)->get();

你可以在关联关系上使用任何查询构建器!

关联关系方法 VS 动态属性

如果你不需要添加额外的条件约束到 Eloquent 关联查询,你可以简单通过动态属性来访问关联对象,例如,还是拿 User和 Post模型作为例子,你可以像这样访问所有用户的文章:

$user = App\User::find(1);foreach ($user->posts as $post) {    //}

动态属性就是“懒惰式加载”,意味着当你真正访问它们的时候才会加载关联数据。正因为如此,开发者经常使用渴求式加载来预加载他们知道在加载模型时要被访问的关联关系。渴求式加载有效减少了必须要被执以加载模型关联的 SQL 查询。

查询已存在的关联关系

访问一个模型的记录的时候,你可能希望基于关联关系是否存在来限制查询结果的数目。例如,假设你想要获取所有至少有一个评论的博客文章,要实现这个,可以传递关联关系的名称到 has方法:

// 获取所有至少有一条评论的文章...$posts = App\Post::has('comments')->get();

你还可以指定操作符和大小来自定义查询:

// 获取所有至少有三条评论的文章...$posts = Post::has('comments', '>=', 3)->get();

还可以使用”.“来构造嵌套has语句,例如,你要获取所有至少有一条评论及投票的所有文章:

// 获取所有至少有一条评论获得投票的文章...$posts = Post::has('comments.votes')->get();

如果你需要更强大的功能,可以使用 whereHas和 orWhereHas方法将 where条件放到 has查询上,这些方法允许你添加自定义条件约束到关联关系条件约束,例如检查一条评论的内容:

// 获取所有至少有一条评论包含foo字样的文章$posts = Post::whereHas('comments', function ($query) {    $query->where('content', 'like', 'foo%');})->get();

渴求式加载

当以属性方式访问数据库关联关系的时候,关联关系数据时“懒惰式加载”的,这意味着关联关系数据直到第一次访问的时候才被加载。然而,Eloquent 可以在查询父级模型的同时”渴求式加载“关联关系。渴求式加载缓解了 N+1查询问题,要阐明 N+1查询问题,考虑下关联到 Author的 Book模型:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Book extends Model{    /**     * 获取写这本书的作者     */    public function author()    {        return $this->belongsTo('App\Author');    }}

现在,让我们获取所有书及其作者:

$books = App\Book::all();foreach ($books as $book) {    echo $book->author->name;}

该循环先执行1次查询获取表中的所有书,然后另一个查询获取每一本书的作者,因此,如果有25本书,要执行26次查询:1次是获取书本身,剩下的25次查询是为每一本书获取其作者。

谢天谢地,我们可以使用渴求式加载来减少该操作到2次查询。当查询的时候,可以使用 with方法指定应该被渴求式加载的关联关系:

$books = App\Book::with('author')->get();foreach ($books as $book) {    echo $book->author->name;}

在该操作中,只执行两次查询即可:

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

渴求式加载多个关联关系

有时候你需要在单个操作中渴求式加载几个不同的关联关系。要实现这个,只需要添加额外的参数到 with方法即可:

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

嵌套的渴求式加载

要渴求式加载嵌套的关联关系,可以使用”.“语法。例如,让我们在一个Eloquent语句中渴求式加载所有书的作者及所有作者的个人联系方式:

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

带条件约束的渴求式加载

有时候我们希望渴求式加载一个关联关系,但还想为渴求式加载指定更多的查询条件:

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

在这个例子中,Eloquent 只渴求式加载 title 包含 first 的文章。当然,你可以调用其它查询构建器来自定义渴求式加载操作:

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

懒惰渴求式加载

有时候你需要在父模型已经被获取后渴求式加载一个关联关系。例如,这在你需要动态决定是否加载关联模型时可能很有用:

$books = App\Book::all();if ($someCondition) {    $books->load('author', 'publisher');}

如果你需要设置更多的查询条件到渴求式加载查询上,可以传递一个闭包到 load方法:

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

4、插入关联模型

基本使用

save方法

Eloquent 提供了便利的方法来添加新模型到关联关系。例如,也许你需要插入新的 Comment到 Post模型,你可以从关联关系的 save方法直接插入 Comment而不是手动设置 Comment的 post_id属性:

$comment = new App\Comment(['message' => 'A new comment.']);$post = App\Post::find(1);$post->comments()->save($comment);

注意我们没有用动态属性方式访问 comments,而是调用 comments方法获取关联关系实例。 save方法会自动添加 post_id值到新的 Comment模型。

如果你需要保存多个关联模型,可以使用 saveMany方法:

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

save & 多对多关联

当处理多对多关联的时候, save方法以数组形式接收额外的中间表属性作为第二个参数:

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

create方法

除了 save和 saveMany方法外,还可以使用 create方法,该方法接收属性数组、创建模型、然后插入数据库。 save和 create的不同之处在于 save接收整个Eloquent模型实例而 create接收原生PHP数组:

$post = App\Post::find(1);$comment = $post->comments()->create([    'message' => 'A new comment.',]);

使用 create方法之前确保先浏览属性批量赋值文档。

更新”属于“关联

更新 belongsTo关联的时候,可以使用 associate方法,该方法会在子模型设置外键:

$account = App\Account::find(10);$user->account()->associate($account);$user->save();

移除 belongsTo关联的时候,可以使用 dissociate方法。该方法在子模型上取消外键和关联:

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

多对多关联

附加/分离

处理多对多关联的时候,Eloquent提供了一些额外的帮助函数来使得处理关联模型变得更加方便。例如,让我们假定一个用户可能有多个角色同时一个角色属于多个用户,要通过在连接模型的中间表中插入记录附加角色到用户上,可以使用 attach方法:

$user = App\User::find(1);$user->roles()->attach($roleId);

附加关联关系到模型,还可以以数组形式传递额外被插入数据到中间表:

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

当然,有时候有必要从用户中移除角色,要移除一个多对多关联记录,使用 detach方法。 detach方法将会从中间表中移除相应的记录;然而,两个模型在数据库中都保持不变:

// 从指定用户中移除角色...$user->roles()->detach($roleId);// 从指定用户移除所有角色...$user->roles()->detach();

为了方便, attach和 detach还接收数组形式的 ID 作为输入:

$user = App\User::find(1);$user->roles()->detach([1, 2, 3]);$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);

同步

你还可以使用 sync方法构建多对多关联。 sync方法接收数组形式的ID并将其放置到中间表。任何不在该数组中的ID对应记录将会从中间表中移除。因此,该操作完成后,只有在数组中的ID对应记录还存在于中间表:

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

你还可以和ID一起传递额外的中间表值:

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

触发父级时间戳

当一个模型属于另外一个时,例如 Comment属于 Post,子模型更新时父模型的时间戳也被更新将很有用,例如,当 Comment模型被更新时,你可能想要”触发“创建其所属模型 Post的 updated_at时间戳。Eloquent使得这项操作变得简单,只需要添加包含关联关系名称的 touches属性到子模型中即可:

<?phpnamespace App;use Illuminate\Database\Eloquent\Model;class Comment extends Model{    /**     * 要触发的所有关联关系     *     * @var array     */    protected $touches = ['post'];    /**     * 评论所属文章     */    public function post()    {        return $this->belongsTo('App\Post');    }}

现在,当你更新 Comment时,所属模型 Post将也会更新其 updated_at值:

$comment = App\Comment::find(1);$comment->text = 'Edit to this comment!';$comment->save();
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。