数据库关系

介绍

数据库表通常彼此相关。 例如,博客文章可能有很多评论,或者订单可能与放置它的用户有关。 October使管理和处理这些关系变得容易,并支持几种不同类型的关系。

注意: 如果要在查询中选择特定列并且还要加载关系,则需要确保包含键控数据的列(即idforeign_key等)包含在select语句中。 否则,October无法连接关系。

定义关系

模型关系定义为模型类的属性。 定义关系的一个例子:

  1. class User extends Model
  2. {
  3. public $hasMany = [
  4. 'posts' => 'Acme\Blog\Models\Post'
  5. ]
  6. }

像模型本身这样的关系也可以作为强大的查询构建器,访问关系作为函数提供强大的方法链接和查询功能。 例如:

  1. $user->posts()->where('is_active', true)->get();

关联关系也可以通过属性的方式访问

  1. $user->posts;

详细定义

每个定义都可以是一个数组,其中键是关系名称,值是详细数组。 detail数组的第一个值始终是相关的模型类名,所有其他值都是必须具有键名的参数。

  1. public $hasMany = [
  2. 'posts' => ['Acme\Blog\Models\Post', 'delete' => true]
  3. ];

以下是可与所有关系一起使用的参数:

参数 描述
order 排序多个记录的顺序。
conditions 使用raw where查询语句过滤关系。
scope 使用提供的范围方法过滤关系。
push 如果设置为false,则不会通过push保存此关系,默认值为:true。
delete 如果设置为true,则删除主模型或删除关系时将删除相关模型,默认值为false。
count 如果设置为true,则结果仅包含count列,用于计算关系,默认值:false。

使用 ordercondition的示例过滤器:

  1. public $belongsToMany = [
  2. 'categories' => [
  3. 'Acme\Blog\Models\Category',
  4. 'order' => 'name desc',
  5. 'conditions' => 'is_active = 1'
  6. ]
  7. ];

使用scope的示例过滤器:

  1. class Post extends Model
  2. {
  3. public $belongsToMany = [
  4. 'categories' => [
  5. 'Acme\Blog\Models\Category',
  6. 'scope' => 'isActive'
  7. ]
  8. ];
  9. }
  10. class Category extends Model
  11. {
  12. public function scopeIsActive($query)
  13. {
  14. return $query->where('is_active', true)->orderBy('name', 'desc');
  15. }
  16. }

使用count的示例过滤器:

  1. public $belongsToMany = [
  2. 'users' => ['Backend\Models\User'],
  3. 'users_count' => ['Backend\Models\User', 'count' => true]
  4. ];

关系类型

可以使用以下关系类型:

一对一

一对一的关系是一种非常基本的关系。 例如,“用户”模型可能与一个“电话”相关联。 为了定义这种关系,我们在User模型的$hasOne属性中添加一个phone条目。

  1. <?php namespace Acme\Blog\Models;
  2. use Model;
  3. class User extends Model
  4. {
  5. public $hasOne = [
  6. 'phone' => 'Acme\Blog\Models\Phone'
  7. ];
  8. }

一旦定义了关系,我们就可以使用相同名称的model属性检索相关记录。 这些属性是动态的,允许您像访问模型上的常规属性一样访问它们:

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

该模型基于模型名称假定关系的外键。 在这种情况下,自动假设Phone模型具有user_id外键。 如果要覆盖此约定,可以将key参数传递给定义:

  1. public $hasOne = [
  2. 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id']
  3. ];

此外,该模型假定外键应具有与父级的“id”列匹配的值。 换句话说,它将在Phone记录的user_id列中查找用户的id列的值。 如果您希望关系使用除id以外的值,则可以将otherKey参数传递给定义:

  1. public $hasOne = [
  2. 'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id', 'otherKey' => 'my_id']
  3. ];

定义关系的倒数

现在我们可以从User访问Phone模型了。 让我们做相反的事情并在Phone模型上定义一个关系,让我们访问拥有手机的User。 我们可以使用$belongsTo属性定义hasOne关系的反转:

  1. class Phone extends Model
  2. {
  3. public $belongsTo = [
  4. 'user' => 'Acme\Blog\Models\User'
  5. ];
  6. }

在上面的例子中,模型将尝试匹配Phone模型中的user_idUser模型上的id。 它通过检查关系定义的名称并使用_id为名称添加后缀来确定默认外键名称。 但是,如果Phone模型上的外键不是user_id,您可以使用定义上的key参数传递自定义键名:

  1. public $belongsTo = [
  2. 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id']
  3. ];

如果您的父模型不使用id作为其主键,或者您希望将子模型连接到不同的列,则可以将otherKey参数传递给定义父表的自定义键的定义:

  1. public $belongsTo = [
  2. 'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id', 'otherKey' => 'my_id']
  3. ];

默认模型

belongsTo关系允许您定义一个默认模型,如果给定的关系为“null”,将返回该默认模型。 此模式通常称为空对象模式,可以帮助删除代码中的条件检查。 在下面的示例中,如果没有“user”附加到帖子,user关系将返回一个空的Acme\Blog\Models\User模型:

  1. public $belongsTo = [
  2. 'user' => ['Acme\Blog\Models\User', 'default' => true]
  3. ];

要使用属性填充默认模型,可以将数组传递给default参数:

  1. public $belongsTo = [
  2. 'user' => [
  3. 'Acme\Blog\Models\User',
  4. 'default' => ['name' => 'Guest']
  5. ]
  6. ];

一对多

一对多关系用于定义单个模型拥有任何数量的其他模型的关系。 例如,博客文章可能有无数的评论。 与所有其他关系一样,定义了一对多关系,在模型上的$hasMany属性中添加一个条目:

  1. class Post extends Model
  2. {
  3. public $hasMany = [
  4. 'comments' => 'Acme\Blog\Models\Comment'
  5. ];
  6. }

请记住,模型将自动确定Comment模型上的正确外键列。 按照惯例,它将采用拥有模型的下划线命名名称,并以_id为后缀。 因此,对于这个例子,我们可以假设Comment模型上的外键是post_id

一旦定义了关系,我们就可以通过访问comments属性来访问Comment集合。 请记住,由于模型提供了“动态属性”,我们可以像访问模型中的属性一样访问关系:

  1. $comments = Post::find(1)->comments;
  2. foreach ($comments as $comment) {
  3. //
  4. }

当然,由于所有关系也充当查询构建器,您可以通过调用comments方法并继续将条件链接到查询来添加更多约束以检索哪些Comment

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

hasOne关系一样,您也可以通过分别在定义上传递keyotherKey参数来覆盖外键和本地键:

  1. public $hasMany = [
  2. 'comments' => ['Acme\Blog\Models\Comment', 'key' => 'my_post_id', 'otherKey' => 'my_id']
  3. ];

定义关系的倒数

现在我们可以访问所有帖子的评论,让我们定义一个关系,以允许评论访问其父帖子。 要定义hasMany关系的反转,请在子模型上定义$belongsTo属性:

  1. class Comment extends Model
  2. {
  3. public $belongsTo = [
  4. 'post' => 'Acme\Blog\Models\Post'
  5. ];
  6. }

一旦定义了关系,我们就可以通过访问post“动态属性”来检索CommentPost模型:

  1. $comment = Comment::find(1);
  2. echo $comment->post->title;

在上面的例子中,模型将尝试将Comment模型中的post_idPost模型上的id相匹配。 它通过检查关系的名称并使用_id对其进行后缀来确定默认外键名称。 但是,如果Comment模型上的外键不是post_id,则可以使用key参数传递自定义键名:

  1. public $belongsTo = [
  2. 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id']
  3. ];

如果您的父模型不使用id作为其主键,或者您希望将子模型连接到不同的列,则可以将otherKey参数传递给定义父表的自定义键的定义:

  1. public $belongsTo = [
  2. 'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id', 'otherKey' => 'my_id']
  3. ];

多对多

多对多关系比hasOnehasMany关系稍微复杂一些。 这种关系的一个示例是具有许多角色的用户,其中角色也由其他用户共享。 例如,许多用户可能具有“管理员”的角色。 要定义这种关系,需要三个数据库表:usersrolesrole_userrole_user表是从相关模型名称的字母顺序派生而来的,包含user_idrole_id列。

下面的示例显示了用于创建连接表的数据库表结构

  1. Schema::create('role_user', function($table)
  2. {
  3. $table->integer('user_id')->unsigned();
  4. $table->integer('role_id')->unsigned();
  5. $table->primary(['user_id', 'role_id']);
  6. });

定义了多对多关系,在模型类的$belongsToMany属性中添加一个条目。 例如,让我们在User模型上定义roles方法:

  1. class User extends Model
  2. {
  3. public $belongsToMany = [
  4. 'roles' => 'Acme\Blog\Models\Role'
  5. ];
  6. }

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

  1. $user = User::find(1);
  2. foreach ($user->roles as $role) {
  3. //
  4. }

当然,像所有其他关系类型一样,您可以调用roles方法继续将查询约束链接到关系上:

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

如前所述,为了确定关系的连接表的表名,模型将按字母顺序连接两个相关的模型名称。 但是,您可以自由地覆盖此约定。 您可以通过将table参数传递给belongsToMany定义来实现:

  1. public $belongsToMany = [
  2. 'roles' => ['Acme\Blog\Models\Role', 'table' => 'acme_blog_role_user']
  3. ];

除了自定义连接表的名称之外,您还可以通过将其他参数传递给belongsToMany定义来自定义表上键的列名。 key参数是您定义关系的模型的外键名称,而otherKey参数是您要加入的模型的外键名称:

  1. public $belongsToMany = [
  2. 'roles' => [
  3. 'Acme\Blog\Models\Role',
  4. 'table' => 'acme_blog_role_user',
  5. 'key' => 'my_user_id',
  6. 'otherKey' => 'my_role_id'
  7. ]
  8. ];

定义关系的倒数

要定义多对多关系的反转,只需在相关模型上放置另一个$belongsToMany属性。 要继续我们的用户角色示例,让我们在Role模型上定义users关系:

  1. class Role extends Model
  2. {
  3. public $belongsToMany = [
  4. 'users' => 'Acme\Blog\Models\User'
  5. ];
  6. }

正如您所看到的,该关系的定义与其“User”对应关系完全相同,只是简单地引用了Acme\Blog\Models\User 模型。 由于我们正在重用$belongsToMany属性,因此在定义多对多关系的反转时,所有常用的表和键自定义选项都可用。

检索中间表列

正如您已经了解的那样,处理多对多关系需要存在中间连接表。 模型提供了一些与此表交互的非常有用的方法。 例如,假设我们的User对象有许多与它相关的Role对象。 访问此关系后,我们可以使用模型上的pivot属性访问中间表:

  1. $user = User::find(1);
  2. foreach ($user->roles as $role) {
  3. echo $role->pivot->created_at;
  4. }

请注意,我们检索的每个Role模型都会自动分配一个pivot属性。 此属性包含表示中间表的模型,可以像任何其他模型一样使用。

默认情况下,只有模型键才会出现在pivot对象上。 如果数据透视表包含额外属性,则必须在定义关系时指定它们:

  1. public $belongsToMany = [
  2. 'roles' => [
  3. 'Acme\Blog\Models\Role',
  4. 'pivot' => ['column1', 'column2']
  5. ]
  6. ];

如果您希望数据透视表自动维护created_atupdated_at时间戳,请在关系定义中使用timestamps参数:

  1. public $belongsToMany = [
  2. 'roles' => ['Acme\Blog\Models\Role', 'timestamps' => true]
  3. ];

这些是belongsToMany关系支持的参数:

参数 描述
table 连接表的名称。
key 定义模型的键列名称。
otherKey 相关模型的键列名称。
pivot 在连接表中找到的数据透视列数组,属性可通过$model-> pivot获得。
pivotModel 指定在访问数据透视关系时要返回的自定义模型类。 默认为October\Rain\Database\Pivot
timestamps 如果为true,则连接表应包含created_atupdated_at列。 默认值:false

Has Many Through

has-many-through关系为通过中间关系访问远程关系提供了方便的捷径。 例如,“Country”模型可能通过中间“User”模型具有许多“Post”模型。 在此示例中,您可以轻松收集给定国家/地区的所有博客帖子。 让我们看一下定义这种关系所需的表:

  1. countries
  2. id - integer
  3. name - string
  4. users
  5. id - integer
  6. country_id - integer
  7. name - string
  8. posts
  9. id - integer
  10. user_id - integer
  11. title - string

虽然posts不包含country_id列,但hasManyThrough关系通过$country-> posts提供对国家帖子的访问。 要执行此查询,模型会检查中间users表上的country_id。 找到匹配的用户ID后,它们用于查询posts表。

现在我们已经检查了关系的表结构,让我们在Country模型上定义它:

  1. class Country extends Model
  2. {
  3. public $hasManyThrough = [
  4. 'posts' => [
  5. 'Acme\Blog\Models\Post',
  6. 'through' => 'Acme\Blog\Models\User'
  7. ],
  8. ];
  9. }

传递给$hasManyThrough关系的第一个参数是我们希望访问的最终模型的名称,而through参数是中间模型的名称。

执行关系查询时将使用典型的外键约定。 如果您想自定义关系的键,可以将它们作为keythroughKey参数传递给$hasManyThrough定义。 key参数是中间模型上的外键的名称,而throughKey参数是最终模型上的外键的名称。

  1. public $hasManyThrough = [
  2. 'posts' => [
  3. 'Acme\Blog\Models\Post',
  4. 'key' => 'my_country_id',
  5. 'through' => 'Acme\Blog\Models\User',
  6. 'throughKey' => 'my_user_id'
  7. ],
  8. ];

多态关系

表结构

多态关系允许模型在单个关联上属于多个其他模型。 例如,假设您要为员工和产品存储照片。 使用多态关系,您可以为这两种情况使用单个“照片”表。 首先,让我们检查构建这种关系所需的表结构:

  1. staff
  2. id - integer
  3. name - string
  4. products
  5. id - integer
  6. price - integer
  7. photos
  8. id - integer
  9. path - string
  10. imageable_id - integer
  11. imageable_type - string

需要注意的两个重要的列是photos表上的imageable_idimageable_type列。 imageable_id列将包含拥有人员或产品的ID值,而imageable_type列将包含拥有模型的类名。 imageable_type列是ORM如何确定在访问imageable关系时要返回的拥有模型的“类型”。

模型结构

接下来,让我们检查构建此关系所需的模型定义:

  1. class Photo extends Model
  2. {
  3. public $morphTo = [
  4. 'imageable' => []
  5. ];
  6. }
  7. class Staff extends Model
  8. {
  9. public $morphMany = [
  10. 'photos' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
  11. ];
  12. }
  13. class Product extends Model
  14. {
  15. public $morphMany = [
  16. 'photos' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
  17. ];
  18. }

检索多态关系

一旦定义了数据库表和模型,您就可以通过模型访问关系。 例如,要访问工作人员的所有照片,我们可以简单地使用photos动态属性:

  1. $staff = Staff::find(1);
  2. foreach ($staff->photos as $photo) {
  3. //
  4. }

您还可以通过访问morphTo关系的名称从多态模型中检索多态关系的所有者。 在我们的例子中,这是Photo模型中的imageable定义。 因此,我们将其作为动态属性访问:

  1. $photo = Photo::find(1);
  2. $imageable = $photo->imageable;

Photo模型上的imageable关系将返回StaffProduct实例,具体取决于拥有该照片的模型类型。

自定义多态类型

默认情况下,完全限定的类名用于存储相关的模型类型。 例如,给定上面的示例,其中Photo可能属于StaffProduct,默认的imageable_type值是’Acme\Blog\Models\StaffAcme\Blog\Models\Product `分别。

使用自定义多态类型可以将数据库与应用程序的内部结构分离。 您可以定义关系“变形图”以为每个模型而不是类名提供自定义名称:

  1. use October\Rain\Database\Relations\Relation;
  2. Relation::morphMap([
  3. 'staff' => 'Acme\Blog\Models\Staff',
  4. 'product' => 'Acme\Blog\Models\Product',
  5. ]);

插件注册文件boot方法中注册morphMap的最常见的地方。

多对多的多态关系

表结构

除了传统的多态关系,您还可以定义“多对多”多态关系。 例如,博客“Post”和“Video”模型可以与“Tag”模型共享多态关系。 使用多对多多态关系,您可以拥有一个在博客帖子和视频中共享的唯一标记列表。 首先,让我们检查表结构:

  1. posts
  2. id - integer
  3. name - string
  4. videos
  5. id - integer
  6. name - string
  7. tags
  8. id - integer
  9. name - string
  10. taggables
  11. tag_id - integer
  12. taggable_id - integer
  13. taggable_type - string

模型结构

接下来,我们准备定义模型上的关系。 PostVideo模型都将在基础模型类的$morphToMany属性中定义tags关系:

  1. class Post extends Model
  2. {
  3. public $morphToMany = [
  4. 'tags' => ['Acme\Blog\Models\Tag', 'name' => 'taggable']
  5. ];
  6. }

定义关系的倒数

接下来,在Tag模型上,您应该为每个相关模型定义关系。 因此,对于这个例子,我们将定义一个posts关系和一个videos关系:

  1. class Tag extends Model
  2. {
  3. public $morphedByMany = [
  4. 'posts' => ['Acme\Blog\Models\Post', 'name' => 'taggable'],
  5. 'videos' => ['Acme\Blog\Models\Video', 'name' => 'taggable']
  6. ];
  7. }

检索关系

一旦定义了数据库表和模型,您就可以通过模型访问关系。 例如,要访问帖子的所有标签,您只需使用tags动态属性:

  1. $post = Post::find(1);
  2. foreach ($post->tags as $tag) {
  3. //
  4. }

您还可以通过访问$morphedByMany属性中定义的关系名称,从多态模型中检索多态关系的所有者。 在我们的例子中,这是Tag模型上的postsvideos方法。 因此,您将作为动态属性访问这些关系:

  1. $tag = Tag::find(1);
  2. foreach ($tag->videos as $video) {
  3. //
  4. }

查询关系

由于可以通过函数调用所有类型的模型关系,因此可以调用这些函数来获取关系的实例,而无需实际执行关系查询。 此外,所有类型的关系也可用作查询构建器,允许您在最终针对数据库执行SQL之前继续将约束链接到关系查询。

For example, imagine a blog system in which a User model has many associated Post models:

  1. class User extends Model
  2. {
  3. public $hasMany = [
  4. 'posts' => ['Acme\Blog\Models\Post']
  5. ];
  6. }

通过关系方法访问

您可以使用posts方法查询 posts 关系并为关系添加其他约束。 这使您能够链接任何查询构建器方法的关系。

  1. $user = User::find(1);
  2. $posts = $user->posts()->where('is_active', 1)->get();
  3. $post = $user->posts()->first();

通过动态属性访问

如果您不需要为关系查询添加其他约束,则可以简单地访问该关系,就像它是属性一样。 例如,继续使用我们的UserPost示例模型,我们可以使用$user-> posts属性访问所有用户的帖子。

  1. $user = User::find(1);
  2. foreach ($user->posts as $post) {
  3. //...
  4. }

动态属性是“延迟加载”,这意味着它们只会在您实际访问它们时加载它们的关系数据。 因此,开发人员经常使用eager loading来预加载他们知道将在加载模型后访问的关系。 预先加载可显着减少必须执行以加载模型关系的SQL查询。

查询关系存在

访问模型的记录时,您可能希望根据关系的存在来限制结果。 例如,假设您要检索至少有一条评论的所有博客帖子。 为此,您可以将关系的名称传递给has方法:

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

您还可以指定运算符并计数以进一步自定义查询:

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

嵌套的has语句也可以使用“点”表示法构造。 例如,您可以检索至少有一条评论和投票的所有帖子:

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

如果你需要更多的功能,你可以使用whereHasorWhereHas方法在你的has查询中放置“where”条件。 这些方法允许您向关系约束添加自定义约束,例如检查Comment的内容:

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

Eager loading

当作为属性访问关系时,关系数据是“延迟加载”。 这意味着在您首次访问该属性之前,实际上不会加载关系数据。 但是,在查询父模型时,模型可以“急切加载”关系。 急切加载缓解了N + 1查询问题。 为了说明N + 1查询问题,请考虑与Author相关的Book模型:

  1. class Book extends Model
  2. {
  3. public $belongsTo = [
  4. 'author' => ['Acme\Blog\Models\Author']
  5. ];
  6. }

现在让我们检索所有书籍及其作者:

  1. $books = Book::all();
  2. foreach ($books as $book) {
  3. echo $book->author->name;
  4. }

此循环将执行1个查询以检索表中的所有书籍,然后对每个书籍执行另一个查询以检索作者。 因此,如果我们有25本书,这个循环将运行26个查询:1个用于原始书籍,25个额外查询用于检索每本书的作者。

值得庆幸的是,我们可以使用预先加载将此操作减少到只有2个查询。 查询时,您可以使用with方法指定应该急切加载哪些关系:

  1. $books = Book::with('author')->get();
  2. foreach ($books as $book) {
  3. echo $book->author->name;
  4. }

对于此操作,将只执行两个查询:

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

Eager loading multiple relationships

有时您可能需要在单个操作中急切地加载几个不同的关系。 为此,只需将其他参数传递给with方法:

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

Nested eager loading

要急切加载嵌套关系,您可以使用“点”语法。 例如,让我们在一个声明中急切地加载本书的所有作者和作者的所有个人联系人:

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

Constraining eager loads

有时您可能希望加载关系,但也为热切加载查询指定其他查询约束。 这是一个例子:

  1. $users = User::with([
  2. 'posts' => function ($query) {
  3. $query->where('title', 'like', '%first%');
  4. }
  5. ])->get();

在这个例子中,如果帖子的title列包含单词first,模型将只会加载帖子。 当然,您可以调用其他查询构建器方法来进一步自定义预先加载操作:

  1. $users = User::with([
  2. 'posts' => function ($query) {
  3. $query->orderBy('created_at', 'desc');
  4. }
  5. ])->get();

Lazy eager loading

有时您可能需要在检索到父模型后急切加载关系。 例如,如果您需要动态决定是否加载相关模型,这可能很有用:

  1. $books = Book::all();
  2. if ($someCondition) {
  3. $books->load('author', 'publisher');
  4. }

如果需要在预先加载的查询上设置其他查询约束,可以将Closure传递给load方法:

  1. $books->load([
  2. 'author' => function ($query) {
  3. $query->orderBy('published_date', 'asc');
  4. }
  5. ]);

插入相关模型

就像查询关系一样,October支持使用方法或动态属性方法定义关系。 例如,也许你需要为Post模型插入一个新的Comment。 您可以直接从关系中插入“Comment”,而不是在“Comment”上手动设置post_id属性。

通过关系方法插入

October提供了为关系添加新模型的便捷方法。 主要模型可以添加到关系中或从关系中删除。 在每种情况下,关系分别相关联或解除关联。

添加方法

使用add方法关联新关系。

  1. $comment = new Comment(['message' => 'A new comment.']);
  2. $post = Post::find(1);
  3. $comment = $post->comments()->add($comment);

请注意,我们没有将comments关系作为动态属性访问。 相反,我们调用comments方法来获取关系的实例。 add方法会自动将相应的post_id值添加到新的Comment模型中。

如果需要保存多个相关模型,可以使用addMany方法:

  1. $post = Post::find(1);
  2. $post->comments()->addMany([
  3. new Comment(['message' => 'A new comment.']),
  4. new Comment(['message' => 'Another comment.']),
  5. ]);

删除方法

相比之下,remove方法可用于解除关系,使其成为孤立的记录。

  1. $post->comments()->remove($comment);

在多对多关系的情况下,记录将从关系的集合中删除。

  1. $post->categories()->remove($category);

在“属于”关系的情况下,您可以使用dissociate方法,该方法不需要传递给它的相关模型。

  1. $post->author()->dissociate();

添加透视数据

当使用多对多关系时,add方法接受一组额外的中间“pivot”表属性作为其第二个参数作为数组。

  1. $user = User::find(1);
  2. $pivotData = ['expires' => $expires];
  3. $user->roles()->add($role, $pivotData);

add方法的第二个参数也可以指定deferred binding在作为字符串传递时使用的会话密钥。 在这些情况下,可以将透视数据作为第三个参数提供。

  1. $user->roles()->add($role, $sessionKey, $pivotData);

创建方法

虽然addaddMany接受一个完整的模型实例,你也可以使用create方法,该方法接受PHP属性数组,创建模型并将其插入数据库。

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

在使用create方法之前,请务必查看属性mass assignment的文档,因为PHP数组中的属性受模型的“可填充”定义的限制。

通过动态属性插入

关系可以通过其属性直接设置,就像访问它们一样。 使用此方法设置关系将覆盖先前存在的任何关系。 之后应该像保存任何属性一样保存模型。

  1. $post->author = $author;
  2. $post->comments = [$comment1, $comment2];
  3. $post->save();

或者,您可以使用主键设置关系,这在使用HTML表单时很有用。

  1. //Assign to author with ID of 3
  2. $post->author = 3;
  3. //Assign comments with IDs of 1, 2 and 3
  4. $post->comments = [1, 2, 3];
  5. $post->save();

可以通过将NULL值分配给属性来解除关系。

  1. $post->author = null;
  2. $post->comments = null;
  3. $post->save();

延迟绑定类似,在不存在的模型上定义的关系在内存中延迟,直到保存为止。 在这个例子中,帖子还不存在,所以不能通过$post-> comments在评论中设置post_id属性。 因此,关联被推迟到通过调用“save”方法创建帖子。

  1. $comment = Comment::find(1);
  2. $post = new Post;
  3. $post->comments = [$comment];
  4. $post->save();

多对多的关系

附加/分离

在处理多对多关系时,模型提供了一些额外的辅助方法,以便更方便地使用相关模型。 例如,假设用户可以拥有多个角色,而角色可以拥有许多用户。 要通过在连接模型的中间表中插入记录来将角色附加到用户,请使用attach方法:

  1. $user = User::find(1);
  2. $user->roles()->attach($roleId);

将关系附加到模型时,您还可以传递要插入到中间表中的其他数据数组:

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

当然,有时可能需要从用户中删除角色。 要删除多对多关系记录,请使用detach方法。 detach方法将从中间表中删除相应的记录; 但是,两种模型都将保留在数据库中:

  1. //Detach a single role from the user...
  2. $user->roles()->detach($roleId);
  3. //Detach all roles from the user...
  4. $user->roles()->detach();

为方便起见,attachdetach也接受ID数组作为输入:

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

同步为方便起见

您也可以使用sync方法构建多对多关联。 sync方法接受要放在中间表上的ID数组。 将从中间表中删除任何不在给定数组中的ID。 因此,在此操作完成后,只有数组中的ID将存在于中间表中:

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

您还可以使用ID传递其他中间表值:

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

触摸父时间戳

当模型belongsTobelongsToMany另一个模型,例如属于PostComment时,有时更新子模型更新时父代的时间戳是有帮助的。 例如,当更新Comment模型时,您可能希望自动“触摸”拥有的Postupdated_at时间戳。 只需添加一个touches属性,其中包含子模型的关系名称:

  1. class Comment extends Model
  2. {
  3. /**
  4. * All of the relationships to be touched.
  5. */
  6. protected $touches = ['post'];
  7. /**
  8. * Relations
  9. */
  10. public $belongsTo = [
  11. 'post' => ['Acme\Blog\Models\Post']
  12. ];
  13. }

现在,当您更新Comment时,拥有的Post也会更新其updated_at列:

  1. $comment = Comment::find(1);
  2. $comment->text = 'Edit to this comment!';
  3. $comment->save();

延期绑定

延迟绑定允许您推迟模型关系绑定,直到主记录提交更改。 如果您需要准备一些模型(例如文件上载)并将它们与另一个尚不存在的模型相关联,这将特别有用。

您可以使用session key 会话密钥将任意数量的 slave 模型推迟到 master 模型。 当主记录与会话密钥一起保存时,将自动更新与从记录的关系。 后端表单行为 会自动支持延迟绑定,但您可能希望在其他位置使用此功能。

生成会话密钥session key

延迟绑定需要会话密钥。 您可以将会话密钥视为事务标识符。 应使用相同的会话密钥来绑定/解除绑定关系并保存主模型。 您可以使用PHPunitqid()函数生成会话密钥。 请注意,表单助手会自动生成包含会话密钥的隐藏字段。

  1. $sessionKey = uniqid('session_key', true);

推迟关系绑定

除非保存帖子,否则下一个示例中的Comment不会添加到帖子中。

  1. $comment = new Comment;
  2. $comment->content = "Hello world!";
  3. $comment->save();
  4. $post = new Post;
  5. $post->comments()->add($comment, $sessionKey);

注意: 尚未保存$post对象,但如果保存发生,则会创建关系。

推迟关系解除绑定

除非保存帖子,否则不会删除下一个示例中的Comment

  1. $comment = Comment::find(1);
  2. $post = Post::find(1);
  3. $post->comments()->delete($comment, $sessionKey);

列出所有绑定

使用关系的withDeferred方法加载所有记录,包括deferred。 结果还将包括现有关系。

  1. $post->comments()->withDeferred($sessionKey)->get();

取消所有绑定

取消延迟绑定并删除从属对象而不是将它们留作孤儿是一个好主意。

  1. $post->cancelDeferred($sessionKey);

提交所有绑定

通过为会话密钥提供save方法的第二个参数,可以在保存主模型时提交(绑定或取消绑定)所有延迟绑定。

  1. $post = new Post;
  2. $post->title = "First blog post";
  3. $post->save(null, $sessionKey);

相同的方法适用于模型的create方法:

  1. $post = Post::create(['title' => 'First blog post'], $sessionKey);

懒提交绑定

如果在保存时无法提供$sessionKey,则可以使用下一个代码随时提交绑定:

  1. $post->commitDeferred($sessionKey);

清理孤立的绑定

销毁尚未提交且超过1天的所有绑定:

  1. October\Rain\Database\Models\DeferredBinding::cleanUp(1);

注意: October会自动销毁超过5天的延迟绑定。 当后端用户登录系统时会发生这种情况。