修改理由:

错别字

相关信息:


此投稿已在 4年前 合并。

内容修改:

红色背景 为原始内容

绿色背景 为新增或者修改的内容

OldNewDifferences
1 # Eloquent:关联
2 
3 - [简介](#introduction)
4 - [定义关联](#defining-relationships)
5    - [一对一](#one-to-one)
6    - [一对多](#one-to-many)
7    - [一对多(反向)](#one-to-many-inverse)
8    - [多对多](#many-to-many)
9    - [远层一对多](#has-many-through)
10    - [多态关联](#polymorphic-relations)
11    - [多对多多态关联](#many-to-many-polymorphic-relations)
12 - [查询关联](#querying-relations)
13    - [关联方法 Vs. 动态属性](#relationship-methods-vs-dynamic-properties)
14    - [基于存在的关联查询](#querying-relationship-existence)
15    - [基于不存在的关联查询](#querying-relationship-absence)
16    - [关联数据计数](#counting-related-models)
17 - [预加载](#eager-loading)
18    - [为预加载添加约束条件](#constraining-eager-loads)
19    - [延迟预加载](#lazy-eager-loading)
20 - [插入 & 更新关联模型](#inserting-and-updating-related-models)
21    - [`save` 方法](#the-save-method)
22    - [`create` 方法](#the-create-method)
23    - [更新 `belongsTo` 关联](#updating-belongs-to-relationships)
24    - [多对多关联](#updating-many-to-many-relationships)
25 - [更新父级时间戳](#touching-parent-timestamps)
26 
27 <a name="introduction"></a>
28 ## 简介
29 
30 数据库表通常相互关联。 例如,一篇博客文章可能有许多评论,或者一个订单对应一个下单用户。Eloquent 让这些关联的管理和使用变得简单,并支持多种类型的关联:
31 
32 - [一对一](#one-to-one)
33 - [一对多](#one-to-many)
34 - [多对多](#many-to-many)
35 - [远层一对多](#has-many-through)
36 - [多态关联](#polymorphic-relations)
37 - [多对多多态关联](#many-to-many-polymorphic-relations)
38 
39 <a name="defining-relationships"></a>
40 ## 定义关联
41 
42 Eloquent 关联在 Eloquent 模型类中以方法的形式呈现。如同 Eloquent 模型本身,关联也可以作为强大的 [查询语句构造器](/docs/{{version}}/queries) 使用,提供了强大的链式调用和查询功能。例如,我们可以在 `posts` 关联的链式调用中附加一个约束条件:
43 
44    $user->posts()->where('active', 1)->get();
45 
46 不过,在深入使用关联之前,让我们先学习如何定义每种关联类型。
47 
48 <a name="one-to-one"></a>
49 ### 一对一
50 
51 一对一关联是最基本的关联关系。例如,一个 `User` 模型可能关联一个 `Phone` 模型。为了定义这个关联,我们要在 `User` 模型中写一个 `phone` 方法,在`phone` 方法内部调用 `hasOne` 方法并返回其结果:
52 
53    <?php
54 
55    namespace App;
56 
57    use Illuminate\Database\Eloquent\Model;
58 
59    class User extends Model
60    {
61        /**
62         * 获得与用户关联的电话记录。
63         */
64        public function phone()
65        {
66            return $this->hasOne('App\Phone');
67        }
68    }
69 
70 `hasOne` 方法的第一个参数是关联模型的类名。关联关系定义好后,我们就可以使用 Eloquent 动态属性获得相关的记录。您可以像在访问模型中定义的属性一样,使用动态属性:
71 
72    $phone = User::find(1)->phone;
73 
74 Eloquent 会基于模型名决定外键名称。在当前场景中,Eloquent 假设 `Phone` 模型有一个 `user_id` 外键,如果外键名不是这个,可以通过给 `hasOne` 方法传递第二个参数覆盖默认使用的外键名:
75 
76    return $this->hasOne('App\Phone', 'foreign_key');
77 
78 此外,Eloquent 假定外键值是与父级 `id`(或自定义 `$primaryKey`)列的值相匹配的。 换句话说,Eloquent 将在 `Phone` 记录的 `user_id` 列中查找与用户表的 `id` 列相匹配的值。 如果您希望该关联使用 `id`以外的自定义键名,则可以给 `hasOne` 方法传递第三个参数:
79 
80    return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
81 
82 #### 定义反向关联
83 
84 我们已经能从 `User` 模型访问到 `Phone` 模型了。现在,再在 `Phone` 模型中定义一个关联,此关联能让我们访问到拥有此电话的 `User` 模型。这时,使用的是与 `hasOne` 方法对应的 `belongsTo` 方法:
85 
86    <?php
87 
88    namespace App;
89 
90    use Illuminate\Database\Eloquent\Model;
91 
92    class Phone extends Model
93    {
94        /**
95         * 获得拥有此电话的用户。
96         */
97        public function user()
98        {
99            return $this->belongsTo('App\User');
100        }
101    }
102 
103 在上面的例子中,Eloquent 会尝试匹配 `Phone` 模型上的 `user_id` 至 `User` 模型上的 `id`。 它是通过检查关系方法的名称并使用 `_id` 作为后缀名来确定默认外键名称的。 但是,如果`Phone`模型的外键不是`user_id`,那么可以将自定义键名作为第二个参数传递给`belongsTo`方法:
104 
105    /**
106     * 获得拥有此电话的用户。
107     */
108    public function user()
109    {
110        return $this->belongsTo('App\User', 'foreign_key');
111    }
112 
113 如果父级模型没有使用 `id` 作为主键,或者是希望用不同的字段来连接子级模型,则可以通过给 `belongsTo` 方法传递第三个参数的形式指定父级数据表的自定义键:
114 
115    /**
116     * 获得拥有此电话的用户。
117     */
118    public function user()
119    {
120        return $this->belongsTo('App\User', 'foreign_key', 'other_key');
121    }
122 
123 <a name="default-models"></a>
124 #### 默认模型
125 
126 `belongsTo` 关联允许定义默认模型,这适应于当关联结果返回的是 `null` 的情况。这种设计模式通常称为 [空对象模式](https://en.wikipedia.org/wiki/Null_Object_pattern),为您免去了额外的条件判断代码。在下面的例子中,`user` 关联如果没有找到文章的作者,就会返回一个空的 `App\User` 模型。
127 
128    /**
129     * 获得此文章的作者。
130     */
131    public function user()
132    {
133        return $this->belongsTo('App\User')->withDefault();
134    }
135 
136 您也可以通过传递数组或者使用闭包的形式,填充默认模型的属性:
137 
138    /**
139     * 获得此文章的作者。
140     */
141    public function user()
142    {
143        return $this->belongsTo('App\User')->withDefault([
144            'name' => '游客',
145        ]);
146    }
147 
148    /**
149     * 获得此文章的作者。
150     */
151    public function user()
152    {
153        return $this->belongsTo('App\User')->withDefault(function ($user) {
154            $user->name = '游客';
155        });
156    }
157 
158 <a name="one-to-many"></a>
159 ### 一对多
160 
161 「一对多」关联用于定义单个模型拥有任意数量的其它关联模型。例如,一篇博客文章可能会有无限多条评论。就像其它的 Eloquent 关联一样,一对多关联的定义也是在 Eloquent 模型中写一个方法:
162 
163    <?php
164 
165    namespace App;
166 
167    use Illuminate\Database\Eloquent\Model;
168 
169    class Post extends Model
170    {
171        /**
172         * 获得此博客文章的评论。
173         */
174        public function comments()
175        {
176            return $this->hasMany('App\Comment');
177        }
178    }
179 
180 记住,Eloquent 会自动确定 `Comment` 模型上正确的外键字段。按照约定,Eloquent 使用父级模型名的「snake case」形式、加上 `_id` 后缀名作为外键字段。对应到上面的场景,就是 Eloquent 假定 `Comment` 模型对应到 `Post` 模型上的那个外键字段是 `post_id`。
181 
182 关联关系定义好后,我们就可以通过访问 `comments` 属性获得评论集合。记住,因为 Eloquent 提供了「动态属性」,所以我们可以像在访问模型中定义的属性一样,访问关联方法:
183 
184    $comments = App\Post::find(1)->comments;
185 
186    foreach ($comments as $comment) {
187        //
188    }
189 
190 当然,由于所有的关联还可以作为查询语句构造器使用,因此你可以使用链式调用的方式、在 `comments` 方法上添加额外的约束条件:
191 
192    $comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
193 
194 形如 `hasOne` 方法,您也可以在使用 `hasMany` 方法的时候,通过传递额外参数来覆盖默认使用的外键与本地键。
195 
196    return $this->hasMany('App\Comment', 'foreign_key');
197 
198    return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
199 
200 <a name="one-to-many-inverse"></a>
201 ### 一对多(反向)
202 
203 现在,我们已经能获得一篇文章的所有评论,接着再定义一个通过评论获得所属文章的关联。这个关联是 `hasMany` 关联的反向关联,在子级模型中使用 `belongsTo` 方法定义它:
204 
205    <?php
206 
207    namespace App;
208 
209    use Illuminate\Database\Eloquent\Model;
210 
211    class Comment extends Model
212    {
213        /**
214         * 获得此评论所属的文章。
215         */
216        public function post()
217        {
218            return $this->belongsTo('App\Post');
219        }
220    }
221 
222 关联关系定义好后,我们就可以在 `Comment` 模型上使用 `post` 「动态属性」获得 `Post` 模型了。
223 
224    $comment = App\Comment::find(1);
225 
226    echo $comment->post->title;
227 
228 在上面的例子中,Eloquent 会尝试用 `Comment` 模型的 `post_id` 与 `Post` 模型的 `id` 进行匹配。默认外键名是 Eloquent 依据关联名、并在关联名后加上 `_id` 后缀确定的。当然,如果 `Comment` 模型的外键不是 `post_id`,那么可以将自定义键名作为第二个参数传递给`belongsTo`方法:
229 
230    /**
231     * 获得此评论所属的文章。
232     */
233    public function post()
234    {
235        return $this->belongsTo('App\Post', 'foreign_key');
236    }
237 
238 如果父级模型没有使用 `id` 作为主键,或者是希望用不同的字段来连接子级模型,则可以通过给 `belongsTo`方法传递第三个参数的形式指定父级数据表的自定义键:
239 
240    /**
241     * 获得此评论所属的文章。
242     */
243    public function post()
244    {
245        return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
246    }
247 
248 <a name="many-to-many"></a>
249 ### 多对多
250 
251 多对多关联比 `hasOne` 和 `hasMany` 关联稍微复杂些。这种关联的一个例子就是具有许多角色的用户,而角色也被其他用户共享。例如,许多用户都可以有「管理员」角色。要定义这种关联,需要用到三个数据库表:`users`、`roles` 和 `role_user`。`role_user` 表是以相关联的两个模型数据表、依照字母顺序排列命名的,并且包含 `user_id` 和 `role_id` 字段。
252 
253 多对多关联是通过写一个方法定义的,在方法内部调用 `belongsToMany` 方法并返回其结果。例如,我们在 `User` 模型中定义一个 `roles` 方法:
254 
255    <?php
256 
257    namespace App;
258 
259    use Illuminate\Database\Eloquent\Model;
260 
261    class User extends Model
262    {
263        /**
264         * 获得此用户的角色。
265         */
266        public function roles()
267        {
268            return $this->belongsToMany('App\Role');
269        }
270    }
271 
272 关联关系定义好后,我们就可以通过 `roles` 动态属性获得用户的角色了:
273 
274    $user = App\User::find(1);
275 
276    foreach ($user->roles as $role) {
277        //
278    }
279 
280 当然,如同所有其它的关联类型,您可以调用 `roles` 方法,利用链式调用对查询语句添加约束条件:
281 
282    $roles = App\User::find(1)->roles()->orderBy('name')->get();
283 
284 如前所述,为了确定连接表表名,Eloquent 会按照字母顺序合并两个关联模型的名称。 当然,您可以自由地覆盖这个约定,通过给 `belongsToMany` 方法指定第二个参数实现:
285 
286    return $this->belongsToMany('App\Role', 'role_user');
287 
288 除了自定义连接表表名,您也可以通过给 `belongsToMany` 方法再次传递额外参数来自定义连接表里的键的字段名称。第三个参数是定义此关联的模型在连接表里的键名,第四个参数是另一个模型在连接表里的键名:
289 
290    return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
291 
292 #### 定义反向关联
293 
294 定义多对多关联的反向关联,您只要在对方模型里再次调用 `belongsToMany` 方法就可以了。让我们接着以用户角色为例,在 `Role` 模型中定义一个 `users` 方法。
295 
296    <?php
297 
298    namespace App;
299 
300    use Illuminate\Database\Eloquent\Model;
301 
302    class Role extends Model
303    {
304        /**
305         * 获得此角色下的用户。
306         */
307        public function users()
308        {
309            return $this->belongsToMany('App\User');
310        }
311    }
312 
313 如你所见,除了引入的模型变为 `App\User` 外,其它与在 `User` 模型中定义的完全一样。由于我们重用了 `belongsToMany` 方法,自定义连接表表名和自定义连接表里的键的字段名称在这里同样适用。
314 
315 #### 获得中间表字段
316 
317 您已经学到,多对多关联需要有一个中间表支持,Eloquent 提供了一些有用的方法来和这张表进行交互。例如,假设我们的 `User` 对象关联了许多的 `Role` 对象。在获得这些关联对象后,可以使用模型的 `pivot` 属性访问中间表数据:
318 
319    $user = App\User::find(1);
320 
321    foreach ($user->roles as $role) {
322        echo $role->pivot->created_at;
323    }
324 
325 需要注意的是,我们取得的每个 `Role` 模型对象,都会被自动赋予 `pivot` 属性,它代表中间表的一个模型对象,能像其它的 Eloquent 模型一样使用。
326 
327 默认情况下,`pivot` 对象只包含两个关联模型的键。如果中间表里还有额外字段,则必须在定义关联时明确指出:
328 
329    return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
330 
331 如果您想让中间表自动维护 `created_at` 和 `updated_at` 时间戳,那么在定义关联时加上 `withTimestamps` 方法即可。
332 
333    return $this->belongsToMany('App\Role')->withTimestamps();
334 
335 #### 通过中间表过滤关联数据
336 
337 在定义关联时,您可以使用 `wherePivot` 和 `wherePivotIn` 方法过滤 `belongsToMany` 返回的结果:
338 
339    return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
340 
341    return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
342 
343 #### 定义自定义中间表模型
344 
345 如果您想定义一个自定义模型来表示关联关系中的中间表,可以在定义关联时调用 `using` 方法。所有自定义中间表模型都必须扩展自 `Illuminate\Database\Eloquent\Relations\Pivot` 类。例如,
346 我们在写 `Role` 模型的关联时,使用自定义中间表模型 `UserRole`:
347 
348    <?php
349 
350    namespace App;
351 
352    use Illuminate\Database\Eloquent\Model;
353 
354    class Role extends Model
355    {
356        /**
357         * 获得此角色下的用户。
358         */
359        public function users()
360        {
361            return $this->belongsToMany('App\User')->using('App\UserRole');
362        }
363    }
364 
365 当定义 `UserRole` 模型时,我们要扩展自 `Pivot` 类:
366 
367    <?php
368 
369    namespace App;
370 
371    use Illuminate\Database\Eloquent\Relations\Pivot;
372 
373    class UserRole extends Pivot
374    {
375        //
376    }
377 
378 <a name="has-many-through"></a>
379 ### 远层一对多
380 
381 「远层一对多」关联提供了方便、简短的方式通过中间的关联来获得远层的关联。例如,一个 `Country` 模型可以通过中间的 `User` 模型获得多个 `Post` 模型。在这个例子中,您可以轻易地收集给定国家的所有博客文章。让我们来看看定义这种关联所需的数据表:
382 
383    countries
384        id - integer
385        name - string
386 
387    users
388        id - integer
389        country_id - integer
390        name - string
391 
392    posts
393        id - integer
394        user_id - integer
395        title - string
396 
397 虽然 `posts` 表中不包含 `country_id` 字段,但 `hasManyThrough` 关联能让我们通过 `$country->posts` 访问到一个国家下所有的用户文章。为了完成这个查询,Eloquent 会先检查中间表 `users` 的 `country_id` 字段,找到所有匹配的用户 ID 后,使用这些 ID,在 `posts` 表中完成查找。
398 
399 现在,我们已经知道了定义这种关联所需的数据表结构,接下来,让我们在 `Country` 模型中定义它:
400 
401    <?php
402 
403    namespace App;
404 
405    use Illuminate\Database\Eloquent\Model;
406 
407    class Country extends Model
408    {
409        /**
410         * 获得某个国家下所有的用户文章。
411         */
412        public function posts()
413        {
414            return $this->hasManyThrough('App\Post', 'App\User');
415        }
416    }
417 
418 `hasManyThrough` 方法的第一个参数是我们最终希望访问的模型名称,而第二个参数是中间模型的名称。
419 
420 当执行关联查询时,通常会使用 Eloquent 约定的外键名。如果您想要自定义关联的键,可以通过给 `hasManyThrough` 方法传递第三个和第四个参数实现,第三个参数表示中间模型的外键名,第四个参数表示最终模型的外键名。第五个参数表示本地键名,而第六个参数表示中间模型的本地键名:
421 
422    class Country extends Model
423    {
424        public function posts()
425        {
426            return $this->hasManyThrough(
427                'App\Post',
428                'App\User',
429                'country_id', // 用户表外键...
430                'user_id', // 文章表外键...
431                'id', // 国家表本地键...
432                'id' // 用户表本地键...
433            );
434        }
435    }
436 
437 <a name="polymorphic-relations"></a>
438 ### 多态关联
439 
440 #### 数据表结构
441 
442 多态关联允许一个模型在单个关联上属于多个其他模型。例如,想象一下使用您应用的用户可以「评论」文章和视频。使用多态关联,您可以用一个 `comments` 表同时满足这两个使用场景。让我们来看看构建这种关联所需的数据表结构:
443 
444    posts
445        id - integer
446        title - string
447        body - text
448 
449    videos
450        id - integer
451        title - string
452        url - string
453 
454    comments
455        id - integer
456        body - text
457        commentable_id - integer
458        commentable_type - string
459 
460 `comments` 表中有两个需要注意的重要字段 `commentable_id` 和 `commentable_type`。`commentable_id` 用来保存文章或者视频的 ID 值,而 `commentable_type` 用来保存所属模型的类名。`commentable_type` 是在我们访问 `commentable` 关联时, 让 ORM 确定所属的模型是哪个「类型」。
461 
462 #### 模型结构
463 
464 接下来,我们来看看创建这种关联所需的模型定义:
465 
466    <?php
467 
468    namespace App;
469 
470    use Illuminate\Database\Eloquent\Model;
471 
472    class Comment extends Model
473    {
474        /**
475         * 获得拥有此评论的模型。
476         */
477        public function commentable()
478        {
479            return $this->morphTo();
480        }
481    }
482 
483    class Post extends Model
484    {
485        /**
486         * 获得此文章的所有评论。
487         */
488        public function comments()
489        {
490            return $this->morphMany('App\Comment', 'commentable');
491        }
492    }
493 
494    class Video extends Model
495    {
496        /**
497         * 获得此视频的所有评论。
498         */
499        public function comments()
500        {
501            return $this->morphMany('App\Comment', 'commentable');
502        }
503    }
504 
505 #### 获取多态关联
506 
507 一旦您的数据库表准备好、模型定义完成后,就可以通过模型来访问关联了。例如,我们只要简单地使用 `comments` 动态属性,就可以获得某篇文章下的所有评论:
508 
509    $post = App\Post::find(1);
510 
511    foreach ($post->comments as $comment) {
512        //
513    }
514 
515 您也可以在多态模型上,通过访问调用了 `morphTo` 的关联方法获得多态关联的拥有者。在当前场景中,就是 `Comment` 模型的 `commentable` 方法。所以,我们可以使用动态属性来访问这个方法:
516 
517    $comment = App\Comment::find(1);
518 
519    $commentable = $comment->commentable;
520 
521 `Comment` 模型的 `commentable` 关联会返回 `Post` 或者 `Video` 实例,这取决于评论所属的模型类型。
522 
523 #### 自定义多态关联的类型字段
524 
525 默认,Laravel 会使用完全限定类名作为关联模型保存在多态模型上的类型字段值。比如,在上面的例子中,`Comment` 属于 `Post` 或者 `Video`,那么 `commentable_type`的默认值对应地就是 `App\Post` 和 `App\Video`。但是,您可能希望将数据库与程序内部结构解耦。那样的话,你可以定义一个「多态映射表」来指示 Eloquent 使用每个模型自定义类型字段名而不是类名:
526 
527    use Illuminate\Database\Eloquent\Relations\Relation;
528 
529    Relation::morphMap([
530        'posts' => 'App\Post',
531        'videos' => 'App\Video',
532    ]);
533 
534 您可以在 `AppServiceProvider` 中的 `boot` 函数中使用 `Relation::morphMap` 方法注册「多态映射表」,或者使用一个独立的服务提供者注册。
535 
536 <a name="many-to-many-polymorphic-relations"></a>
537 ### 多对多多态关联
538 
539 #### 数据表结构
540 
541 除了传统的多态关联,您也可以定义「多对多」的多态关联。例如,`Post` 模型和 `Video` 模型可以共享一个多态关联至 `Tag` 模型。 使用多对多多态关联可以让您在文章和视频中共享唯一的标签列表。首先,我们来看看数据表结构:
542 
543    posts
544        id - integer
545        name - string
546 
547    videos
548        id - integer
549        name - string
550 
551    tags
552        id - integer
553        name - string
554 
555    taggables
556        tag_id - integer
557        taggable_id - integer
558        taggable_type - string
559 
560 #### 模型结构
561 
562 接下来,我们准备在模型上定义关联关系。`Post` 和 `Video` 两个模型都有一个 `tags` 方法,方法内部都调用了 Eloquent 类自身的 `morphToMany` 方法:
563 
564    <?php
565 
566    namespace App;
567 
568    use Illuminate\Database\Eloquent\Model;
569 
570    class Post extends Model
571    {
572        /**
573         * 获得此文章的所有标签。
574         */
575        public function tags()
576        {
577            return $this->morphToMany('App\Tag', 'taggable');
578        }
579    }
580 
581 #### 定义反向关联
582 
583 接下里,在 `Tag` 模型中,您应该为每个关联模型定义一个方法。在这个例子里,我们要定义一个 `posts` 方法和一个 `videos` 方法:
584 
585    <?php
586 
587    namespace App;
588 
589    use Illuminate\Database\Eloquent\Model;
590 
591    class Tag extends Model
592    {
593        /**
594         * 获得此标签下所有的文章。
595         */
596        public function posts()
597        {
598            return $this->morphedByMany('App\Post', 'taggable');
599        }
600 
601        /**
602         * 获得此标签下所有的视频。
603         */
604        public function videos()
605        {
606            return $this->morphedByMany('App\Video', 'taggable');
607        }
608    }
609 
610 #### 获取关联
611 
612 一旦您的数据库表准备好、模型定义完成后,就可以通过模型来访问关联了。例如,我们只要简单地使用 `tags` 动态属性,就可以获得某篇文章下的所有标签:
613 
614    $post = App\Post::find(1);
615 
616    foreach ($post->tags as $tag) {
617        //
618    }
619 
620 您也可以在多态模型上,通过访问调用了 `morphedByMany` 的关联方法获得多态关联的拥有者。在当前场景中,就是 `Tag` 模型上的 `posts` 方法和 `videos` 方法。所以,我们可以使用动态属性来访问这两个方法:
621 
622    $tag = App\Tag::find(1);
623 
624    foreach ($tag->videos as $video) {
625        //
626    }
627 
628 <a name="querying-relations"></a>
629 ## 查询关联
630 
631 由于所有类型的关联都通过方法定义,您可以调用这些方法来获取关联实例,而不需要实际运行关联的查询。此外,所有类型的关联都可以作为 [查询语句构造器](/docs/{{version}}/queries) 使用,让你在向数据库执行 SQL 语句前,使用链式调用的方式添加约束条件。
632 
633 例如,假设一个博客系统,其中 `User` 模型有许多关联的 `Post` 模型:
634 
635    <?php
636 
637    namespace App;
638 
639    use Illuminate\Database\Eloquent\Model;
640 
641    class User extends Model
642    {
643        /**
644         * 获得此用户所有的文章。
645         */
646        public function posts()
647        {
648            return $this->hasMany('App\Post');
649        }
650    }
651 
652 您也可以像这样在 `posts` 关联上添加额外约束条件:
653 
654    $user = App\User::find(1);
655 
656    $user->posts()->where('active', 1)->get();
657 
658 您可以在关联上使用任何 [查询语句构造器](/docs/{{version}}/queries) 的方法,所以,欢迎查阅查询语句构造器的相关文档以便了解您可以使用哪些方法。
659 
660 <a name="relationship-methods-vs-dynamic-properties"></a>
661 ### 关联方法 Vs. 动态属性
662 
663 如果您不需要给 Eloquent 关联查询添加额外约束条件,你可以简单的像访问属性一样访问关联。例如,我们刚刚的 `User` 和 `Post` 模型例子中,我们可以这样访问所有用户的文章:
664 
665    $user = App\User::find(1);
666 
667    foreach ($user->posts as $post) {
668        //
669    }
670 
671 动态属性是「懒加载」的,意味着它们的关联数据只在实际被访问时才被加载。因此,开发者经常使用 [预加载](#eager-loading) 提前加载他们之后会用到的关联数据。预加载有效减少了 SQL 语句请求数,避免了重复执行一个模型关联加载数据、发送 SQL 请求带来的性能问题。
672 
673 <a name="querying-relationship-existence"></a>
674 ### 基于存在的关联查询
675 
676 当获取模型记录时,您可能希望根据存在的关联对结果进行限制。例如,您想获得至少有一条评论的所有博客文章。为了实现这个功能,您可以给 `has` 方法传递关联名称:
677 
678    // 获得所有至少有一条评论的文章...
679    $posts = App\Post::has('comments')->get();
680 
681 您也可以指定一个运算符和数目,进一步自定义查询:
682 
683    // 获得所有有三条或三条以上评论的文章...
684    $posts = Post::has('comments', '>=', 3)->get();
685 
686 也可以使用「点」符号构造嵌套的的 `has` 语句。例如,您可以获得所有至少有一条获赞评论的文章:
687 
688    // 获得所有至少有一条获赞评论的文章...
689    $posts = Post::has('comments.votes')->get();
690 
691 如果您需要更高级的用法,可以使用 `whereHas`和 `orWhereHas` 方法在 `has` 查询里设置「where」条件。此方法可以让你增加自定义条件至关联约束中,例如对评论内容进行检查:
692 
693    // 获得所有至少有一条评论内容满足 foo% 条件的文章
694    $posts = Post::whereHas('comments', function ($query) {
695        $query->where('content', 'like', 'foo%');
696    })->get();
697 
698 <a name="querying-relationship-absence"></a>
699 ### 基于不存在的关联查询
700 
701 当获取模型记录时,您可能希望根据不存在的关联对结果进行限制。例如,您想获得 **没有** 任何评论的所有博客文章。为了实现这个功能,您可以给 `doesntHave` 方法传递关联名称:
702 
703    $posts = App\Post::doesntHave('comments')->get();
704 
705 如果您需要更高级的用法,可以使用 `whereDoesntHave` 方法在 `doesntHave` 查询里设置「where」条件。此方法可以让你增加自定义条件至关联约束中,例如对评论内容进行检查:
706 
707    $posts = Post::whereDoesntHave('comments', function ($query) {
708        $query->where('content', 'like', 'foo%');
709    })->get();
710 
711 <a name="counting-related-models"></a>
712 ### 关联数据计数
713 
714 如果您只想统计结果数而不需要加载实际数据,那么可以使用 `withCount` 方法,此方法会在您的结果集模型中添加一个 `{关联名}_count` 字段。例如:
715 
716    $posts = App\Post::withCount('comments')->get();
717 
718    foreach ($posts as $post) {
719        echo $post->comments_count;
720    }
721 
722 您可以为多个关联数据「计数」,并为其查询添加约束条件:
723 
724    $posts = Post::withCount(['votes', 'comments' => function ($query) {
725        $query->where('content', 'like', 'foo%');
726    }])->get();
727 
728    echo $posts[0]->votes_count;
729    echo $posts[0]->comments_count;
730 
731 您也可以为关联数据计数结果起别名,允许在同一个关联上多次计数:
732 
733    $posts = Post::withCount([
734        'comments',
735        'comments as pending_comments_count' => function ($query) {
736            $query->where('approved', false);
737        }
738    ])->get();
739 
740    echo $posts[0]->comments_count;
741 
742    echo $posts[0]->pending_comments_count;
743 
744 <a name="eager-loading"></a>
745 ## 预加载
746 
747 当作为属性访问 Eloquent 关联时,关联数据是「懒加载」的。意味着在你第一次访问该属性时,才会加载关联数据。不过,是当你查询父模型时,Eloquent 可以「预加载」关联数据。预加载避免了 N + 1 查询问题。要说明 N + 1 查询问题,试想一个 `Book` 模型关联到 `Author` 模型:
748 
749    <?php
750 
751    namespace App;
752 
753    use Illuminate\Database\Eloquent\Model;
754 
755    class Book extends Model
756    {
757        /**
758         * 获得此书的作者。
759         */
760        public function author()
761        {
762            return $this->belongsTo('App\Author');
763        }
764    }
765 
766 现在,让我们来获得所有书籍和作者数据:
767 
768    $books = App\Book::all();
769 
770    foreach ($books as $book) {
771        echo $book->author->name;
772    }
773 
774 这个循环会运行一次查询取回所有数据表上的书籍数据,然后又运行一次查询获得每本书的作者数据。如果我们有 25 本书,则循环就会执行 26 次查询:1 次是获得所有书籍数据,另外 25 条查询用来获得每本书的作者数据。
775 
776 谢天谢地,我们使用预加载让整个查询减少到 2 次。这是通过指定关联给 `with` 方法办到的:
777 
778    $books = App\Book::with('author')->get();
779 
780    foreach ($books as $book) {
781        echo $book->author->name;
782    }
783 
784 整个操作,只执行了两条查询:
785 
786    select * from books
787 
788    select * from authors where id in (1, 2, 3, 4, 5, ...)
789 
790 #### 预加载多个关联
791 
792 有时,你需要在一次操作中预加载几个不同的关联。为了实现这个功能,只需在 `with` 方法上传递额外的参数即可:       
793 
794    $books = App\Book::with(['author', 'publisher'])->get();
795 
796 #### 嵌套预加载
797 
798 预加载嵌套关联,可以使用「点」语法。例如,在一个 Eloquent 语句中,预加载所有书籍作者和这些作者的联系信息:
799 
800    $books = App\Book::with('author.contacts')->get();
801 
802 <a name="constraining-eager-loads"></a>
803 ### 为预加载添加约束条件
804 
805 有时,你可能希望在预加载关联数据的时候,为查询指定额外的约束条件。这有个例子:
806 
807    $users = App\User::with(['posts' => function ($query) {
808        $query->where('title', 'like', '%first%');
809    }])->get();
810 
811 在这个例子中,Eloquent 只会预加载标题里包含 `first` 文本的文章。您也可以调用其它的 [查询语句构造器](/docs/{{version}}/queries) 进一步自定义预加载约束条件:
812 
813    $users = App\User::with(['posts' => function ($query) {
814        $query->orderBy('created_at', 'desc');
815    }])->get();
816 
817 <a name="lazy-eager-loading"></a>
818 ### 延迟预加载
819 
820 有时,您可能需要在获得父级模型后才去预加载关联数据。例如,当你需要来动态决定是否加载关联模型时,这可能很有帮助:
821 
822    $books = App\Book::all();
823 
824    if ($someCondition) {
825        $books->load('author', 'publisher');
826    }
827 
828 如果您想设置预加载查询的额外约束条件,可以通过给 `load` 添加数组键的形式达到目的,数组值是接收查询实例的闭包:
829 
830    $books->load(['author' => function ($query) {
831        $query->orderBy('published_date', 'asc');
832    }]);
833 
834 <a name="inserting-and-updating-related-models"></a>
835 ## 插入 & 更新关联模型
836 
837 <a name="the-save-method"></a>
838 ### `save` 方法
839 
840 Eloquent 提供了便捷的方法来将新的模型增加至关联中。例如,也许你需要为一个 `Post` 模型插入一个新的 `Comment`。这是你无须为 `Comment` 手动设置 `posts` 属性,直接在关联上使用 `save` 方法插入 `Comment` 即可:
841 
842    $comment = new App\Comment(['message' => '一条新的评论。']);
843 
844    $post = App\Post::find(1);
845 
846    $post->comments()->save($comment);
847 
848 需要注意的是,我们没有使用动态属性形式访问 `comments` 关联。相反,我们调用了 `comments` 方法获得关联实例。`save` 方法会自动在新的 `Comment` 模型中添加正确的 `post_id`值。
849 
850 如果您需要保存多个关联模型,可以使用 `saveMany` 方法:
851 
852    $post = App\Post::find(1);
853 
854    $post->comments()->saveMany([
855        new App\Comment(['message' => '一条新的评论。']),
856        new App\Comment(['message' => '另一条评论。']),
857    ]);
858 
859 <a name="the-create-method"></a>
860 ### `create` 方法
861 
862 除了 `save` 和 `saveMany` 方法,您也可以使用 `create` 方法,它接收一个属性数组、创建模型并插入数据库。还有,`save` 和 `create` 的不同之处在于,`save` 接收的是一个完整的 Eloquent 模型实例,而 `create` 接收的是一个纯 PHP 数组:
863 
864    $post = App\Post::find(1);
865 
866    $comment = $post->comments()->create([
867        'message' => '一条新的评论。',
868    ]);
869 
870 > {tip} 在使用 `create` 方法前,请确认您已经浏览了本文档的 [批量赋值](/docs/{{version}}/eloquent#mass-assignment) 章节。
871 
872 您可以使用 `createMany` 方法保存多个关联模型:
873 
874    $post = App\Post::find(1);
875 
876    $post->comments()->createMany([
877        [
878            'message' => '一条新的评论。',
879        ],
880        [
881            'message' => '另一条新的评论。',
882        ],
883    ]);
884 
885 <a name="updating-belongs-to-relationships"></a>
886 ### 更新 `belongsTo` 关联
887 
888 当更新 `belongsTo` 关联时,可以使用 `associate` 方法。此方法会在子模型中设置外键:
889 
890    $account = App\Account::find(10);
891 
892    $user->account()->associate($account);
893 
894    $user->save();
895 
896 当删除 `belongsTo` 关联时,可以使用 `dissociate`方法。此方法会设置关联外键为 `null`:
897 
898    $user->account()->dissociate();
899 
900    $user->save();
901 
902 <a name="updating-many-to-many-relationships"></a>
903 ### 多对多关联
904 
905 #### 附加 / 移除
906 
907 Eloquent 也提供了几个额外的辅助方法,让操作关联模型更加便捷。例如:我们假设一个用户可以拥有多个角色,并且每个角色都可以被多个用户共享。给某个用户附加一个角色是通过向中间表插入一条记录实现的,使用 `attach` 方法:
908 
909    $user = App\User::find(1);
910 
911    $user->roles()->attach($roleId);
912 
913 使用 `attach` 方法时,您也可以通过传递一个数组参数向中间表写入额外数据:
914 
915    $user->roles()->attach($roleId, ['expires' => $expires]);
916 
917 当然,有时也需要移除用户的角色。删除多对多关联记录,使用 `detach` 方法。`detach` 方法会移除掉正确的记录;当然,这两个模型数据依然保存在数据库中:
918 
919    // 移除用户的一个角色...
920    $user->roles()->detach($roleId);
921 
922    // 移除用户的所有角色...
923    $user->roles()->detach();
924 
925 为了方便,`attach` 和 `detach` 都允许传入 ID 数组:
926 
927    $user = App\User::find(1);
928 
929    $user->roles()->detach([1, 2, 3]);
930 
931    $user->roles()->attach([
932        1 => ['expires' => $expires],
933        2 => ['expires' => $expires]
934    ]);
935 
936 #### 同步关联
937 
938 您也可以使用 `sync` 方法来构造多对多关联。`sync` 方法可以接收 ID 数组,向中间表插入对应关联数据记录。所有没放在数组里的 IDs 都会从中间表里移除。所以,这步操作完成后,只有在数组里的 IDs 会被保留在中间表中。
939 
940    $user->roles()->sync([1, 2, 3]);
941 
942 您可以通过 ID 传递其他额外的数据到中间表:
943 
944    $user->roles()->sync([1 => ['expires' => true], 2, 3]);
945 
946 如果您不想移除现有的 IDs,可以使用 `syncWithoutDetaching` 方法:
947 
948    $user->roles()->syncWithoutDetaching([1, 2, 3]);
949 
950 #### 切换关联
951 
952 多对多关联也提供了一个 `toggle` 方法用于「切换」给定 IDs 的附加状态。如果给定 ID 已附加,就会被移除。同样的,如果给定 ID 已移除,就会被附加:
953 
954    $user->roles()->toggle([1, 2, 3]);
955 
956 #### 在中间表上保存额外数据
957 
958 当处理多对多关联时,`save` 方法还可以使用第二个参数,它是一个属性数组,包含插入到中间表的额外字段数据。
959 
960    App\User::find(1)->roles()->save($role, ['expires' => $expires]);
961 
962 #### 更新中间表记录
963 
964 如果您需要更新中间表中已存在的记录,可以使用 `updateExistingPivot` 方法。此方法接收中间记录的外键和一个属性数组进行更新:
965 
966    $user = App\User::find(1);
967 
968    $user->roles()->updateExistingPivot($roleId, $attributes);
969 
970 <a name="touching-parent-timestamps"></a>
971 ## 更新父级时间戳
972 
973 当一个模型 `belongsTo` 或者 `belongsToMany` 另一个模型,比如一个 `Comment` 属于一个 `Post`,有时更新子模型导致更新父模型时间戳非常有用。例如,当一个 `Comment` 模型更新时,您要自动「触发」父级 `Post` 模型的 `updated_at` 时间戳的更新,Eloquent 让它变得简单。只要在子模型加一个包含关联名称的 `touches` 属性即可:
974 
975    <?php
976 
977    namespace App;
978 
979    use Illuminate\Database\Eloquent\Model;
980 
981    class Comment extends Model
982    {
983        /**
984         * 所有会被触发的关联。
985         *
986         * @var array
987         */
988        protected $touches = ['post'];
989 
990        /**
991         * 获得此评论所属的文章。
992         */
993        public function post()
994        {
995            return $this->belongsTo('App\Post');
996        }
997    }
998 
999 现在,当你更新一个 `Comment` 时,对应父级 `Post` 模型的 `updated_at` 字段也会被同时更新,使其更方便得知何时让一个 `Post` 模型的缓存失效:
1000 
1001    $comment = App\Comment::find(1);
1002 
1003    $comment->text = '编辑了这条评论!';
1004 
1005    $comment->save();
1006 
1007 ## 译者署名
1008 | 用户名 | 头像 | 职能 | 签名 |
1009 |---|---|---|---|
1010 | [@baooab](https://learnku.com/users/17319) | <img class="avatar-66 rm-style" src="https://cdn.learnku.com/uploads/images/201708/11/17319/KbHzLBdgHs.png?imageView2/1/w/100/h/100"> | 翻译 | 我在 [这儿](https://github.com/baooab/) |
1011 
1012 
1013 ---
1014 
1015 > {note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。
1016 >
1017 > 转载请注明:本文档由 Laravel China 社区 [laravel-china.org](https://laravel-china.org) 组织翻译,详见 [翻译召集帖](https://learnku.com/laravel/t/5756/laravel-55-document-translation-call-come-and-join-the-translation)。
1018 >
 1# Eloquent:关联
 2
 3- [简介](#introduction)
 4- [定义关联](#defining-relationships)
 5   - [一对一](#one-to-one)
 6   - [一对多](#one-to-many)
 7   - [一对多(反向)](#one-to-many-inverse)
 8   - [多对多](#many-to-many)
 9   - [远层一对多](#has-many-through)
 10   - [多态关联](#polymorphic-relations)
 11   - [多对多多态关联](#many-to-many-polymorphic-relations)
 12- [查询关联](#querying-relations)
 13   - [关联方法 Vs. 动态属性](#relationship-methods-vs-dynamic-properties)
 14   - [基于存在的关联查询](#querying-relationship-existence)
 15   - [基于不存在的关联查询](#querying-relationship-absence)
 16   - [关联数据计数](#counting-related-models)
 17- [预加载](#eager-loading)
 18   - [为预加载添加约束条件](#constraining-eager-loads)
 19   - [延迟预加载](#lazy-eager-loading)
 20- [插入 & 更新关联模型](#inserting-and-updating-related-models)
 21   - [`save` 方法](#the-save-method)
 22   - [`create` 方法](#the-create-method)
 23   - [更新 `belongsTo` 关联](#updating-belongs-to-relationships)
 24   - [多对多关联](#updating-many-to-many-relationships)
 25- [更新父级时间戳](#touching-parent-timestamps)
 26
 27<a name="introduction"></a>
 28## 简介
 29
 30数据库表通常相互关联。 例如,一篇博客文章可能有许多评论,或者一个订单对应一个下单用户。Eloquent 让这些关联的管理和使用变得简单,并支持多种类型的关联:
 31
 32- [一对一](#one-to-one)
 33- [一对多](#one-to-many)
 34- [多对多](#many-to-many)
 35- [远层一对多](#has-many-through)
 36- [多态关联](#polymorphic-relations)
 37- [多对多多态关联](#many-to-many-polymorphic-relations)
 38
 39<a name="defining-relationships"></a>
 40## 定义关联
 41
 42Eloquent 关联在 Eloquent 模型类中以方法的形式呈现。如同 Eloquent 模型本身,关联也可以作为强大的 [查询语句构造器](/docs/{{version}}/queries) 使用,提供了强大的链式调用和查询功能。例如,我们可以在 `posts` 关联的链式调用中附加一个约束条件:
 43
 44   $user->posts()->where('active', 1)->get();
 45
 46不过,在深入使用关联之前,让我们先学习如何定义每种关联类型。
 47
 48<a name="one-to-one"></a>
 49### 一对一
 50
 51一对一关联是最基本的关联关系。例如,一个 `User` 模型可能关联一个 `Phone` 模型。为了定义这个关联,我们要在 `User` 模型中写一个 `phone` 方法,在`phone` 方法内部调用 `hasOne` 方法并返回其结果:
 52
 53   <?php
 54
 55   namespace App;
 56
 57   use Illuminate\Database\Eloquent\Model;
 58
 59   class User extends Model
 60   {
 61       /**
 62        * 获得与用户关联的电话记录。
 63        */
 64       public function phone()
 65       {
 66           return $this->hasOne('App\Phone');
 67       }
 68   }
 69
 70`hasOne` 方法的第一个参数是关联模型的类名。关联关系定义好后,我们就可以使用 Eloquent 动态属性获得相关的记录。您可以像在访问模型中定义的属性一样,使用动态属性:
 71
 72   $phone = User::find(1)->phone;
 73
 74Eloquent 会基于模型名决定外键名称。在当前场景中,Eloquent 假设 `Phone` 模型有一个 `user_id` 外键,如果外键名不是这个,可以通过给 `hasOne` 方法传递第二个参数覆盖默认使用的外键名:
 75
 76   return $this->hasOne('App\Phone', 'foreign_key');
 77
 78此外,Eloquent 假定外键值是与父级 `id`(或自定义 `$primaryKey`)列的值相匹配的。 换句话说,Eloquent 将在 `Phone` 记录的 `user_id` 列中查找与用户表的 `id` 列相匹配的值。 如果您希望该关联使用 `id`以外的自定义键名,则可以给 `hasOne` 方法传递第三个参数:
 79
 80   return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
 81
 82#### 定义反向关联
 83
 84我们已经能从 `User` 模型访问到 `Phone` 模型了。现在,再在 `Phone` 模型中定义一个关联,此关联能让我们访问到拥有此电话的 `User` 模型。这时,使用的是与 `hasOne` 方法对应的 `belongsTo` 方法:
 85
 86   <?php
 87
 88   namespace App;
 89
 90   use Illuminate\Database\Eloquent\Model;
 91
 92   class Phone extends Model
 93   {
 94       /**
 95        * 获得拥有此电话的用户。
 96        */
 97       public function user()
 98       {
 99           return $this->belongsTo('App\User');
 100       }
 101   }
 102
 103在上面的例子中,Eloquent 会尝试匹配 `Phone` 模型上的 `user_id` 至 `User` 模型上的 `id`。 它是通过检查关系方法的名称并使用 `_id` 作为后缀名来确定默认外键名称的。 但是,如果`Phone`模型的外键不是`user_id`,那么可以将自定义键名作为第二个参数传递给`belongsTo`方法:
 104
 105   /**
 106    * 获得拥有此电话的用户。
 107    */
 108   public function user()
 109   {
 110       return $this->belongsTo('App\User', 'foreign_key');
 111   }
 112
 113如果父级模型没有使用 `id` 作为主键,或者是希望用不同的字段来连接子级模型,则可以通过给 `belongsTo` 方法传递第三个参数的形式指定父级数据表的自定义键:
 114
 115   /**
 116    * 获得拥有此电话的用户。
 117    */
 118   public function user()
 119   {
 120       return $this->belongsTo('App\User', 'foreign_key', 'other_key');
 121   }
 122
 123<a name="default-models"></a>
 124#### 默认模型
 125
 126`belongsTo` 关联允许定义默认模型,这适应于当关联结果返回的是 `null` 的情况。这种设计模式通常称为 [空对象模式](https://en.wikipedia.org/wiki/Null_Object_pattern),为您免去了额外的条件判断代码。在下面的例子中,`user` 关联如果没有找到文章的作者,就会返回一个空的 `App\User` 模型。
 127
 128   /**
 129    * 获得此文章的作者。
 130    */
 131   public function user()
 132   {
 133       return $this->belongsTo('App\User')->withDefault();
 134   }
 135
 136您也可以通过传递数组或者使用闭包的形式,填充默认模型的属性:
 137
 138   /**
 139    * 获得此文章的作者。
 140    */
 141   public function user()
 142   {
 143       return $this->belongsTo('App\User')->withDefault([
 144           'name' => '游客',
 145       ]);
 146   }
 147
 148   /**
 149    * 获得此文章的作者。
 150    */
 151   public function user()
 152   {
 153       return $this->belongsTo('App\User')->withDefault(function ($user) {
 154           $user->name = '游客';
 155       });
 156   }
 157
 158<a name="one-to-many"></a>
 159### 一对多
 160
 161「一对多」关联用于定义单个模型拥有任意数量的其它关联模型。例如,一篇博客文章可能会有无限多条评论。就像其它的 Eloquent 关联一样,一对多关联的定义也是在 Eloquent 模型中写一个方法:
 162
 163   <?php
 164
 165   namespace App;
 166
 167   use Illuminate\Database\Eloquent\Model;
 168
 169   class Post extends Model
 170   {
 171       /**
 172        * 获得此博客文章的评论。
 173        */
 174       public function comments()
 175       {
 176           return $this->hasMany('App\Comment');
 177       }
 178   }
 179
 180记住,Eloquent 会自动确定 `Comment` 模型上正确的外键字段。按照约定,Eloquent 使用父级模型名的「snake case」形式、加上 `_id` 后缀名作为外键字段。对应到上面的场景,就是 Eloquent 假定 `Comment` 模型对应到 `Post` 模型上的那个外键字段是 `post_id`。
 181
 182关联关系定义好后,我们就可以通过访问 `comments` 属性获得评论集合。记住,因为 Eloquent 提供了「动态属性」,所以我们可以像在访问模型中定义的属性一样,访问关联方法:
 183
 184   $comments = App\Post::find(1)->comments;
 185
 186   foreach ($comments as $comment) {
 187       //
 188   }
 189
 190当然,由于所有的关联还可以作为查询语句构造器使用,因此你可以使用链式调用的方式、在 `comments` 方法上添加额外的约束条件:
 191
 192   $comments = App\Post::find(1)->comments()->where('title', 'foo')->first();
 193
 194形如 `hasOne` 方法,您也可以在使用 `hasMany` 方法的时候,通过传递额外参数来覆盖默认使用的外键与本地键。
 195
 196   return $this->hasMany('App\Comment', 'foreign_key');
 197
 198   return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
 199
 200<a name="one-to-many-inverse"></a>
 201### 一对多(反向)
 202
 203现在,我们已经能获得一篇文章的所有评论,接着再定义一个通过评论获得所属文章的关联。这个关联是 `hasMany` 关联的反向关联,在子级模型中使用 `belongsTo` 方法定义它:
 204
 205   <?php
 206
 207   namespace App;
 208
 209   use Illuminate\Database\Eloquent\Model;
 210
 211   class Comment extends Model
 212   {
 213       /**
 214        * 获得此评论所属的文章。
 215        */
 216       public function post()
 217       {
 218           return $this->belongsTo('App\Post');
 219       }
 220   }
 221
 222关联关系定义好后,我们就可以在 `Comment` 模型上使用 `post` 「动态属性」获得 `Post` 模型了。
 223
 224   $comment = App\Comment::find(1);
 225
 226   echo $comment->post->title;
 227
 228在上面的例子中,Eloquent 会尝试用 `Comment` 模型的 `post_id` 与 `Post` 模型的 `id` 进行匹配。默认外键名是 Eloquent 依据关联名、并在关联名后加上 `_id` 后缀确定的。当然,如果 `Comment` 模型的外键不是 `post_id`,那么可以将自定义键名作为第二个参数传递给`belongsTo`方法:
 229
 230   /**
 231    * 获得此评论所属的文章。
 232    */
 233   public function post()
 234   {
 235       return $this->belongsTo('App\Post', 'foreign_key');
 236   }
 237
 238如果父级模型没有使用 `id` 作为主键,或者是希望用不同的字段来连接子级模型,则可以通过给 `belongsTo`方法传递第三个参数的形式指定父级数据表的自定义键:
 239
 240   /**
 241    * 获得此评论所属的文章。
 242    */
 243   public function post()
 244   {
 245       return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
 246   }
 247
 248<a name="many-to-many"></a>
 249### 多对多
 250
 251多对多关联比 `hasOne` 和 `hasMany` 关联稍微复杂些。这种关联的一个例子就是具有许多角色的用户,而角色也被其他用户共享。例如,许多用户都可以有「管理员」角色。要定义这种关联,需要用到三个数据库表:`users`、`roles` 和 `role_user`。`role_user` 表是以相关联的两个模型数据表、依照字母顺序排列命名的,并且包含 `user_id` 和 `role_id` 字段。
 252
 253多对多关联是通过写一个方法定义的,在方法内部调用 `belongsToMany` 方法并返回其结果。例如,我们在 `User` 模型中定义一个 `roles` 方法:
 254
 255   <?php
 256
 257   namespace App;
 258
 259   use Illuminate\Database\Eloquent\Model;
 260
 261   class User extends Model
 262   {
 263       /**
 264        * 获得此用户的角色。
 265        */
 266       public function roles()
 267       {
 268           return $this->belongsToMany('App\Role');
 269       }
 270   }
 271
 272关联关系定义好后,我们就可以通过 `roles` 动态属性获得用户的角色了:
 273
 274   $user = App\User::find(1);
 275
 276   foreach ($user->roles as $role) {
 277       //
 278   }
 279
 280当然,如同所有其它的关联类型,您可以调用 `roles` 方法,利用链式调用对查询语句添加约束条件:
 281
 282   $roles = App\User::find(1)->roles()->orderBy('name')->get();
 283
 284如前所述,为了确定连接表表名,Eloquent 会按照字母顺序合并两个关联模型的名称。 当然,您可以自由地覆盖这个约定,通过给 `belongsToMany` 方法指定第二个参数实现:
 285
 286   return $this->belongsToMany('App\Role', 'role_user');
 287
 288除了自定义连接表表名,您也可以通过给 `belongsToMany` 方法再次传递额外参数来自定义连接表里的键的字段名称。第三个参数是定义此关联的模型在连接表里的键名,第四个参数是另一个模型在连接表里的键名:
 289
 290   return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');
 291
 292#### 定义反向关联
 293
 294定义多对多关联的反向关联,您只要在对方模型里再次调用 `belongsToMany` 方法就可以了。让我们接着以用户角色为例,在 `Role` 模型中定义一个 `users` 方法。
 295
 296   <?php
 297
 298   namespace App;
 299
 300   use Illuminate\Database\Eloquent\Model;
 301
 302   class Role extends Model
 303   {
 304       /**
 305        * 获得此角色下的用户。
 306        */
 307       public function users()
 308       {
 309           return $this->belongsToMany('App\User');
 310       }
 311   }
 312
 313如你所见,除了引入的模型变为 `App\User` 外,其它与在 `User` 模型中定义的完全一样。由于我们重用了 `belongsToMany` 方法,自定义连接表表名和自定义连接表里的键的字段名称在这里同样适用。
 314
 315#### 获得中间表字段
 316
 317您已经学到,多对多关联需要有一个中间表支持,Eloquent 提供了一些有用的方法来和这张表进行交互。例如,假设我们的 `User` 对象关联了许多的 `Role` 对象。在获得这些关联对象后,可以使用模型的 `pivot` 属性访问中间表数据:
 318
 319   $user = App\User::find(1);
 320
 321   foreach ($user->roles as $role) {
 322       echo $role->pivot->created_at;
 323   }
 324
 325需要注意的是,我们取得的每个 `Role` 模型对象,都会被自动赋予 `pivot` 属性,它代表中间表的一个模型对象,能像其它的 Eloquent 模型一样使用。
 326
 327默认情况下,`pivot` 对象只包含两个关联模型的键。如果中间表里还有额外字段,则必须在定义关联时明确指出:
 328
 329   return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');
 330
 331如果您想让中间表自动维护 `created_at` 和 `updated_at` 时间戳,那么在定义关联时加上 `withTimestamps` 方法即可。
 332
 333   return $this->belongsToMany('App\Role')->withTimestamps();
 334
 335#### 通过中间表过滤关联数据
 336
 337在定义关联时,您可以使用 `wherePivot` 和 `wherePivotIn` 方法过滤 `belongsToMany` 返回的结果:
 338
 339   return $this->belongsToMany('App\Role')->wherePivot('approved', 1);
 340
 341   return $this->belongsToMany('App\Role')->wherePivotIn('priority', [1, 2]);
 342
 343#### 定义自定义中间表模型
 344
 345如果您想定义一个自定义模型来表示关联关系中的中间表,可以在定义关联时调用 `using` 方法。所有自定义中间表模型都必须扩展自 `Illuminate\Database\Eloquent\Relations\Pivot` 类。例如,
 346我们在写 `Role` 模型的关联时,使用自定义中间表模型 `UserRole`:
 347
 348   <?php
 349
 350   namespace App;
 351
 352   use Illuminate\Database\Eloquent\Model;
 353
 354   class Role extends Model
 355   {
 356       /**
 357        * 获得此角色下的用户。
 358        */
 359       public function users()
 360       {
 361           return $this->belongsToMany('App\User')->using('App\UserRole');
 362       }
 363   }
 364
 365当定义 `UserRole` 模型时,我们要扩展自 `Pivot` 类:
 366
 367   <?php
 368
 369   namespace App;
 370
 371   use Illuminate\Database\Eloquent\Relations\Pivot;
 372
 373   class UserRole extends Pivot
 374   {
 375       //
 376   }
 377
 378<a name="has-many-through"></a>
 379### 远层一对多
 380
 381「远层一对多」关联提供了方便、简短的方式通过中间的关联来获得远层的关联。例如,一个 `Country` 模型可以通过中间的 `User` 模型获得多个 `Post` 模型。在这个例子中,您可以轻易地收集给定国家的所有博客文章。让我们来看看定义这种关联所需的数据表:
 382
 383   countries
 384       id - integer
 385       name - string
 386
 387   users
 388       id - integer
 389       country_id - integer
 390       name - string
 391
 392   posts
 393       id - integer
 394       user_id - integer
 395       title - string
 396
 397虽然 `posts` 表中不包含 `country_id` 字段,但 `hasManyThrough` 关联能让我们通过 `$country->posts` 访问到一个国家下所有的用户文章。为了完成这个查询,Eloquent 会先检查中间表 `users` 的 `country_id` 字段,找到所有匹配的用户 ID 后,使用这些 ID,在 `posts` 表中完成查找。
 398
 399现在,我们已经知道了定义这种关联所需的数据表结构,接下来,让我们在 `Country` 模型中定义它:
 400
 401   <?php
 402
 403   namespace App;
 404
 405   use Illuminate\Database\Eloquent\Model;
 406
 407   class Country extends Model
 408   {
 409       /**
 410        * 获得某个国家下所有的用户文章。
 411        */
 412       public function posts()
 413       {
 414           return $this->hasManyThrough('App\Post', 'App\User');
 415       }
 416   }
 417
 418`hasManyThrough` 方法的第一个参数是我们最终希望访问的模型名称,而第二个参数是中间模型的名称。
 419
 420当执行关联查询时,通常会使用 Eloquent 约定的外键名。如果您想要自定义关联的键,可以通过给 `hasManyThrough` 方法传递第三个和第四个参数实现,第三个参数表示中间模型的外键名,第四个参数表示最终模型的外键名。第五个参数表示本地键名,而第六个参数表示中间模型的本地键名:
 421
 422   class Country extends Model
 423   {
 424       public function posts()
 425       {
 426           return $this->hasManyThrough(
 427               'App\Post',
 428               'App\User',
 429               'country_id', // 用户表外键...
 430               'user_id', // 文章表外键...
 431               'id', // 国家表本地键...
 432               'id' // 用户表本地键...
 433           );
 434       }
 435   }
 436
 437<a name="polymorphic-relations"></a>
 438### 多态关联
 439
 440#### 数据表结构
 441
 442多态关联允许一个模型在单个关联上属于多个其他模型。例如,想象一下使用您应用的用户可以「评论」文章和视频。使用多态关联,您可以用一个 `comments` 表同时满足这两个使用场景。让我们来看看构建这种关联所需的数据表结构:
 443
 444   posts
 445       id - integer
 446       title - string
 447       body - text
 448
 449   videos
 450       id - integer
 451       title - string
 452       url - string
 453
 454   comments
 455       id - integer
 456       body - text
 457       commentable_id - integer
 458       commentable_type - string
 459
 460`comments` 表中有两个需要注意的重要字段 `commentable_id` 和 `commentable_type`。`commentable_id` 用来保存文章或者视频的 ID 值,而 `commentable_type` 用来保存所属模型的类名。`commentable_type` 是在我们访问 `commentable` 关联时, 让 ORM 确定所属的模型是哪个「类型」。
 461
 462#### 模型结构
 463
 464接下来,我们来看看创建这种关联所需的模型定义:
 465
 466   <?php
 467
 468   namespace App;
 469
 470   use Illuminate\Database\Eloquent\Model;
 471
 472   class Comment extends Model
 473   {
 474       /**
 475        * 获得拥有此评论的模型。
 476        */
 477       public function commentable()
 478       {
 479           return $this->morphTo();
 480       }
 481   }
 482
 483   class Post extends Model
 484   {
 485       /**
 486        * 获得此文章的所有评论。
 487        */
 488       public function comments()
 489       {
 490           return $this->morphMany('App\Comment', 'commentable');
 491       }
 492   }
 493
 494   class Video extends Model
 495   {
 496       /**
 497        * 获得此视频的所有评论。
 498        */
 499       public function comments()
 500       {
 501           return $this->morphMany('App\Comment', 'commentable');
 502       }
 503   }
 504
 505#### 获取多态关联
 506
 507一旦您的数据库表准备好、模型定义完成后,就可以通过模型来访问关联了。例如,我们只要简单地使用 `comments` 动态属性,就可以获得某篇文章下的所有评论:
 508
 509   $post = App\Post::find(1);
 510
 511   foreach ($post->comments as $comment) {
 512       //
 513   }
 514
 515您也可以在多态模型上,通过访问调用了 `morphTo` 的关联方法获得多态关联的拥有者。在当前场景中,就是 `Comment` 模型的 `commentable` 方法。所以,我们可以使用动态属性来访问这个方法:
 516
 517   $comment = App\Comment::find(1);
 518
 519   $commentable = $comment->commentable;
 520
 521`Comment` 模型的 `commentable` 关联会返回 `Post` 或者 `Video` 实例,这取决于评论所属的模型类型。
 522
 523#### 自定义多态关联的类型字段
 524
 525默认,Laravel 会使用完全限定类名作为关联模型保存在多态模型上的类型字段值。比如,在上面的例子中,`Comment` 属于 `Post` 或者 `Video`,那么 `commentable_type`的默认值对应地就是 `App\Post` 和 `App\Video`。但是,您可能希望将数据库与程序内部结构解耦。那样的话,你可以定义一个「多态映射表」来指示 Eloquent 使用每个模型自定义类型字段名而不是类名:
 526
 527   use Illuminate\Database\Eloquent\Relations\Relation;
 528
 529   Relation::morphMap([
 530       'posts' => 'App\Post',
 531       'videos' => 'App\Video',
 532   ]);
 533
 534您可以在 `AppServiceProvider` 中的 `boot` 函数中使用 `Relation::morphMap` 方法注册「多态映射表」,或者使用一个独立的服务提供者注册。
 535
 536<a name="many-to-many-polymorphic-relations"></a>
 537### 多对多多态关联
 538
 539#### 数据表结构
 540
 541除了传统的多态关联,您也可以定义「多对多」的多态关联。例如,`Post` 模型和 `Video` 模型可以共享一个多态关联至 `Tag` 模型。 使用多对多多态关联可以让您在文章和视频中共享唯一的标签列表。首先,我们来看看数据表结构:
 542
 543   posts
 544       id - integer
 545       name - string
 546
 547   videos
 548       id - integer
 549       name - string
 550
 551   tags
 552       id - integer
 553       name - string
 554
 555   taggables
 556       tag_id - integer
 557       taggable_id - integer
 558       taggable_type - string
 559
 560#### 模型结构
 561
 562接下来,我们准备在模型上定义关联关系。`Post` 和 `Video` 两个模型都有一个 `tags` 方法,方法内部都调用了 Eloquent 类自身的 `morphToMany` 方法:
 563
 564   <?php
 565
 566   namespace App;
 567
 568   use Illuminate\Database\Eloquent\Model;
 569
 570   class Post extends Model
 571   {
 572       /**
 573        * 获得此文章的所有标签。
 574        */
 575       public function tags()
 576       {
 577           return $this->morphToMany('App\Tag', 'taggable');
 578       }
 579   }
 580
 581#### 定义反向关联
 582
 583接下里,在 `Tag` 模型中,您应该为每个关联模型定义一个方法。在这个例子里,我们要定义一个 `posts` 方法和一个 `videos` 方法:
 584
 585   <?php
 586
 587   namespace App;
 588
 589   use Illuminate\Database\Eloquent\Model;
 590
 591   class Tag extends Model
 592   {
 593       /**
 594        * 获得此标签下所有的文章。
 595        */
 596       public function posts()
 597       {
 598           return $this->morphedByMany('App\Post', 'taggable');
 599       }
 600
 601       /**
 602        * 获得此标签下所有的视频。
 603        */
 604       public function videos()
 605       {
 606           return $this->morphedByMany('App\Video', 'taggable');
 607       }
 608   }
 609
 610#### 获取关联
 611
 612一旦您的数据库表准备好、模型定义完成后,就可以通过模型来访问关联了。例如,我们只要简单地使用 `tags` 动态属性,就可以获得某篇文章下的所有标签:
 613
 614   $post = App\Post::find(1);
 615
 616   foreach ($post->tags as $tag) {
 617       //
 618   }
 619
 620您也可以在多态模型上,通过访问调用了 `morphedByMany` 的关联方法获得多态关联的拥有者。在当前场景中,就是 `Tag` 模型上的 `posts` 方法和 `videos` 方法。所以,我们可以使用动态属性来访问这两个方法:
 621
 622   $tag = App\Tag::find(1);
 623
 624   foreach ($tag->videos as $video) {
 625       //
 626   }
 627
 628<a name="querying-relations"></a>
 629## 查询关联
 630
 631由于所有类型的关联都通过方法定义,您可以调用这些方法来获取关联实例,而不需要实际运行关联的查询。此外,所有类型的关联都可以作为 [查询语句构造器](/docs/{{version}}/queries) 使用,让你在向数据库执行 SQL 语句前,使用链式调用的方式添加约束条件。
 632
 633例如,假设一个博客系统,其中 `User` 模型有许多关联的 `Post` 模型:
 634
 635   <?php
 636
 637   namespace App;
 638
 639   use Illuminate\Database\Eloquent\Model;
 640
 641   class User extends Model
 642   {
 643       /**
 644        * 获得此用户所有的文章。
 645        */
 646       public function posts()
 647       {
 648           return $this->hasMany('App\Post');
 649       }
 650   }
 651
 652您也可以像这样在 `posts` 关联上添加额外约束条件:
 653
 654   $user = App\User::find(1);
 655
 656   $user->posts()->where('active', 1)->get();
 657
 658您可以在关联上使用任何 [查询语句构造器](/docs/{{version}}/queries) 的方法,所以,欢迎查阅查询语句构造器的相关文档以便了解您可以使用哪些方法。
 659
 660<a name="relationship-methods-vs-dynamic-properties"></a>
 661### 关联方法 Vs. 动态属性
 662
 663如果您不需要给 Eloquent 关联查询添加额外约束条件,你可以简单的像访问属性一样访问关联。例如,我们刚刚的 `User` 和 `Post` 模型例子中,我们可以这样访问所有用户的文章:
 664
 665   $user = App\User::find(1);
 666
 667   foreach ($user->posts as $post) {
 668       //
 669   }
 670
 671动态属性是「懒加载」的,意味着它们的关联数据只在实际被访问时才被加载。因此,开发者经常使用 [预加载](#eager-loading) 提前加载他们之后会用到的关联数据。预加载有效减少了 SQL 语句请求数,避免了重复执行一个模型关联加载数据、发送 SQL 请求带来的性能问题。
 672
 673<a name="querying-relationship-existence"></a>
 674### 基于存在的关联查询
 675
 676当获取模型记录时,您可能希望根据存在的关联对结果进行限制。例如,您想获得至少有一条评论的所有博客文章。为了实现这个功能,您可以给 `has` 方法传递关联名称:
 677
 678   // 获得所有至少有一条评论的文章...
 679   $posts = App\Post::has('comments')->get();
 680
 681您也可以指定一个运算符和数目,进一步自定义查询:
 682
 683   // 获得所有有三条或三条以上评论的文章...
 684   $posts = Post::has('comments', '>=', 3)->get();
 685
 686也可以使用「点」符号构造嵌套的的 `has` 语句。例如,您可以获得所有至少有一条获赞评论的文章:
 687
 688   // 获得所有至少有一条获赞评论的文章...
 689   $posts = Post::has('comments.votes')->get();
 690
 691如果您需要更高级的用法,可以使用 `whereHas`和 `orWhereHas` 方法在 `has` 查询里设置「where」条件。此方法可以让你增加自定义条件至关联约束中,例如对评论内容进行检查:
 692
 693   // 获得所有至少有一条评论内容满足 foo% 条件的文章
 694   $posts = Post::whereHas('comments', function ($query) {
 695       $query->where('content', 'like', 'foo%');
 696   })->get();
 697
 698<a name="querying-relationship-absence"></a>
 699### 基于不存在的关联查询
 700
 701当获取模型记录时,您可能希望根据不存在的关联对结果进行限制。例如,您想获得 **没有** 任何评论的所有博客文章。为了实现这个功能,您可以给 `doesntHave` 方法传递关联名称:
 702
 703   $posts = App\Post::doesntHave('comments')->get();
 704
 705如果您需要更高级的用法,可以使用 `whereDoesntHave` 方法在 `doesntHave` 查询里设置「where」条件。此方法可以让你增加自定义条件至关联约束中,例如对评论内容进行检查:
 706
 707   $posts = Post::whereDoesntHave('comments', function ($query) {
 708       $query->where('content', 'like', 'foo%');
 709   })->get();
 710
 711<a name="counting-related-models"></a>
 712### 关联数据计数
 713
 714如果您只想统计结果数而不需要加载实际数据,那么可以使用 `withCount` 方法,此方法会在您的结果集模型中添加一个 `{关联名}_count` 字段。例如:
 715
 716   $posts = App\Post::withCount('comments')->get();
 717
 718   foreach ($posts as $post) {
 719       echo $post->comments_count;
 720   }
 721
 722您可以为多个关联数据「计数」,并为其查询添加约束条件:
 723
 724   $posts = Post::withCount(['votes', 'comments' => function ($query) {
 725       $query->where('content', 'like', 'foo%');
 726   }])->get();
 727
 728   echo $posts[0]->votes_count;
 729   echo $posts[0]->comments_count;
 730
 731您也可以为关联数据计数结果起别名,允许在同一个关联上多次计数:
 732
 733   $posts = Post::withCount([
 734       'comments',
 735       'comments as pending_comments_count' => function ($query) {
 736           $query->where('approved', false);
 737       }
 738   ])->get();
 739
 740   echo $posts[0]->comments_count;
 741
 742   echo $posts[0]->pending_comments_count;
 743
 744<a name="eager-loading"></a>
 745## 预加载
 746
 747当作为属性访问 Eloquent 关联时,关联数据是「懒加载」的。意味着在你第一次访问该属性时,才会加载关联数据。不过,是当你查询父模型时,Eloquent 可以「预加载」关联数据。预加载避免了 N + 1 查询问题。要说明 N + 1 查询问题,试想一个 `Book` 模型关联到 `Author` 模型:
 748
 749   <?php
 750
 751   namespace App;
 752
 753   use Illuminate\Database\Eloquent\Model;
 754
 755   class Book extends Model
 756   {
 757       /**
 758        * 获得此书的作者。
 759        */
 760       public function author()
 761       {
 762           return $this->belongsTo('App\Author');
 763       }
 764   }
 765
 766现在,让我们来获得所有书籍和作者数据:
 767
 768   $books = App\Book::all();
 769
 770   foreach ($books as $book) {
 771       echo $book->author->name;
 772   }
 773
 774这个循环会运行一次查询取回所有数据表上的书籍数据,然后又运行一次查询获得每本书的作者数据。如果我们有 25 本书,则循环就会执行 26 次查询:1 次是获得所有书籍数据,另外 25 条查询用来获得每本书的作者数据。
 775
 776谢天谢地,我们使用预加载让整个查询减少到 2 次。这是通过指定关联给 `with` 方法办到的:
 777
 778   $books = App\Book::with('author')->get();
 779
 780   foreach ($books as $book) {
 781       echo $book->author->name;
 782   }
 783
 784整个操作,只执行了两条查询:
 785
 786   select * from books
 787
 788   select * from authors where id in (1, 2, 3, 4, 5, ...)
 789
 790#### 预加载多个关联
 791
 792有时,你需要在一次操作中预加载几个不同的关联。为了实现这个功能,只需在 `with` 方法上传递额外的参数即可:       
 793
 794   $books = App\Book::with(['author', 'publisher'])->get();
 795
 796#### 嵌套预加载
 797
 798预加载嵌套关联,可以使用「点」语法。例如,在一个 Eloquent 语句中,预加载所有书籍作者和这些作者的联系信息:
 799
 800   $books = App\Book::with('author.contacts')->get();
 801
 802<a name="constraining-eager-loads"></a>
 803### 为预加载添加约束条件
 804
 805有时,你可能希望在预加载关联数据的时候,为查询指定额外的约束条件。这有个例子:
 806
 807   $users = App\User::with(['posts' => function ($query) {
 808       $query->where('title', 'like', '%first%');
 809   }])->get();
 810
 811在这个例子中,Eloquent 只会预加载标题里包含 `first` 文本的文章。您也可以调用其它的 [查询语句构造器](/docs/{{version}}/queries) 进一步自定义预加载约束条件:
 812
 813   $users = App\User::with(['posts' => function ($query) {
 814       $query->orderBy('created_at', 'desc');
 815   }])->get();
 816
 817<a name="lazy-eager-loading"></a>
 818### 延迟预加载
 819
 820有时,您可能需要在获得父级模型后才去预加载关联数据。例如,当你需要来动态决定是否加载关联模型时,这可能很有帮助:
 821
 822   $books = App\Book::all();
 823
 824   if ($someCondition) {
 825       $books->load('author', 'publisher');
 826   }
 827
 828如果您想设置预加载查询的额外约束条件,可以通过给 `load` 添加数组键的形式达到目的,数组值是接收查询实例的闭包:
 829
 830   $books->load(['author' => function ($query) {
 831       $query->orderBy('published_date', 'asc');
 832   }]);
 833
 834<a name="inserting-and-updating-related-models"></a>
 835## 插入 & 更新关联模型
 836
 837<a name="the-save-method"></a>
 838### `save` 方法
 839
 840Eloquent 提供了便捷的方法来将新的模型增加至关联中。例如,也许你需要为一个 `Post` 模型插入一个新的 `Comment`。这时你无须为 `Comment` 手动设置 `posts` 属性,直接在关联上使用 `save` 方法插入 `Comment` 即可:
 841
 842   $comment = new App\Comment(['message' => '一条新的评论。']);
 843
 844   $post = App\Post::find(1);
 845
 846   $post->comments()->save($comment);
 847
 848需要注意的是,我们没有使用动态属性形式访问 `comments` 关联。相反,我们调用了 `comments` 方法获得关联实例。`save` 方法会自动在新的 `Comment` 模型中添加正确的 `post_id`值。
 849
 850如果您需要保存多个关联模型,可以使用 `saveMany` 方法:
 851
 852   $post = App\Post::find(1);
 853
 854   $post->comments()->saveMany([
 855       new App\Comment(['message' => '一条新的评论。']),
 856       new App\Comment(['message' => '另一条评论。']),
 857   ]);
 858
 859<a name="the-create-method"></a>
 860### `create` 方法
 861
 862除了 `save` 和 `saveMany` 方法,您也可以使用 `create` 方法,它接收一个属性数组、创建模型并插入数据库。还有,`save` 和 `create` 的不同之处在于,`save` 接收的是一个完整的 Eloquent 模型实例,而 `create` 接收的是一个纯 PHP 数组:
 863
 864   $post = App\Post::find(1);
 865
 866   $comment = $post->comments()->create([
 867       'message' => '一条新的评论。',
 868   ]);
 869
 870> {tip} 在使用 `create` 方法前,请确认您已经浏览了本文档的 [批量赋值](/docs/{{version}}/eloquent#mass-assignment) 章节。
 871
 872您可以使用 `createMany` 方法保存多个关联模型:
 873
 874   $post = App\Post::find(1);
 875
 876   $post->comments()->createMany([
 877       [
 878           'message' => '一条新的评论。',
 879       ],
 880       [
 881           'message' => '另一条新的评论。',
 882       ],
 883   ]);
 884
 885<a name="updating-belongs-to-relationships"></a>
 886### 更新 `belongsTo` 关联
 887
 888当更新 `belongsTo` 关联时,可以使用 `associate` 方法。此方法会在子模型中设置外键:
 889
 890   $account = App\Account::find(10);
 891
 892   $user->account()->associate($account);
 893
 894   $user->save();
 895
 896当删除 `belongsTo` 关联时,可以使用 `dissociate`方法。此方法会设置关联外键为 `null`:
 897
 898   $user->account()->dissociate();
 899
 900   $user->save();
 901
 902<a name="updating-many-to-many-relationships"></a>
 903### 多对多关联
 904
 905#### 附加 / 移除
 906
 907Eloquent 也提供了几个额外的辅助方法,让操作关联模型更加便捷。例如:我们假设一个用户可以拥有多个角色,并且每个角色都可以被多个用户共享。给某个用户附加一个角色是通过向中间表插入一条记录实现的,使用 `attach` 方法:
 908
 909   $user = App\User::find(1);
 910
 911   $user->roles()->attach($roleId);
 912
 913使用 `attach` 方法时,您也可以通过传递一个数组参数向中间表写入额外数据:
 914
 915   $user->roles()->attach($roleId, ['expires' => $expires]);
 916
 917当然,有时也需要移除用户的角色。删除多对多关联记录,使用 `detach` 方法。`detach` 方法会移除掉正确的记录;当然,这两个模型数据依然保存在数据库中:
 918
 919   // 移除用户的一个角色...
 920   $user->roles()->detach($roleId);
 921
 922   // 移除用户的所有角色...
 923   $user->roles()->detach();
 924
 925为了方便,`attach` 和 `detach` 都允许传入 ID 数组:
 926
 927   $user = App\User::find(1);
 928
 929   $user->roles()->detach([1, 2, 3]);
 930
 931   $user->roles()->attach([
 932       1 => ['expires' => $expires],
 933       2 => ['expires' => $expires]
 934   ]);
 935
 936#### 同步关联
 937
 938您也可以使用 `sync` 方法来构造多对多关联。`sync` 方法可以接收 ID 数组,向中间表插入对应关联数据记录。所有没放在数组里的 IDs 都会从中间表里移除。所以,这步操作完成后,只有在数组里的 IDs 会被保留在中间表中。
 939
 940   $user->roles()->sync([1, 2, 3]);
 941
 942您可以通过 ID 传递其他额外的数据到中间表:
 943
 944   $user->roles()->sync([1 => ['expires' => true], 2, 3]);
 945
 946如果您不想移除现有的 IDs,可以使用 `syncWithoutDetaching` 方法:
 947
 948   $user->roles()->syncWithoutDetaching([1, 2, 3]);
 949
 950#### 切换关联
 951
 952多对多关联也提供了一个 `toggle` 方法用于「切换」给定 IDs 的附加状态。如果给定 ID 已附加,就会被移除。同样的,如果给定 ID 已移除,就会被附加:
 953
 954   $user->roles()->toggle([1, 2, 3]);
 955
 956#### 在中间表上保存额外数据
 957
 958当处理多对多关联时,`save` 方法还可以使用第二个参数,它是一个属性数组,包含插入到中间表的额外字段数据。
 959
 960   App\User::find(1)->roles()->save($role, ['expires' => $expires]);
 961
 962#### 更新中间表记录
 963
 964如果您需要更新中间表中已存在的记录,可以使用 `updateExistingPivot` 方法。此方法接收中间记录的外键和一个属性数组进行更新:
 965
 966   $user = App\User::find(1);
 967
 968   $user->roles()->updateExistingPivot($roleId, $attributes);
 969
 970<a name="touching-parent-timestamps"></a>
 971## 更新父级时间戳
 972
 973当一个模型 `belongsTo` 或者 `belongsToMany` 另一个模型,比如一个 `Comment` 属于一个 `Post`,有时更新子模型导致更新父模型时间戳非常有用。例如,当一个 `Comment` 模型更新时,您要自动「触发」父级 `Post` 模型的 `updated_at` 时间戳的更新,Eloquent 让它变得简单。只要在子模型加一个包含关联名称的 `touches` 属性即可:
 974
 975   <?php
 976
 977   namespace App;
 978
 979   use Illuminate\Database\Eloquent\Model;
 980
 981   class Comment extends Model
 982   {
 983       /**
 984        * 所有会被触发的关联。
 985        *
 986        * @var array
 987        */
 988       protected $touches = ['post'];
 989
 990       /**
 991        * 获得此评论所属的文章。
 992        */
 993       public function post()
 994       {
 995           return $this->belongsTo('App\Post');
 996       }
 997   }
 998
 999现在,当你更新一个 `Comment` 时,对应父级 `Post` 模型的 `updated_at` 字段也会被同时更新,使其更方便得知何时让一个 `Post` 模型的缓存失效:
 1000
 1001   $comment = App\Comment::find(1);
 1002
 1003   $comment->text = '编辑了这条评论!';
 1004
 1005   $comment->save();
 1006
 1007## 译者署名
 1008| 用户名 | 头像 | 职能 | 签名 |
 1009|---|---|---|---|
 1010| [@baooab](https://learnku.com/users/17319) | <img class="avatar-66 rm-style" src="https://cdn.learnku.com/uploads/images/201708/11/17319/KbHzLBdgHs.png?imageView2/1/w/100/h/100"> | 翻译 | 我在 [这儿](https://github.com/baooab/) |
 1011
 1012
 1013---
 1014
 1015> {note} 欢迎任何形式的转载,但请务必注明出处,尊重他人劳动共创开源社区。
 1016>
 1017> 转载请注明:本文档由 Laravel China 社区 [laravel-china.org](https://laravel-china.org) 组织翻译,详见 [翻译召集帖](https://learnku.com/laravel/t/5756/laravel-55-document-translation-call-come-and-join-the-translation)。
 1018>
10191019> 文档永久地址: https://learnku.com/docs/laravel