什么是关联
数据库中的表之间经常存在关联。例如一篇博客文章有多条评论,一笔订单归属于下单的用户。
Eloquent 提供了简洁定义和操作这些关联的方式。关联作为 Eloquent 模型的方法进行定义。
本页以 User、Post、Comment、Tag 等模型为例。假设这些模型与表已经创建好了。
Eloquent 支持的主要关联类型:
关联 说明 hasOne一对一(父拥有一个子) belongsTo一对一的反向(子属于父) hasMany一对多(父拥有多个子) belongsToMany多对多(通过中间表)
hasOne(一对一)
hasOne 表示某模型拥有另一个模型的一条记录。例如 User 拥有一个 Profile。
定义关联
在 User 模型定义 profile 方法:
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ HasOne ;
class User extends Model
{
public function profile () : HasOne
{
return $this -> hasOne ( Profile :: class );
}
}
Eloquent 根据父模型名推断外键,此处会假定 profiles 表有 user_id 列。
获取关联数据
关联可以作为属性访问:
$user = User :: find ( 1 );
$profile = $user -> profile ;
以属性方式调用关联方法时,Eloquent 会自动发起查询并返回数据。这称为「动态关联属性」。
belongsTo(一对一的反向)
belongsTo 是 hasOne 的反向,定义在拥有外键的一侧。例如 Profile 属于 User。
定义关联
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ BelongsTo ;
class Profile extends Model
{
public function user () : BelongsTo
{
return $this -> belongsTo ( User :: class );
}
}
Eloquent 会用「方法名 + _id」(此处为 user_id)作为外键。
获取关联数据
$profile = Profile :: find ( 1 );
$user = $profile -> user ;
echo $user -> name ;
hasMany(一对多)
hasMany 是最常见的一对多关联。例如一篇 Post 拥有多条 Comment。
定义关联
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ HasMany ;
class Post extends Model
{
public function comments () : HasMany
{
return $this -> hasMany ( Comment :: class );
}
}
Eloquent 会假定 comments 表有 post_id。
获取关联数据
hasMany 返回集合:
$post = Post :: find ( 1 );
foreach ( $post -> comments as $comment ) {
echo $comment -> body ;
}
也可以追加查询条件:
$recentComments = Post :: find ( 1 )
-> comments ()
-> latest ()
-> take ( 5 )
-> get ();
反向关联(belongsTo)
从评论访问文章:
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ BelongsTo ;
class Comment extends Model
{
public function post () : BelongsTo
{
return $this -> belongsTo ( Post :: class );
}
}
$comment = Comment :: find ( 1 );
echo $comment -> post -> title ;
belongsToMany(多对多)
多对多用于两个模型互相拥有多个关联。例如 Post 有多个 Tag,Tag 也属于多个 Post。
表结构
多对多需要中间表。例如 Post 与 Tag 使用 post_tag 中间表:
posts
id - integer
title - string
tags
id - integer
name - string
post_tag
post_id - integer
tag_id - integer
Eloquent 会按字母顺序拼接两个模型名推断中间表名(post + tag → post_tag)。
定义关联
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ BelongsToMany ;
class Post extends Model
{
public function tags () : BelongsToMany
{
return $this -> belongsToMany ( Tag :: class );
}
}
在 Tag 上同样定义即可反向访问:
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Illuminate\Database\Eloquent\Relations\ BelongsToMany ;
class Tag extends Model
{
public function posts () : BelongsToMany
{
return $this -> belongsToMany ( Post :: class );
}
}
获取关联数据
$post = Post :: find ( 1 );
foreach ( $post -> tags as $tag ) {
echo $tag -> name ;
}
增删关联数据
用 attach 添加、detach 删除中间表记录:
$post = Post :: find ( 1 );
// 添加标签
$post -> tags () -> attach ( $tagId );
// 删除标签
$post -> tags () -> detach ( $tagId );
// 完全替换当前关联
$post -> tags () -> sync ([ $tagId1 , $tagId2 ]);
sync 会自动删除未指定的中间表记录,只保留传入的 ID。
预加载(Eager Loading)
什么是 N+1 问题
以属性方式访问关联,Eloquent 每次都会发查询。这称为「延迟加载」。在循环中访问会产生 N+1 问题。
// 取全部文章 1 次查询
$posts = Post :: all ();
foreach ( $posts as $post ) {
// 每次循环再查一次 user(N 次)
echo $post -> user -> name ;
}
100 篇文章会产生共 101 次查询,性能问题很大。
with() 预加载
with() 一次性把关联取回来:
// 只查 2 次
$posts = Post :: with ( 'user' ) -> get ();
foreach ( $posts as $post ) {
// 不再有额外查询
echo $post -> user -> name ;
}
执行的 SQL 只有两条:
select * from posts
select * from users where id in ( 1 , 2 , 3 , ...)
同时预加载多个关联
$posts = Post :: with ([ 'user' , 'comments' , 'tags' ]) -> get ();
嵌套预加载
用点号访问嵌套关联:
// 预加载 comments 及其 user
$posts = Post :: with ( 'comments.user' ) -> get ();
预加载对避免 N+1 至关重要。循环中访问关联时请养成使用 with() 的习惯。
下一步
Eloquent 入门 回顾 Eloquent 的基本 CRUD。