模型关联
Eloquent: 关联
简介
数据库表通常相互关联。例如,一篇博客文章可能有许多评论,或者一个订单对应一个下单用户。Eloquent 让这些关联的管理和使用变得简单,并支持多种常用的关联类型:
定义关联
Eloquent 关联在 Eloquent 模型类中以方法的形式呈现。如同 Eloquent 模型本身,关联也可以作为强大的 查询语句构造器 使用,提供了强大的链式调用和查询功能。例如,我们可以在 posts
关联的链式调用中附加一个约束条件:
$user->posts()->where('active', 1)->get();
不过在深入使用关联之前,让我们先学习如何定义每种关联类型。
一对一
一对一是最基本的数据库关系。例如,一个 User
模型可能与一个 Phone
模型相关联。为了定义这个关联关系,我们要在 User
模型中写一个 phone
方法,在 phone
方法中调用 hasOne
方法并返回其结果。hasOne
方法被定义在 Illuminate\Database\Eloquent\Model
这个模型基类中:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取与用户相关的电话记录
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}
hasOne
方法的第一个参数是关联模型的类名。一旦定义了模型关联,我们就可以使用 Eloquent 的动态属性获得相关的记录。动态属性允许你访问该关联方法,就像访问模型中定义的属性一样:
$phone = User::find(1)->phone;
Eloquent 基于父模型(User
)的名称来确定关联模型(Phone
)的外键名称。在本例中,会自动假定 Phone
模型有一个 user_id
的外键。如果你想重写这个约定,可以传递第二个参数给 hasOne
方法:
return $this->hasOne(Phone::class, 'foreign_key');
另外,Eloquent 假设外键的值是与父模型的主键(Primary Key)相同的。换句话说,Eloquent 将会通过 Phone
记录的 user_id
列中查找与用户表的 id
列相匹配的值。如果你希望使用自定义的主键值,而不是使用 id
或者模型中的 $primaryKey
属性,你可以给 hasOne
方法传递第三个参数:
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
定义反向关联
我们已经能从 User
模型访问到 Phone
模型了。接下来,让我们再在 Phone
模型上定义一个关联,它能让我们访问到拥有该电话的用户。我们可以使用 belongsTo
方法来定义反向关联, belongsTo
方法与 hasOne
方法相对应:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* 获取拥有此电话的用户
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
在调用 user
方法时,Eloquent 会尝试查找一个 User
模型,该 User
模型上的 id
字段会与 Phone
模型上的 user_id
字段相匹配。
Eloquent 通过关联方法(user
)的名称并使用 _id
作为后缀名来确定外键名称。因此,在本例中,Eloquent 会假设 Phone
模型有一个 user_id
字段。但是,如果 Phone
模型的外键不是 user_id
,这时你可以给 belongsTo
方法的第二个参数传递一个自定义键名:
/**
* 获取拥有此电话的用户
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key');
}
如果父模型不使用 id
字段来作为主键,或者您想要使用其他的字段来匹配相关联的模型,那么您可以向 belongsTo
方法传递第三个参数,这个参数是在父模型中自己定义的字段:
/**
* 获取当前手机号的用户
*/
public function user()
{
return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
一对多
当要定义一个模型是其他 (一个或者多个)模型的父模型这种关系时,可以使用一对多关联。例如,一篇博客可以有很多条评论。和其他模型关联一样,一对多关联也是在 Eloquent 模型文件中用一个方法来定义的:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* 获取这篇博客的所有评论
*/
public function comments()
{
return $this->hasMany(Comment::class);
}
}
注意,Eloquent 将会自动为 Comment
模型选择一个合适的外键。通常,这个外键是通过使用父模型的「蛇形命名」方式,然后再加上 _id
的方式来命名的。因此,在上面这个例子中,Eloquent 将会默认 Comment
模型的外键是 post_id
字段。
如果关联方法被定义,那么我们就可以通过 comments
属性来访问相关的评论 集合。注意,由于 Eloquent 提供了「动态属性」,所以我们就可以像访问模型属性一样来访问关联方法:
use App\Models\Post;
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
由于所有的关系都可以看成是查询构造器,所以您也可以通过链式调用的方式,在 comments
方法中继续添加条件约束:
$comment = Post::find(1)->comments()
->where('title', 'foo')
->first();
像 hasOne
方法一样,hasMany
方法中也可以接受额外的参数,从而来覆盖外键和本地键:
return $this->hasMany(Comment::class, 'foreign_key');
return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
一对多 (反向) / Belongs To
目前我们可以访问一篇博客的所有评论,下面我们可以定义一个关联关系,从而让我们可以通过一条评论来获取到它所属的博客。这个关联关系是 hasMany
的反向,可以子模型中通过 belongsTo
方法来定义这种关联关系:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 获取这条评论所属的博客。
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}
如果定义了这种关联关系,那么我们就可以通过 Comment
模型中的 post
「动态属性」来获取到这条评论所属的博客:
use App\Models\Comment;
$comment = Comment::find(1);
return $comment->post->title;
在上面这个例子中,Eloquent 将会尝试寻找 Post
模型中的 id
字段与 Comment
模型中的 post_id
字段相匹配。
Eloquent 通过检查关联方法的名称,从而在关联方法名称后面加上 _
,然后再加上父模型 (Post)的主键名称,以此来作为默认的外键名。因此,在上面这个例子中,Eloquent 将会默认 Post
模型在 comments
表中的外键是 post_id
。
但是,如果您的外键不遵循这种约定的话,那么您可以传递一个自定义的外键名来作为 belongsTo
方法的第二个参数:
/**
* 获取这条评论所属的博客。
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key');
}
如果您的父表(Post 表)不使用 id
来作为它的主键的话,或者您希望通过其他列来关联相关模型的话,那么您可以传递一个参数来作为 belongsTo
方法的第三个参数,这个参数是父表(Post 表)中想要作为关联关系的字段的名称。
/**
* 获取这条评论所属的博客。
*/
public function post()
{
return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
默认模型
当belongsTo
,hasOne
,hasOneThrough
和 morphOne
这些关联方法返回为 null
的时候,允许您定义一个默认的模型来返回。这种模式通常被称为 空对象模式,这种模式可以帮您省略代码中的一些条件判断。在下面这个例子中,如果 Post
模型中没有用户,那么 user
关联关系将会返回一个空的 App\Models\User
模型:
/**
* 获取这篇博客所属的用户。
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault();
}
如果想要这个默认模型中包含一些属性的话,那么您可以向 withDefault
方法中传递一个数组或者一个闭包:
/**
* 获取这篇博客所属的用户。
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
/**
* 获取这篇博客所属的用户。
*/
public function user()
{
return $this->belongsTo(User::class)->withDefault(function ($user, $post) {
$user->name = 'Guest Author';
});
}
远程一对一
「has-one-through」关联定义了一个模型和另外一个模型之间一对一的关系。同时,这种关联关系是需要第三个模型作为中间模型来实现的。
例如,在一个汽车维修的应用程序中,每一个 Mechanic
模型都与一个 Car
模型相关联,同时,每一个 Car
模型也和一个 Owner
模型相关联。虽然 mechani 和 owner 在数据库中并没有直接的关系,但是 mechani 可以通过 Car
模型来访问 owner。下面是定义这种关联关系所需要的数据表:
mechanics
id - integer
name - string
cars
id - integer
model - string
mechanic_id - integer
owners
id - integer
name - string
car_id - integer
既然我们已经了解了远程一对一的表结构,那么我们就可以在 Mechanic
模型中定义这种关系:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Mechanic extends Model
{
/**
* 获取车辆的主人
*/
public function carOwner()
{
return $this->hasOneThrough(Owner::class, Car::class);
}
}
hasOneThrough
方法的第一个参数是最终想要访问的模型的名称,第二个参数是中间模型的名称。
约定的键名
当使用远程一对一进行关联查询时,Eloquent 将会使用约定的外键名。如果您想要自定义相关联的键名的话,那么您可以传递两个参数来分别作为 hasOneThrough
方法的第三个和第四个参数。第三个参数是中间表的外键名。第四个参数是最终想要访问的模型的外键名。第五个参数是当前模型的本地键名,第六个参数是中间模型的本地键名:
class Mechanic extends Model
{
/**
* Get the car's owner.
*/
public function carOwner()
{
return $this->hasOneThrough(
Owner::class,
Car::class,
'mechanic_id', // cars 表的外键
'car_id', // owners 表的外键
'id', // mechanics 表的本地键名
'id' // cars 表的本地键名
);
}
}
远程一对多
「has-many-through」关联是可以通过中间关系来实现远程一对多的。例如,我们正在构建一个像 Laravel Vapor 这样的部署平台。一个 Project
模型可以通过一个中间的 Environment
模型来访问许多个 Deployment
模型。就像上面的这个例子,您可以在给定的 environment 中很方便的获取所有的 deployments。下面是定义这种关联关系所需要的数据表:
projects
id - integer
name - string
environments
id - integer
project_id - integer
name - string
deployments
id - integer
environment_id - integer
commit_hash - string
既然我们已经了解了远程一对多关联的表结构,那么就可以在 Project
模型中来定义这种关联关系:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
/**
* 从 project 中获取所有的 deployments
*/
public function deployments()
{
return $this->hasManyThrough(Deployment::class, Environment::class);
}
}
hasManyThrough
方法的第一个参数是我们最终想要访问的模型的名称,第二个参数是中间模型的名称。
虽然 Deployment
模型中没有 project_id
字段,但是在 hasManyThrough
中可以通过 $project->deployments
来访问一个 project 的 deployments。如果想要查询上面的模型,Eloquent 将会检查在中间模型(Environment)中是否存在 project_id
字段。在找到相关的 environment 的 id 后,就可以查询 Deployment
模型的内容了。
约定的键名
当使用远程一对多进行查询的时候,Eloquent 将会使用约定的外键名。如果您想要自定义相关联的键名的话,那么您可以传递两个参数来分别作为 hasManyThrough
方法的第三个和第四个参数。第三个参数是中间表的外键名。第四个参数是最终想要访问的模型的外键名。第五个参数是当前模型的本地键名,第六个参数是中间模型的本地键名:
class Project extends Model
{
public function deployments()
{
return $this->hasManyThrough(
Deployment::class,
Environment::class,
'project_id', // environments 表的外键名
'environment_id', // deployments 表的外键名
'id', // projects 表的本地键名
'id' // environments 表的本地键名
);
}
}
多对多关联
多对多关联比 hasOne
和 hasMany
关联稍微复杂些。举个例子,一个用户可以拥有多个角色,同时这些角色也可以分配给其他用户。例如,一个用户可是「作者」和「编辑」;当然,这些角色也可以分配给其他用户。所以,一个用户可以拥有多个角色,一个角色可以分配给多个用户。
表结构
要定义这种关联,需要三个数据库表: users
,roles
和 role_user
。role_user
表的命名是由关联的两个模型按照字母顺序来的,并且包含了 user_id
和 role_id
字段。该表用作链接users
和roles
的中间表
特别提醒,由于角色可以属于多个用户,因此我们不能简单地在roles
表上放置user_id
列。如果这样,这意味着角色只能属于一个用户。为了支持将角色分配给多个用户,需要使用role_user
表。我们可以这样定义表结构:
users
id - integer
name - string
roles
id - integer
name - string
role_user
user_id - integer
role_id - integer
模型结构
多对多关联通过调用 belongsToMany 这个内部方法返回的结果来定义。例如,我们在 User
模型中定义 roles
方法:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 用户所拥有的角色
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
一旦关联关系被定义后,你可以通过 roles
「动态属性」获取用户角色:
use App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
//
}
当然,像其它所有关联模型一样,你可以使用 roles
方法,利用链式调用对查询语句添加约束条件:
$roles = User::find(1)->roles()->orderBy('name')->get();
正如前面所提到的,为了确定关联连接表的表名,Eloquent 会按照字母顺序连接两个关联模型的名字。当然,你也可以不使用这种约定,传递第二个参数到 belongsToMany
方法即可:
return $this->belongsToMany(Role::class, 'role_user');
除了自定义连接表的表名,你还可以通过传递额外的参数到 belongsToMany
方法来定义该表中字段的键名。第三个参数是定义此关联的模型在连接表里的外键名,第四个参数是另一个模型在连接表里的外键名:
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
定义反向关联
要定义多对多的反向关联, 你只需要在关联模型中调用 belongsToMany
方法。我们在 Role
模型中定义 users
方法:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 拥有此角色的用户
*/
public function users()
{
return $this->belongsToMany(User::class);
}
}
如你所见,除了引入模型 App\Models\User
外,其它与在 User
模型中定义的完全一样。由于我们重用了 belongsToMany
方法,自定义连接表表名和自定义连接表里的键的字段名称在这里同样适用。
获取中间表字段
正如你刚才所了解的一样,多对多的关联关系需要一个中间表来提供支持, Eloquent 提供了一些有用的方法来和这张表进行交互。例如,假设我们的 User
对象关联了多个 Role
对象。在获得这些关联对象后,可以使用模型的 pivot
属性访问中间表的属性:
use App\Models\User;
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
需要注意的是,我们获取的每个 Role
模型对象,都会被自动赋予 pivot
属性,它代表中间表的一个模型对象,并且可以像其他的 Eloquent 模型一样使用。
默认情况下,pivot
对象只包含两个关联模型的主键,如果你的中间表里还有其他额外字段,你必须在定义关联时明确指出:
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
如果你想让中间表自动维护 created_at
和 updated_at
时间戳,那么在定义关联时附加上 withTimestamps
方法即可:
return $this->belongsToMany(Role::class)->withTimestamps();
注意:使用 Eloquent 自动维护时间戳的中间表需要同时具有
created_at
和updated_at
时间戳字段。
自定义 <code>pivot</code> 属性名称
如前所述,可以通过 pivot
属性在模型上访问中间表中的属性。 但是,你可以随意自定义此属性的名称,以更好地反映其在应用程序中的用途。
例如,如果你的应用程序包含可能订阅播客的用户,则用户和播客之间可能存在多对多关系。 如果是这种情况,你可能希望将中间表属性重命名为 subscription
而不是 pivot
。 这可以在定义关系时使用 as
方法来完成:
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();
一旦定义完成,你可以使用自定义名称访问中间表数据:
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
echo $podcast->subscription->created_at;
}
通过中间表过滤查询
在定义关系时,你还可以使用 wherePivot
、wherePivotIn
和 wherePivotNotIn
方法来过滤 belongsToMany
返回的结果:
return $this->belongsToMany(Role::class)->wherePivot('approved', 1);
return $this->belongsToMany(Role::class)->wherePivotIn('priority', [1, 2]);
return $this->belongsToMany(Role::class)->wherePivotNotIn('priority', [1, 2]);
自定义中间表模型
如果你想定义一个自定义模型来表示多对多关系的中间表,你可以在定义关系时调用 using
方法。
自定义多对多中间表模型都必须扩展自 Illuminate\Database\Eloquent\Relations\Pivot
类,自定义多对多(多态)中间表模型必须继承 Illuminate\Database\Eloquent\Relations\MorphPivot
类。例如,我们在写 Role
模型的关联时,使用自定义中间表模型 RoleUser
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
/**
* 拥有此角色的所有用户
*/
public function users()
{
return $this->belongsToMany(User::class)->using(RoleUser::class);
}
}
当定义 RoleUser
模型时,我们要扩展 Pivot
类:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class RoleUser extends Pivot
{
//
}
注意:
Pivot
模型不能使用SoftDeletes
特性。 如果你需要软删除数据关联记录,请考虑将您的数据关联模型转换为实际的 Eloquent 模型。
带有递增 ID 的自定义中继模型
如果你用一个自定义的中继模型定义了多对多的关系,而且这个中继模型拥有一个自增的主键,你应当确保这个自定义中继模型类中定义了一个 incrementing
属性且其值为 true
。
/**
* 标识 ID 是否自增
*
* @var bool
*/
public $incrementing = true;
多态关联
多态关联允许目标模型借助单个关联从属于多个模型。例如,你正在构建一个允许用户共享博客文章和视频的应用程序,其中 Comment
模型可能同时从属于 Post
和 Video
模型。
一对一 (多态)
表结构
一对一多态关联与简单的一对一关联类似,不过,目标模型能够在一个关联上从属于多个模型。例如,博客 Post
和 User
可能共享一个关联到 Image
模型的关系。使用一对一多态关联允许使用一个唯一图片列表同时用于博客文章和用户账户。让我们先看看表结构:
posts
id - integer
name - string
users
id - integer
name - string
images
id - integer
url - string
imageable_id - integer
imageable_type - string
要特别留意 images
表的 imageable_id
和 imageable_type
列。 imageable_id
列包含文章或用户的 ID 值,而 imageable_type
列包含的则是父模型的类名。Eloquent 在访问 imageable
时使用 imageable_type
列来判断父模型的「类型」。
模型结构
接下来,再看看建立关联的模型定义:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Image extends Model
{
/**
* 获取拥有此图片的模型
*/
public function imageable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* 获取文章图片
*/
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}
class User extends Model
{
/**
* 获取文章图片
*/
public function image()
{
return $this->morphOne(Image::class, 'imageable');
}
}
获取关联
一旦定义了表和模型,就可以通过模型访问此关联。比如,要获取文章图片,可以使用 image
动态属性:
use App\Models\Post;
$post = Post::find(1);
$image = $post->image;
还可以通过访问执行 morphTo
调用的方法名来从多态模型中获知父模型。在这个例子中,就是 Image
模型的 imageable
方法。所以,我们可以像动态属性那样访问这个方法:
use App\Models\Image;
$image = Image::find(1);
$imageable = $image->imageable;
Image
模型上的 imageable
关系将返回 Post
实例或 User
实例,具体取决于模型拥有图像的类型。
自定义列
如有必要,你可以指定多态子模型使用的 id
和 type
列的名称。 如果这样做,请确保始终将关系名称作为第一个参数传递给 morphTo
方法。 通常,此值应与方法名称匹配,因此你可以使用 PHP 的 __FUNCTION__
常量:
/**
* 获取 image 实例所属的模型
*/
public function imageable()
{
return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}
一对多(多态)
表结构
一对多多态关联与简单的一对多关联类似,不过,目标模型可以在一个关联中从属于多个模型。假设应用中的用户可以同时「评论」文章和视频。使用多态关联,可以用单个 comments
表同时满足这些情况。我们还是先来看看用来构建这种关联的表结构:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
模型结构
接下来,看看构建这种关联的模型定义:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 获取拥有此评论的模型
*/
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* 获取此文章的所有评论
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
/**
* 获取此视频的所有评论
*/
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
获取关联
一旦定义了数据库表和模型,就可以通过模型访问关联。例如,可以使用 comments
动态属性访问文章的全部评论:
use App\Models\Post;
$post = Post::find(1);
foreach ($post->comments as $comment) {
//
}
您还可以通过访问执行对 morphTo
的调用的方法名来从多态模型获取其所属模型。在我们的例子中,这就是 Comment
模型上的 commentable
方法。因此,我们将以动态属性的形式访问该方法:
use App\Models\Comment;
$comment = Comment::find(1);
$commentable = $comment->commentable;
Comment
模型的 commentable
关联将返回 Post
或 Video
实例,其结果取决于评论所属的模型。
多对多(多态)
表结构
多对多多态关联比 morphOne
和 morphMany
关联略微复杂一些。例如,博客 Post
和 Video
模型能够共享关联到 Tag
模型的多态关系。使用多对多多态关联允许使用一个唯一标签在博客文章和视频间共享。以下是多对多多态关联的表结构:
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
技巧:在深入研究多态多对多关系之前,你可能会从阅读有关典型 多对多关系 的文档中受益。
模型结构
接下来,在模型上定义关联。Post
和 Video
模型都有调用 Eloquent
基类上 morphToMany
方法的 tags
方法。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* 获取文章的所有标签
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
定义反向关联关系
下面,需要在 Tag
模型上为每个关联模型定义一个方法。在这个示例中,我们将会定义 posts
方法和 videos
方法:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* 获取被打上此标签的所有文章
*/
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
/**
* 获取被打上此标签的所有视频
*/
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
获取关联
一旦定义了数据库表和模型,就可以通过模型访问关联。例如,可以使用 tags
动态属性访问文章的所有标签:
use App\Models\Post;
$post = Post::find(1);
foreach ($post->tags as $tag) {
//
}
还可以访问执行 morphedByMany
方法调用的方法名来从多态模型获取其所属模型。在这个示例中,就是 Tag
模型的 posts
或 videos
方法。可以像动态属性一样访问这些方法:
use App\Models\Tag;
$tag = Tag::find(1);
foreach ($tag->posts as $post) {
//
}
foreach ($tag->videos as $video) {
//
}
自定义多态类型
默认情况下, Laravel 使用完全限定类名存储关联模型类型。在上面的一对多示例中, 因为 Comment
可能从属于一个 Post
或一个 Video
,默认的 commentable_type
就将分别是 App\Post 或 App\Video
。不过,你可能希望数据库与应用的内部结构解耦。在这种情况下,可以定义一个「morph 映射」来通知 Eloquent 使用自定义名称代替对应的类名:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'post' => 'App\Models\Post',
'video' => 'App\Models\Video',
]);
可以在 App\Providers\AppServiceProvider
的 boot
函数中注册 morphMap
,或者创建一个单独的服务提供者。
您可以在运行时使用 getMorphClass
方法确定给定模型的别名。相反,您可以使用 Relation::getMorphedModel
方法来确定与别名相关联的类名:
use Illuminate\Database\Eloquent\Relations\Relation;
$alias = $post->getMorphClass();
$class = Relation::getMorphedModel($alias);
注意:在现有应用程序中添加「morph 映射」时,数据库中仍包含完全限定类的每个可变形
*_type
列值都需要转换为其「映射」名称。
动态关联
您可以使用 resolveRelationUsing
方法在运行时定义 Eloquent 模型之间的关系。虽然通常不建议在常规应用程序开发中使用它,但是在开发 Laravel 软件包时,这有时可能会很有用:
resolveRelationUsing
方法的第一个参数是关联名称。传递给该方法的第二个参数应该是一个闭包,闭包接受模型实例并返回一个有效的 Eloquent 关联定义。通常情况下,你应该在服务提供器的启动方法中配置动态关联。
use App\Models\Order;
use App\Models\Customer;
Order::resolveRelationUsing('customer', function ($orderModel) {
return $orderModel->belongsTo(Customer::class, 'customer_id');
});
注意:当定义动态关联时,请始终为 Eloquent 的关联方法提供显式的键名。
查询关联
因为所有的 Eloquent 关联都是通过方法定义的,你可以调用这些方法来获取关联的实例,而无需真实执行查询来获取相关的模型。此外,所有的 Eloquent 关联也可以用作查询生成器,允许你在最终对数据库执行 SQL 查询之前,继续通过链式调用添加约束条件。
例如,假设有一个博客系统,它的 User
模型有许多关联的 Post
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 获取该用户的所有文章
*/
public function posts()
{
return $this->hasMany(Post::class);
}
}
你可以查询 posts
关联,并给它添加额外的约束条件,如下例所示:
use App\Models\User;
$user = User::find(1);
$user->posts()->where('active', 1)->get();
你可以在关联上使用任意的 查询构造器 方法,所以一定要阅读查询构造器的文档,了解它的所有方法,这会对你非常有用。
在关联之后链式添加 <code>orWhere</code> 子句
如上例所示,你可以在查询关联时,自由的给关联添加额外的约束条件。但是,在将 orWhere
子句链接到关联上时,一定要小心,因为 orWher
子句将在逻辑上与关联约束处于同一级别:
$user->posts()
->where('active', 1)
->orWhere('votes', '>=', 100)
->get();
上面的例子将生成以下SQL。像你看到的那样, 这个 or
子句的查询指令,将返回大于100票的任一用户,查询不再限于特定的用户:
select *
from posts
where user_id = ? and active = 1 or votes >= 100
在大多数情况下,你应该使用 逻辑组 在括号中对条件检查进行分组:
use Illuminate\Database\Eloquent\Builder;
$user->posts()
->where(function (Builder $query) {
return $query->where('active', 1)
->orWhere('votes', '>=', 100);
})
->get();
上面的示例将生成以下 SQL。 请注意,逻辑分组已对约束进行了正确分组,并且查询仍然限定于特定用户:
select *
from posts
where user_id = ? and (active = 1 or votes >= 100)
关联方法 Vs 动态属性
如果您不需要向 Eloquent 关联查询添加额外的约束,您可以像访问属性一样访问关联。 例如,继续使用我们的 User
和 Post
示例模型,我们可以像这样访问用户的所有帖子:
use App\Models\User;
$user = User::find(1);
foreach ($user->posts as $post) {
//
}
动态属性是 「懒加载」 的, 只有实际访问到才会加载关联数据。因此,通常用 预加载 来准备模型需要用到的关联数据。预加载能大量减少因加载模型关联执行的 SQL 语句。
查询已存在的关联
检索模型记录时,您可能希望根据关系的存在限制结果。 例如,假设您要检索至少有一条评论的所有博客文章。 为此,您可以将关系的名称传递给 has
和 orHas
方法:
use App\Models\Post;
//查出至少有一条评论的文章...
$posts = Post::has('comments')->get();
也可以指定运算符和数量来进一步自定义查询:
// 查出至少有三条评论的文章...
$posts = Post::has('comments', '>=', 3)->get();
也可以用「点」语法构造嵌套的 has
语句。例如,查出至少有一条评论和图片的文章:
// 查出至少有一条带图片的评论的文章...
$posts = Post::has('comments.images')->get();
如果需要更多功能,可以使用 whereHas
和 orWhereHas
方法将「where」条件放到 has
查询上。这些方法允许你向关联加入自定义约束,比如检查评论内容:
use Illuminate\Database\Eloquent\Builder;
// 获取至少带有一条评论内容包含 foo% 关键词的文章...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
// 获取至少带有十条评论内容包含 foo% 关键词的文章...
$posts = Post::whereHas('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
}, '>=', 10)->get();
注意:Eloquent 目前不支持跨数据库查询关系是否存在。 这些关系必须存在于同一数据库中。
查询不存在的关联
访问模型的记录时,您可能希望根据不存在的关系来筛选结果。例如,假设您要检索所有 没有 评论的博客文章。为此,您可以将关系的名称传递给doesntHave
和 orDoesntHave
方法:
use App\Models\Post;
$posts = Post::doesntHave('comments')->get();
如果需要更多功能,可以使用 whereDoesntHave
和 orWhereDoesntHave
方法将「where」 条件加到 doesntHave
查询上。这些方法允许你向关联加入自定义限制,比如检测评论内容:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::whereDoesntHave('comments', function (Builder $query) {
$query->where('content', 'like', 'code%');
})->get();
您可以使用“点”符号对嵌套关系执行查询。 例如,以下查询将检索所有没有评论的帖子; 但是,有未被禁止的作者评论的帖子将包含在结果中:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
$query->where('banned', 0);
})->get();
多态关联查询
要查询多态关联关系的存在,您可以使用 whereHasMorph
和 whereDoesntHaveMorph
方法。 这些方法接受关联名称作为它们的第一个参数。 接下来,这些方法接受您希望包含在查询中的相关模型的名称。 最后,您也可以提供一个自定义关系查询的闭包:
use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;
// 查询与帖子或视频相关并且标题包含 foo 的评论...
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
// 查询与帖子相关的评论,标题不包含 foo%...
$comments = Comment::whereDoesntHaveMorph(
'commentable',
Post::class,
function (Builder $query) {
$query->where('title', 'like', 'code%');
}
)->get();
您可能偶尔需要根据相关多态模型的“类型”添加查询约束。 传递给 whereHasMorph
方法的闭包可能会接收一个 $type
值作为它的第二个参数。 此参数允许您检查正在构建的查询的“类型”:
use Illuminate\Database\Eloquent\Builder;
$comments = Comment::whereHasMorph(
'commentable',
[Post::class, Video::class],
function (Builder $query, $type) {
$column = $type === Post::class ? 'content' : 'title';
$query->where($column, 'like', 'code%');
}
)->get();
查询所有关联模型
您可以提供 *
作为通配符值,而不是传递可能存在多态模型数组。这将指示 Laravel
从数据库中检索所有可能的多态类型。为了执行此操作,Laravel
将执行一个附加查询:
use Illuminate\Database\Eloquent\Builder;
$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
$query->where('title', 'like', 'foo%');
})->get();
聚合关联模型
关联模型计数
有时您可能需要计算给定关系的相关模型的数量,而不实际加载模型。 为此, 您可以使用 withCount
方法。 withCount
方法将在生成的模型上放置 {relation}_count
属性:
use App\Models\Post;
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count;
}
通过将数组传递到 withCount
方法, 可以为多个关系添加「计数」,并向查询添加附加约束:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'code%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
您还可以以别名关系计数结果,允许对同一关系进行多个计数:
use Illuminate\Database\Eloquent\Builder;
$posts = Post::withCount([
'comments',
'comments as pending_comments_count' => function (Builder $query) {
$query->where('approved', false);
},
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
延迟加载计数
使用 loadCount
可以在模型获取后加载关联关系的数量。
$book = Book::first();
$book->loadCount('genres');
如果你需要在统计时设置额外查询条件,可以通过传递键为关联关系名、值为查询闭包的数组来实现:
$book->loadCount(['reviews' => function ($query) {
$query->where('rating', 5);
}])
关联关系计数与自定义获取字段
如果你的查询同时包含 withCount
和 select
,请确保 withCount
一定在 select
之后调用:
$posts = Post::select(['title', 'body'])
->withCount('comments')
->get();
其他聚合函数
除了 withCount
方法外,Eloquent 还提供了 withMin
, withMax
, withAvg
和 withSum
等聚合方法。
这些方法会通过 {relation}_{function}_{column}
的命名方式将聚合结果添加到获取到的模型属性中:
use App\Models\Post;
$posts = Post::withSum('comments', 'votes')->get();
foreach ($posts as $post) {
echo $post->comments_sum_votes;
}
与 loadCount
方法类似,这些方法也有延迟调用的方法。这些延迟方法可在已获取到的Eloquent模型上调用:
$post = Post::first();
$post->loadSum('comments', 'votes');
计算多态关联关系的数量
如果你想预加载多态关联关系以及这个关联关系关联的其他关联关系的计数统计,可以通过将with
方法与 morphTo
关系和morphWithCount
方法结合来实现。
在这个例子中,我们假设 Photo
和 Post
模型可以创建 ActivityFeed
模型。 我们将假设 ActivityFeed
模型定义了一个名为 parentable
的多态关联关系,它允许我们为给定的 ActivityFeed
实例检索父级 Photo
或 Post
模型。 此外,让我们假设Photo
模型有很多Tag
模型、Post
模型有很多Comment
模型。
假如我们想要检索 ActivityFeed
实例并为每个 ActivityFeed
实例预先加载 parentable
父模型。 此外,我们想要检索与每张父照片关联的标签数量以及与每个父帖子关联的评论数量:
use Illuminate\Database\Eloquent\Relations\MorphTo;
$activities = ActivityFeed::with([
'parentable' => function (MorphTo $morphTo) {
$morphTo->morphWithCount([
Photo::class => ['tags'],
Post::class => ['comments'],
]);
}])->get();
延迟加载计数
假设我们已经检索了一组 ActivityFeed
模型,现在我们想要加载与活动提要关联的各种 parentable
模型的嵌套关系计数。 您可以使用 loadMorphCount
方法来完成此操作:
$activities = ActivityFeed::with('parentable')->get();
$activities->loadMorphCount('parentable', [
Photo::class => ['tags'],
Post::class => ['comments'],
]);
渴求式加载
当将 Eloquent 关系作为属性访问时,相关模型是延迟加载的。 这意味着在您第一次访问该属性之前不会实际加载关联数据。 但是,Eloquent 可以在您查询父模型时主动加载关联关系。 渴求加载减轻了 N + 1
查询问题。 为了说明 N + 1
查询问题,请参考属于 Author
模型的 Book
模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* Get the author that wrote the book.
*/
public function author()
{
return $this->belongsTo(Author::class);
}
}
我们检索所有书籍及其作者:
use App\Models\Book;
$books = Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
该循环将执行一个查询以检索数据库表中的所有书籍,然后对每本书执行另一个查询以检索该书的作者。 因此,如果我们有 25 本书,上面的代码将运行 26 个查询:一个查询原本的书籍信息,另外 25 个查询来检索每本书的作者。
值得庆幸的是,我们可以使用预加载将这个操作减少到两个查询。 在构建查询时,您可以使用 with
方法指定应该预加载哪些关系:
$books = Book::with('author')->get();
foreach ($books as $book) {
echo $book->author->name;
}
对于此操作,将只执行两个查询 - 一个查询检索所有书籍,一个查询检索所有书籍的所有作者:
select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)
预加载多个关联
有时,你可能需要在单一操作中预加载几个不同的关联。要达成此目的,只要向 with
方法传递多个关联名称构成的数组参数:
$books = Book::with(['author', 'publisher'])->get();
嵌套预加载
可以使用 「点」 语法预加载嵌套关联。比如在一个 Eloquent 语句中预加载所有书籍作者及其联系方式:
$books = Book::with('author.contacts')->get();
嵌套预加载 <code>morphTo</code> 关联
如果你希望加载一个 morphTo
关系,以及该关系可能返回的各种实体的嵌套关系,可以将 with
方法与 morphTo
关系的 morphWith
方法结合使用。 为了帮助说明这种方法,让我们参考以下模型:
<?php
use Illuminate\Database\Eloquent\Model;
class ActivityFeed extends Model
{
/**
* 获取活动提要记录的父级
*/
public function parentable()
{
return $this->morphTo();
}
}
在这个例子中,我们假设 Event
,Photo
和 Post
模型可以创建 ActivityFeed
模型。 另外,我们假设 Event
模型属于 Calendar
模型,Photo
模型与 Tag
模型相关联,Post
模型属于 Author
模型。
使用这些模型定义和关联,我们可以查询 ActivityFeed
模型实例并预加载所有 parentable
模型及其各自的嵌套关系:
use Illuminate\Database\Eloquent\Relations\MorphTo;
$activities = ActivityFeed::query()
->with(['parentable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
}])->get();
预加载指定列
并不是总需要获取关系的每一列。在这种情况下,Eloquent 允许你为关联指定想要获取的列:
$books = Book::with('author:id,name')->get();
注意:在使用这个特性时,一定要在要获取的列的列表中包含
id
列。
默认预加载
有时可能希望在查询模型时始终加载某些关联。 为此,你可以在模型上定义 $with
属性:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
/**
* 默认加载的关联
*
* @var array
*/
protected $with = ['author'];
/**
* 获取书籍作者
*/
public function author()
{
return $this->belongsTo(Author::class);
}
}
如果你想从单个查询的 $with
属性中删除一个预加载,你可以使用 without
方法:
$books = Book::without('author')->get();
为预加载添加约束
有时,你可能希望预加载一个关联,同时为预加载查询添加额外查询条件。您可以通过将一个关联数组传递给 with
方法来实现这一点,其中数组键是关联名称,数组值是一个闭包,它为预先加载查询添加了额外的约束:
use App\Models\User;
$users = User::with(['posts' => function ($query) {
$query->where('title', 'like', '%code%');
}])->get();
在这个例子中, Eloquent 将仅预加载那些 title
列包含 first
关键词的文章。也可以调用其它的 查询构造器 方法进一步自定义预加载操作:
$users = User::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}])->get();
注意:在约束预加载时,不能使用
limit
和take
查询构造器方法。
<code>morphTo</code>关联预加载添加约束
预加载 morphTo
关联关系时,Eloquent 将运行多个查询来获取每种类型的相关模型。 您可以使用 MorphTo
关系的 constrain
方法为每个查询添加额外的约束:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
$morphTo->constrain([
Post::class => function (Builder $query) {
$query->whereNull('hidden_at');
},
Video::class => function (Builder $query) {
$query->where('type', 'educational');
},
]);
}])->get();
在这个例子中,Eloquent 只会预先加载未被隐藏的帖子,并且视频的 type
值为 educational
。
延迟预加载
有可能你还希望在模型加载完成后在进行渴求式加载。举例来说,如果你想要根据某个条件动态决定是否加载关联数据,那么 load
方法对你来说会非常有用:
use App\Models\Book;
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
如果你想要在渴求式加载的查询语句中进行条件约束,你可以通过数组的形式去加载,键为对应的关联关系,值为 Closure
闭包函数,该闭包的参数为一个查询实例:
$author->load(['books' => function ($query) {
$query->orderBy('published_date', 'asc');
}]);
如果希望仅加载未被加载的关联关系时,你可以使用 loadMissing
方法:
$book->loadMissing('author');
嵌套延迟预加载 & <code>morphTo</code>
如果希望快速加载 morphTo
关系,以及该关系可能返回的各种实体上的嵌套关系,可以使用 loadMorph
方法。
这个方法接受 morphTo
关系的名称作为它的第一个参数,第二个参数接收模型数组、关系数组。为了帮助说明这个方法,可以看一下以下模型例子:
<?php
use Illuminate\Database\Eloquent\Model;
class ActivityFeed extends Model
{
/**
* Get the parent of the activity feed record.
*/
public function parentable()
{
return $this->morphTo();
}
}
在这个例子中,让我们假设 Event
、Photo
和 Post
模型可以创建 ActivityFeed
模型。此外,让我们假设 Event
模型属于 Calendar
模型,Photo
模型与 Tag
模型相关联,Post
模型属于 Author
模型。
使用这些模型定义和关联关系,我们方可以检索 ActivityFeed
模型实例,并立即加载所有 parentable
模型及其各自的嵌套关系:
$activities = ActivityFeed::with('parentable')
->get()
->loadMorph('parentable', [
Event::class => ['calendar'],
Photo::class => ['tags'],
Post::class => ['author'],
]);
插入 & 更新关联模型
<code>save</code> 方法
Eloquent 提供了向关系中添加新模型的便捷方法。例如,你可能需要向一篇文章(Post
模型)添加一条新的评论(Comment
模型),你不用手动设置 Comment
模型上的 post_id
属性,你可以直接使用关联模型中的 save
方法:
use App\Models\Comment;
use App\Models\Post;
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);
注意,我们没有将 comments
关联作为动态属性访问,相反,我们调用了 comments
方法来来获得关联实例, save
方法会自动添加适当的 post_id
值到新的 Comment
模型中。
如果需要保存多个关联模型,你可以使用 saveMany
方法:
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);
save
和 saveMany
方法不会将新模型(Comment
)加载到父模型(Post
)上, 如果你计划在使用 save
or saveMany
方法后访问该关联模型(Comment
),你需要使用 refresh
方法重新加载模型及其关联,这样你就可以访问到所有评论,包括新保存的评论了:
$post->comments()->save($comment);
$post->refresh();
// 所有评论,包括新保存的评论...
$post->comments;
递归保存模型和关联数据
如果你想 save
你的模型及其所有关联数据,你可以使用 push
方法,在此示例中,将保存 Post
模型及其评论和评论作者:
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();
新增方法
除了 save
和 saveMany
方法外,你还可以使用 create
方法。它接受一个属性数组,同时会创建模型并插入到数据库中。 还有, save
方法和 create
方法的不同之处在于, save
方法接受一个完整的 Eloquent 模型实例,而 create
则接受普通的 PHP 数组:
use App\Models\Post;
$post = Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
你还可以使用 createMany
方法去创建多个关联模型:
$post = Post::find(1);
$post->comments()->createMany([
['message' => 'A new comment.'],
['message' => 'Another new comment.'],
]);
你还可以使用 findOrNew
、firstOrNew
、firstOrCreate
和 updateOrCreate
方法来 创建和更新关系模型。
技巧:在使用
create
方法前,请务必确保查看过本文档的 批量赋值 章节。
更新 <code>belongsTo</code> 关联
当更新 belongsTo
关联时,可以使用 associate
方法。此方法将会在子模型中设置外键。在这个例子中,User
模型定义了一个与 Account
模型的 belongsTo
关系。 这个 associate
方法将在子模型上设置外键:
use App\Models\Account;
$account = Account::find(10);
$user->account()->associate($account);
$user->save();
当移除 belongsTo
关联时,可以使用 dissociate
方法。此方法会将关联外键设置为 null
:
$user->account()->dissociate();
$user->save();
多对多关联
附加 / 分离
Eloquent 也提供了一些额外的辅助方法,使相关模型的使用更加方便。例如,我们假设一个用户可以拥有多个角色,并且每个角色都可以被多个用户共享。给某个用户附加一个角色是通过向中间表插入一条记录实现的,可以使用 attach
方法完成该操作:
use App\Models\User;
$user = 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 = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
1 => ['expires' => $expires],
2 => ['expires' => $expires],
]);
同步关联
你也可以使用 sync
方法构建多对多关联。sync
方法接收一个 ID 数组以替换中间表的记录。中间表记录中,所有未在 ID 数组中的记录都将会被移除。所以该操作结束后,只有给出数组的 ID 会被保留在中间表中:
$user->roles()->sync([1, 2, 3]);
你也可以通过 ID 传递额外的附加数据到中间表:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
如果你不想移除现有的 ID,可以使用 syncWithoutDetaching
方法:
$user->roles()->syncWithoutDetaching([1, 2, 3]);
切换关联
多对多关联也提供了 toggle
方法用于「切换」给定 ID 数组的附加状态。 如果给定的 ID 已被附加在中间表中,那么它将会被移除,同样,如果给定的 ID 已被移除,它将会被附加:
$user->roles()->toggle([1, 2, 3]);
更新中间表上的记录
如果你需要在中间表中更新一条已存在的记录,可以使用 updateExistingPivot
。此方法接收中间表的外键与要更新的数据数组进行更新:
$user = User::find(1);
$user->roles()->updateExistingPivot($roleId, [
'active' => false,
]);
更新父级时间戳
当一个模型属 belongsTo
或者 belongsToMany
另一个模型时, 例如 Comment
属于 Post
,有时更新子模型导致更新父模型时间戳非常有用。
例如,当 Comment
模型被更新时,你需要自动「触发」父级 Post 模型的 updated_at
时间戳的更新。Eloquent 让它变得简单。只要在子模型加一个包含关联名称的 touches
属性即可:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* 需要触发的所有关联关系
*
* @var array
*/
protected $touches = ['post'];
/**
* 获取评论所属文章
*/
public function post()
{
return $this->belongsTo(Post::class);
}
}
注意:只有使用 Eloquent 的
save
方法更新子模型时,才会更新父模型时间戳。