Laravel 模型关联关系
一对一
建立关联关系
通过hasOne
方法定义一对一关联
public function profile()
{
return $this->hasOne(UserProfile::class);
}
Eloquent 底层约定
hasOne
方法参数
public function hasOne($related, $foreignKey = null, $localKey = null)
第一个参数是关联模型的类名,第二个参数是关联模型类所属表的外键,第三个参数是关联表的外键关联到当前模型所属表的哪个字段。
建立相对的关联关系
通过belongsTo
方法来建立相对的一对一关联关系。
public function user()
{
return $this->belongsTo(User::class);
}
belongsTo
方法参数
public function belongsTo($related,$foreignKey = null, $ownerKer = null, $relation = null)
第一个参数是关联模型的类名。第二个参数是当前模型类所属表的外键,第三个参数是关联模型类所属表的主键,第四个参数默认约定是关联关系方法名,也是关联关系动态属性名。
一对多
建立关联关系
通过hasMany
方法实现
public function posts()
{
return $this->hasMany(Post::class);
}
懒加载
通过with
方法实现
$post = Post::with('author')
->where('views','>',0)
->get();
多对多
建立关联关系
通过belongsToMany
方法实现
public function tags()
{
return $this->belongsToMany(Tag::class,'post_tags');
}
底层约定
belongsToMany
方法参数如下
public function belongsToMany($related, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null, $relation = null)
第一个参数是关联模型的类名。第二个参数是中间表,第三个参数是中间表中当前模型类的外键,第四个参数是中间表当前关联模型类的外键,第五个参数表示对应当前模型的哪个字段,第六个参数表示对应关联模型的哪个字段,最后一个参数表示关联关系名称,默认是关联方法名。
获取中间表字段
通过with
方法传入字段然后将其返回:
public function tags()
{
return $this->belongsToMany(Tag::class,'post_tags')->withPivot('user_id')->withTimestamps();
}
远程一对多关联
远程一对多是借助中间表进行关联。
建立远程一对多关联关系
通过hasManyThrough
方法定义
public function posts()
{
return $this->hasManyThrough(Post::class,User::class);
}
第一个参数是关联的模型类,第二个参数是中间借助的模型类。
底层约定
hasManyThrough
方法的方法参数
public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null, $localKey = null, $secondLocalKey = null)
第一个参数是关联模型类,第二个参数是中间模型类,第三个参数是中间模型类与当前模型类的关联外键,第四个参数指的是中间模型类与关联模型类的关联外键,第五个参数是当前模型类的主键,第六个参数是中间模型类的主键。
定义相对的关联关系
public function image()
{
return $this->morphOne(Image::class,'imageable');
}
一对多的多态关联
一对多多态关联与一对一类似。
在模型类中构建一对多多态关联
通过morphTo
方法定义
public function commentable()
{
return $this->morphTo();
}
定义相对的关联关系
通过morphMany
方法实现
public function comments()
{
return $this->morphMany(Comment::class,'commentable');
}
多对多的多态关联
通过morphedByMany
方法实现
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
morphedByMany
方法完整参数如下
public function morphedByMany($related, $name, $table = null, $foreignPivotKey = null, $relatedPivotKey = null, $parentKey = null, $relatedKey = null)
第一个参数表示关联的模型类,第二个参数表示关联名称,第三个参数表示中间表名称,第四个参数表示当前模型类在中间表的外键,第五个参数表示中间表中的关联ID字段,第六个参数表示当前模型类的主键,第七个参数表示关联模型类的主键。
定义相对的关联关系
morphToMany
方法实现
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
关联查询
关联查询分为懒惰式加载(动态属性),一种是渴求式加载(通过with
方法)。
懒惰式加载
$post = Post::findOrFail(1);
$author = $post->author;
基于关联查询过滤模型实例
有结果过滤
如要查询所有发布过文章的用户
$users = User::has('posts')->get();
获取发布文章数量大于一的
$users = User::has('posts','>',1)->get();
获取发布的文章且有评论的用户
$users = User::has('posts.comments')->get();
包含评论或标签的文章
$posts = Post::has('comments')->orHas('tags')->get();
无结果过滤
获取没有发布过文章的用户
$users = User::doesntHave('posts')->get();
统计关联模型
通过withCount
在不加载关联模型的情况下统计关联结果数量。
$post = Post::withCount('comments')->findOrFail(32);
渴求式加载
通过with
方法实现
$post = Post::with('author')->findOrFail(1);
懒惰渴求式加载
$users = User::all();
$condition = true;
if ($condition) {
$users->load('posts');
}
关联插入与更新
一对多关联记录插入
$post = Post::findOrFail(1);
$faker = \Faker\Factory::create();
$comment = new Comment();
$comment->content = $faker->paragraph;
$comment->user_id = mt_rand(1, 15);
$post->comments()->save($comment);
通过saveMany
方法一次插入多条关联记录
$post = Post::findOrFail(1);
$faker = \Faker\Factory::create();
$post->comments()->saveMany([
new Comment(['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)]),
new Comment(['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)]),
new Comment(['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)]),
new Comment(['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)]),
new Comment(['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)])
]);
还可通过create/createMany
方法来插入关联数据
// 插入一条记录
$post->comments()->create([
'content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)
]);
// 插入多条记录
$post->comments()->createMany([
['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)],
['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)],
['content' => $faker->paragraph, 'user_id' => mt_rand(1, 15)]
]);
更新一对多所属模型外键字段
更新新创建的模型实例所属模型(父模型)的外键字段
$user = User::findOrFail(1);
$post->author()->associate($user);
$post->save();
通过dissociate
方法接触当前模型与所属模型之间的关联
$post->author()->dissociate();
$post->save();
空对象模型
public function author()
{
return $this->belongsTo(User::class, 'user_id', 'id', 'author')
->withDefault();
}
空对象指定默认值
public function author()
{
return $this->belongsTo(User::class, 'user_id', 'id', 'author')
->withDefault([
'id' => 0,
'name' => '游客用户',
]);
}
多对多关联的绑定与解除
// 插入单条记录
$post->tags()->save(
new Tag(['name' => $faker->word])
);
// 如果中间表接收额外参数可以通过第二个参数传入
$post->tags()->save(
new Tag(['name' => $faker->word]),
['user_id' => 1]
);
// 插入多条记录
$post->tags()->saveMany([
new Tag(['name' => $faker->unique()->word]),
new Tag(['name' => $faker->unique()->word]),
new Tag(['name' => $faker->unique()->word])
]);
// 如果插入多条记录需要传递中间表额外字段值(通过键值关联对应记录与额外字段)
$post->tags()->saveMany([
1 => new Tag(['name' => $faker->unique()->word]),
2 => new Tag(['name' => $faker->unique()->word]),
3 => new Tag(['name' => $faker->unique()->word])
], [
1 => ['user_id' => 1],
2 => ['user_id' => 2],
3 => ['user_id' => 3],
]);
绑定通过attach
方法实现
$post = Post::findOrFail(1);
$tag = Tag::findOrFail(1);
$post->tags()->attach($tag->id);
// 如果中间表还有其它额外字段,可以通过第二个数组参数传入
// $post->tags()->attach($tag->id, ['user_id' => $userId]);
// 还可以一次绑定多个标签
// $post->tags()->attach([1, 2]);
// 如果绑定多个标签,要传递额外字段值,可以这么做:
/*$post->tags()->attach([
1 => ['user_id' => 1],
2 => ['user_id' => 2]
]);*/
解除关联关系
$post->tags()->detach(1);
// 如果想要一次解除多个关联,可以这么做:
// $post->tags()->detach([1, 2]);
// 如果想要一次解除所有关联,可以这么做:
// $post->tags()->detach();
只更新中间表字段通过updateExistingPivot
方法实现
$post->tags()->updateExistingPivot($tagId, $attributes);
触发父模型时间戳更新
需要在子模型中配置$touches
属性
// 要触发更新的父级关联关系
protected $touches = [
'commentable'
];