菜单链接,人称统一

修改理由:
相关信息:
- 类型:文档文章
- 文章: 模型关联
- 文档: 《Laravel 10 中文文档(10.x)》
此投稿由 Vbm_pz 在 2年前 合并。
标题修改:
内容修改:
Old | New | Differences |
---|---|---|
1 | ||
2 | 1 | # Eloquent: 关联 |
3 | 2 | |
4 | 3 | - [简介](#introduction) | … | … |
48 | 47 | |
49 | 48 | <div class="content-list" markdown="1"> |
50 | 49 | |
51 | - [一对一](# | |
52 | - [一对多](# | |
53 | - [多对多](# | |
54 | - [远程一对一](# | |
55 | - [远程一对多](# | |
56 | - [多态一对一](# | |
57 | - [多态一对多](# | |
58 | - [多态多对多](# | |
50 | - [一对一](#one-to-one) | |
51 | - [一对多](#one-to-many) | |
52 | - [多对多](#many-to-many) | |
53 | - [远程一对一](#has-one-through) | |
54 | - [远程一对多](#has-many-through) | |
55 | - [多态一对一](#one-to-one-polymorphic-relations) | |
56 | - [多态一对多](#one-to-many-polymorphic-relations) | |
57 | - [多态多对多](#many-to-many-polymorphic-relations) | |
59 | 58 | |
60 | 59 | </div> |
61 | 60 | |
62 | ||
63 | ||
61 | ||
62 | ||
64 | 63 | <a name="defining-relationships"></a> |
65 | 64 | ## 定义关联 |
66 | 65 | … | … |
101 | 100 | |
102 | 101 | return $this->hasOne(Phone::class, 'foreign_key'); |
103 | 102 | |
104 | ||
105 | ||
103 | ||
104 | ||
106 | 105 | 另外,Eloquent 假设外键的值是与父模型的主键(Primary Key)相同的。换句话说,Eloquent 将会通过 `Phone` 记录的 `user_id` 列中查找与用户表的 `id` 列相匹配的值。如果你希望使用自定义的主键值,而不是使用 `id` 或者模型中的 `$primaryKey` 属性,你可以给 `hasOne` 方法传递第三个参数: |
107 | 106 | |
108 | 107 | return $this->hasOne(Phone::class, 'foreign_key', 'local_key'); | … | … |
142 | 141 | return $this->belongsTo(User::class, 'foreign_key'); |
143 | 142 | } |
144 | 143 | |
145 | ||
146 | ||
144 | ||
145 | ||
147 | 146 | 如果父模型的主键未使用 `id` 作为字段名,或者你想要使用其他的字段来匹配相关联的模型,那么你可以向 `belongsTo` 方法传递第三个参数,这个参数是在父模型中自己定义的字段名称: |
148 | 147 | |
149 | 148 | /** | … | … |
189 | 188 | // ... |
190 | 189 | } |
191 | 190 | |
192 | ||
193 | ||
191 | ||
192 | ||
194 | 193 | 由于所有的关系都可以看成是查询构造器,所以你也可以通过链式调用的方式,在 `comments` 方法中继续添加条件约束: |
195 | 194 | |
196 | 195 | $comment = Post::find(1)->comments() | … | … |
237 | 236 | 在上面这个例子中,Eloquent 将会尝试寻找 `Post` 模型中的 `id` 字段与 `Comment` 模型中的 `post_id` 字段相匹配。 |
238 | 237 | |
239 | 238 | Eloquent 通过检查关联方法的名称,从而在关联方法名称后面加上 `_` ,然后再加上父模型 (Post)的主键名称,以此来作为默认的外键名。因此,在上面这个例子中,Eloquent 将会默认 `Post` 模型在 `comments` 表中的外键是 `post_id`。 |
240 | ||
241 | ||
239 | ||
240 | ||
242 | 241 | 但是,如果你的外键不遵循这种约定的话,那么你可以传递一个自定义的外键名来作为 `belongsTo` 方法的第二个参数: |
243 | 242 | |
244 | 243 | /** | … | … |
294 | 293 | }); |
295 | 294 | } |
296 | 295 | |
297 | ||
298 | ||
296 | ||
297 | ||
299 | 298 | <a name="querying-belongs-to-relationships"></a> |
300 | 299 | #### 查询所属关系 |
301 | 300 | … | … |
334 | 333 | } |
335 | 334 | ``` |
336 | 335 | |
337 | ||
338 | ||
336 | ||
337 | ||
339 | 338 | 同样,你可以定义一个方法来检索 「oldest」或第一个相关模型: |
340 | 339 | |
341 | 340 | ```php | … | … |
387 | 386 | } |
388 | 387 | ``` |
389 | 388 | |
390 | ||
391 | ||
389 | ||
390 | ||
392 | 391 | <a name="has-one-through"></a> |
393 | 392 | ### 远程一对一 |
394 | 393 | … | … |
442 | 441 | return $this->throughCars()->hasOwner(); |
443 | 442 | ``` |
444 | 443 | |
445 | ||
446 | ||
444 | ||
445 | ||
447 | 446 | <a name="has-one-through-key-conventions"></a> |
448 | 447 | #### 键名约定 |
449 | 448 | … | … |
495 | 494 | environment_id - integer |
496 | 495 | commit_hash - string |
497 | 496 | |
498 | ||
499 | ||
497 | ||
498 | ||
500 | 499 | 既然我们已经检查了关系的表结构,现在让我们在 `Project` 模型上定义该关系: |
501 | 500 | |
502 | 501 | <?php | … | … |
550 | 549 | } |
551 | 550 | } |
552 | 551 | |
553 | ||
554 | ||
552 | ||
553 | ||
555 | 554 | 或者,如前所述,如果涉及关系的相关关系已经在所有模型上定义,你可以通过调用 `through` 方法并提供这些关系的名称来定义「has-many-through」关系。这种方法的优点是可以重复使用已经定义在现有关系上的键约定: |
556 | 555 | |
557 | 556 | ```php | … | … |
586 | 585 | user_id - integer |
587 | 586 | role_id - integer |
588 | 587 | |
589 | ||
590 | ||
588 | ||
589 | ||
591 | 590 | <a name="many-to-many-model-structure"></a> |
592 | 591 | #### 模型结构 |
593 | 592 | … | … |
633 | 632 | |
634 | 633 | return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id'); |
635 | 634 | |
636 | ||
637 | ||
635 | ||
636 | ||
638 | 637 | <a name="many-to-many-defining-the-inverse-of-the-relationship"></a> |
639 | 638 | #### 定义反向关联 |
640 | 639 | … | … |
679 | 678 | |
680 | 679 | return $this->belongsToMany(Role::class)->withPivot('active', 'created_by'); |
681 | 680 | |
682 | ||
683 | ||
681 | ||
682 | ||
684 | 683 | 如果你想让中间表自动维护 `created_at` 和 `updated_at` 时间戳,那么在定义关联时附加上 `withTimestamps` 方法即可: |
685 | 684 | |
686 | 685 | return $this->belongsToMany(Role::class)->withTimestamps(); | … | … |
737 | 736 | ->as('subscriptions') |
738 | 737 | ->wherePivotNotNull('expired_at'); |
739 | 738 | |
740 | ||
741 | ||
739 | ||
740 | ||
742 | 741 | <a name="ordering-queries-via-intermediate-table-columns"></a> |
743 | 742 | ### 通过中间表字段排序 |
744 | 743 | … | … |
791 | 790 | |
792 | 791 | |
793 | 792 | <a name="custom-pivot-models-and-incrementing-ids"></a> |
794 | ||
795 | ||
793 | ||
794 | ||
796 | 795 | #### 自定义中间模型和自增 ID |
797 | 796 | |
798 | 797 | 如果你用一个自定义的中继模型定义了多对多的关系,而且这个中继模型拥有一个自增的主键,你应当确保这个自定义中继模型类中定义了一个 `incrementing` 属性且其值为 `true`。 | … | … |
833 | 832 | |
834 | 833 | 请注意 `images` 表上的 `imageable_id` 和 `imageable_type` 两列。`imageable_id` 列将包含帖子或用户的ID值,而 `imageable_type` 列将包含父模型的类名。`imageable_type` 列用于 Eloquent 在访问 `imageable` 关联时确定要返回哪种类型的父模型。在本例中,该列将包含 `App\Models\Post` 或 `App\Models\User`。 |
835 | 834 | |
836 | ||
837 | ||
835 | ||
836 | ||
838 | 837 | <a name="one-to-one-polymorphic-model-structure"></a> |
839 | 838 | #### 模型结构 |
840 | 839 | … | … |
920 | 919 | return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id'); |
921 | 920 | } |
922 | 921 | |
923 | ||
924 | ||
922 | ||
923 | ||
925 | 924 | <a name="one-to-many-polymorphic-relations"></a> |
926 | 925 | ### 一对多(多态) |
927 | 926 | … | … |
1010 | 1009 | // ... |
1011 | 1010 | } |
1012 | 1011 | |
1013 | ||
1014 | ||
1012 | ||
1013 | ||
1015 | 1014 | 你还可以通过访问执行对 `morphTo` 的调用的方法名来从多态模型获取其所属模型。在我们的例子中,这就是 `Comment` 模型上的 `commentable` 方法。因此,我们将以动态属性的形式访问该方法: |
1016 | 1015 | |
1017 | 1016 | use App\Models\Comment; | … | … |
1050 | 1049 | ``` |
1051 | 1050 | |
1052 | 1051 | 默认情况下,`latestOfMany` 和 `oldestOfMany` 方法将基于模型的主键(必须可排序)检索最新或最旧的相关模型。但是,有时你可能希望使用不同的排序条件从较大的关系中检索单个模型。 |
1053 | ||
1054 | ||
1052 | ||
1053 | ||
1055 | 1054 | 例如,使用 `ofMany` 方法,可以检索用户点赞最高的图像。`ofMany` 方法接受可排序列作为其第一个参数,以及在查询相关模型时应用哪个聚合函数(`min` 或 `max`): |
1056 | 1055 | |
1057 | 1056 | ```php | … | … |
1101 | 1100 | |
1102 | 1101 | 接下来,我们可以定义模型之间的关联。`Post` 和 `Video` 模型都将包含一个 `tags` 方法,该方法调用了基础 Eloquent 模型类提供的 `morphToMany` 方法。 |
1103 | 1102 | |
1104 | ||
1105 | ||
1103 | ||
1104 | ||
1106 | 1105 | `morphToMany` 方法接受相关模型的名称以及“关系名称”。根据我们分配给中间表的名称及其包含的键,我们将将关系称为 「taggable」: |
1107 | 1106 | |
1108 | 1107 | <?php | … | … |
1159 | 1158 | <a name="many-to-many-polymorphic-retrieving-the-relationship"></a> |
1160 | 1159 | #### 获取关联 |
1161 | 1160 | |
1162 | 一旦定义了数据库表和模型, | |
1161 | 一旦定义了数据库表和模型,你就可以通过模型访问关系。 例如,要访问帖子的所有标签,你可以使用 `tags` 动态关系属性: | |
1163 | 1162 | |
1164 | 1163 | use App\Models\Post; |
1165 | 1164 | … | … |
1169 | 1168 | // ... |
1170 | 1169 | } |
1171 | 1170 | |
1172 | ||
1173 | ||
1171 | ||
1172 | ||
1174 | 1173 | 还可以访问执行 `morphedByMany` 方法调用的方法名来从多态模型获取其所属模型。在这个示例中,就是 `Tag` 模型的 `posts` 或 `videos` 方法。可以像动态属性一样访问这些方法: |
1175 | 1174 | |
1176 | 1175 | use App\Models\Tag; | … | … |
1212 | 1211 | > **注意** |
1213 | 1212 | > 向现有应用程序添加「变形映射」时,数据库中仍包含完全限定类的每个可变形 `*_type` 列值都需要转换为其「映射」名称。 |
1214 | 1213 | |
1215 | ||
1216 | ||
1214 | ||
1215 | ||
1217 | 1216 | <a name="dynamic-relationships"></a> |
1218 | 1217 | ### 动态关联 |
1219 | 1218 | … | … |
1264 | 1263 | |
1265 | 1264 | $user->posts()->where('active', 1)->get(); |
1266 | 1265 | |
1267 | ||
1268 | ||
1266 | ||
1267 | ||
1269 | 1268 | 你可以在关联上使用任意的 [查询构造器]([查询构造器 | 《Laravel 10 中文文档》 (learnku.com)](https://learnku.com/docs/laravel/10.x/queriesmd/14883)) 方法,所以一定要阅读查询构造器的文档,了解它的所有方法,这会对你非常有用。 |
1270 | 1269 | |
1271 | 1270 | <a name="chaining-orwhere-clauses-after-relationships"></a> | … | … |
1318 | 1317 | // ... |
1319 | 1318 | } |
1320 | 1319 | |
1321 | ||
1322 | ||
1320 | ||
1321 | ||
1323 | 1322 | 动态属性是 「懒加载」 的,只有实际访问到才会加载关联数据。因此,通常用 [预加载](#eager-loading) 来准备模型需要用到的关联数据。预加载能大量减少因加载模型关联执行的 SQL 语句。 |
1324 | 1323 | |
1325 | 1324 | <a name="querying-relationship-existence"></a> | … | … |
1359 | 1358 | > **注意** |
1360 | 1359 | > Eloquent 目前不支持跨数据库查询关系是否存在。 这些关系必须存在于同一数据库中。 |
1361 | 1360 | |
1362 | ||
1363 | ||
1361 | ||
1362 | ||
1364 | 1363 | <a name="inline-relationship-existence-queries"></a> |
1365 | 1364 | #### 内联关系存在查询 |
1366 | 1365 | … | … |
1401 | 1400 | $query->where('banned', 0); |
1402 | 1401 | })->get(); |
1403 | 1402 | |
1404 | ||
1405 | ||
1403 | ||
1404 | ||
1406 | 1405 | <a name="querying-morph-to-relationships"></a> |
1407 | 1406 | ### 查询多态关联 |
1408 | 1407 | … | … |
1456 | 1455 | $query->where('title', 'like', 'foo%'); |
1457 | 1456 | })->get(); |
1458 | 1457 | |
1459 | ||
1460 | ||
1458 | ||
1459 | ||
1461 | 1460 | <a name="aggregating-related-models"></a> |
1462 | 1461 | ## 聚合相关模型 |
1463 | 1462 | … | … |
1514 | 1513 | $query->where('rating', 5); |
1515 | 1514 | }]) |
1516 | 1515 | |
1517 | ||
1518 | ||
1516 | ||
1517 | ||
1519 | 1518 | <a name="relationship-counting-and-custom-select-statements"></a> |
1520 | 1519 | #### 关联计数和自定义查询字段 |
1521 | 1520 | … | … |
1567 | 1566 | |
1568 | 1567 | 在这个例子中,我们假设 `Photo` 和 `Post` 模型可以创建 `ActivityFeed` 模型。 我们将假设 `ActivityFeed`模型定义了一个名为`parentable`的多态关联关系,它允许我们为给定的 `ActivityFeed` 实例检索父级 `Photo` 或 `Post` 模型。 此外,让我们假设 `Photo` 模型有很多 `Tag` 模型、`Post` 模型有很多 `Comment` 模型。 |
1569 | 1568 | |
1570 | ||
1571 | ||
1569 | ||
1570 | ||
1572 | 1571 | 假如我们想要检索 `ActivityFeed` 实例并为每个 `ActivityFeed` 实例预先加载 `parentable` 父模型。 此外,我们想要检索与每张父照片关联的标签数量以及与每个父帖子关联的评论数量: |
1573 | 1572 | |
1574 | 1573 | use Illuminate\Database\Eloquent\Relations\MorphTo; | … | … |
1626 | 1625 | echo $book->author->name; |
1627 | 1626 | } |
1628 | 1627 | |
1629 | ||
1630 | ||
1628 | ||
1629 | ||
1631 | 1630 | 该循环将执行一个查询以检索数据库表中的所有书籍,然后对每本书执行另一个查询以检索该书的作者。 因此,如果我们有 25 本书,上面的代码将运行 26 个查询:一个查询原本的书籍信息,另外 25 个查询来检索每本书的作者。 |
1632 | 1631 | |
1633 | 1632 | 值得庆幸的是,我们可以使用预加载将这个操作减少到两个查询。 在构建查询时,可以使用 `with` 方法指定应该预加载哪些关系: | … | … |
1669 | 1668 | ], |
1670 | 1669 | ])->get(); |
1671 | 1670 | |
1672 | ||
1673 | ||
1671 | ||
1672 | ||
1674 | 1673 | <a name="nested-eager-loading-morphto-relationships"></a> |
1675 | 1674 | #### 嵌套预加载 `morphTo` 关联 |
1676 | 1675 | … | … |
1717 | 1716 | > **注意** |
1718 | 1717 | > 使用此功能时,应始终在要检索的列列表中包括 `id` 列和任何相关的外键列。 |
1719 | 1718 | |
1720 | ||
1721 | ||
1719 | ||
1720 | ||
1722 | 1721 | <a name="eager-loading-by-default"></a> |
1723 | 1722 | #### 默认预加载 |
1724 | 1723 | … | … |
1768 | 1767 | <a name="constraining-eager-loads"></a> |
1769 | 1768 | ### 约束预加载 |
1770 | 1769 | |
1771 | 有时,你可能希望预加载一个关联,同时为预加载查询添加额外查询条件。 | |
1770 | 有时,你可能希望预加载一个关联,同时为预加载查询添加额外查询条件。你可以通过将一个关联数组传递给 `with` 方法来实现这一点,其中数组键是关联名称,数组值是一个闭包,它为预先加载查询添加了额外的约束: | |
1772 | 1771 | |
1773 | 1772 | use App\Models\User; |
1774 | 1773 | … | … |
1785 | 1784 | > **注意** |
1786 | 1785 | > 在约束预加载时,不能使用 limit 和 take 查询构造器方法。 |
1787 | 1786 | |
1788 | ||
1789 | ||
1787 | ||
1788 | ||
1790 | 1789 | <a name="constraining-eager-loading-of-morph-to-relationships"></a> |
1791 | 1790 | #### `morphTo` 关联预加载添加约束 |
1792 | 1791 | … | … |
1833 | 1832 | $books->load('author', 'publisher'); |
1834 | 1833 | } |
1835 | 1834 | |
1836 | ||
1837 | ||
1835 | ||
1836 | ||
1838 | 1837 | 如果要在渴求式加载的查询语句中进行条件约束,可以通过数组的形式去加载,键为对应的关联关系,值为 `Closure` 闭包函数,该闭包的参数为一个查询实例: |
1839 | 1838 | |
1840 | 1839 | $author->load(['books' => function (Builder $query) { | … | … |
1848 | 1847 | <a name="nested-lazy-eager-loading-morphto"></a> |
1849 | 1848 | #### 嵌套延迟预加载 & `morphTo` |
1850 | 1849 | |
1851 | 如果要预加载 `morphTo` 关系,以及该关系可能返回的各种实体上的嵌套关系, | |
1850 | 如果要预加载 `morphTo` 关系,以及该关系可能返回的各种实体上的嵌套关系,你可以使用 `loadMorph` 方法。 | |
1852 | 1851 | |
1853 | 1852 | 这个方法接受 `morphTo` 关系的名称作为它的第一个参数,第二个参数接收模型数组、关系数组。例如: |
1854 | 1853 | … | … |
1880 | 1879 | Post::class => ['author'], |
1881 | 1880 | ]); |
1882 | 1881 | |
1883 | ||
1884 | ||
1882 | ||
1883 | ||
1885 | 1884 | <a name="preventing-lazy-loading"></a> |
1886 | 1885 | ### 防止延迟加载 |
1887 | 1886 | … | … |
1930 | 1929 | |
1931 | 1930 | $post->comments()->save($comment); |
1932 | 1931 | |
1933 | ||
1934 | ||
1932 | ||
1933 | ||
1935 | 1934 | 注意,我们没有将 `comments` 关联作为动态属性访问,相反,我们调用了 `comments` 方法来来获得关联实例, `save` 方法会自动添加适当的 `post_id` 值到新的 `Comment` 模型中。 |
1936 | 1935 | |
1937 | 1936 | 如果需要保存多个关联模型,你可以使用 `saveMany` 方法: | … | … |
1981 | 1980 | 'message' => 'A new comment.', |
1982 | 1981 | ]); |
1983 | 1982 | |
1984 | ||
1985 | ||
1983 | ||
1984 | ||
1986 | 1985 | 你还可以使用 `createMany` 方法去创建多个关联模型: |
1987 | 1986 | |
1988 | 1987 | $post = Post::find(1); | … | … |
2012 | 2011 | <a name="updating-belongs-to-relationships"></a> |
2013 | 2012 | ### Belongs To 关联 |
2014 | 2013 | |
2015 | 如果 | |
2014 | 如果你想将子模型分配给新的父模型,你可以使用 `associate` 方法。在这个例子中,`User` 模型定义了一个与 `Account` 模型的 `belongsTo` 关系。 这个 `associate` 方法将在子模型上设置外键: | |
2016 | 2015 | |
2017 | 2016 | use App\Models\Account; |
2018 | 2017 | … | … |
2022 | 2021 | |
2023 | 2022 | $user->save(); |
2024 | 2023 | |
2025 | 要从子模型中删除父模型, | |
2024 | 要从子模型中删除父模型,你可以使用 `dissociate` 方法。此方法会将关联外键设置为 `null`: | |
2026 | 2025 | |
2027 | 2026 | $user->account()->dissociate(); |
2028 | 2027 | … | … |
2042 | 2041 | |
2043 | 2042 | $user->roles()->attach($roleId); |
2044 | 2043 | |
2045 | ||
2046 | ||
2044 | ||
2045 | ||
2047 | 2046 | 在将关系附加到模型时,还可以传递一组要插入到中间表中的附加数据: |
2048 | 2047 | |
2049 | 2048 | $user->roles()->attach($roleId, ['expires' => $expires]); | … | … |
2078 | 2077 | |
2079 | 2078 | $user->roles()->sync([1 => ['expires' => true], 2, 3]); |
2080 | 2079 | |
2081 | 如果 | |
2080 | 如果你想为每个同步的模型 IDs 插入相同的中间表,你可以使用 `syncWithPivotValues` 方法: | |
2082 | 2081 | |
2083 | 2082 | $user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]); |
2084 | 2083 | … | … |
2086 | 2085 | |
2087 | 2086 | $user->roles()->syncWithoutDetaching([1, 2, 3]); |
2088 | 2087 | |
2089 | ||
2090 | ||
2088 | ||
2089 | ||
2091 | 2090 | <a name="toggling-associations"></a> |
2092 | 2091 | #### 切换关联 |
2093 | 2092 | … | … |
2095 | 2094 | |
2096 | 2095 | $user->roles()->toggle([1, 2, 3]); |
2097 | 2096 | |
2098 | ||
2097 | 你还可以将附加的中间表值与ID 一起传递: | |
2099 | 2098 | |
2100 | 2099 | $user->roles()->toggle([ |
2101 | 2100 | 1 => ['expires' => true], | … | … |
2146 | 2145 | } |
2147 | 2146 | |
2148 | 2147 | > **注意**:只有使用 `Eloquent` 的 `save` 方法更新子模型时,才会触发更新父模型时间戳。 |
2148 | ||
2149 | 2149 | |
2150 |