Laravel Eloquent ORM 高级用法详解:关联关系、查询优化与模型事件
Orion K Lv6

前言

Eloquent ORM 是 Laravel 框架中最强大的特性之一,它提供了优雅的 ActiveRecord 实现来与数据库交互。1 本文将深入探讨 Eloquent ORM 的高级用法,包括复杂的关联关系、查询优化技巧和模型事件处理。

基础模型定义与配置

模型基本设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model
{
use HasFactory, SoftDeletes;

/**
* 关联的数据表
*/
protected $table = 'posts';

/**
* 主键字段
*/
protected $primaryKey = 'id';

/**
* 是否自动维护时间戳
*/
public $timestamps = true;

/**
* 日期字段
*/
protected $dates = ['published_at', 'deleted_at'];

/**
* 可批量赋值的字段(白名单)
*/
protected $fillable = [
'title', 'content', 'excerpt', 'published_at', 'user_id', 'category_id'
];

/**
* 不可批量赋值的字段(黑名单)
*/
protected $guarded = ['id', 'created_at', 'updated_at'];

/**
* 字段类型转换
*/
protected $casts = [
'published_at' => 'datetime',
'is_featured' => 'boolean',
'meta_data' => 'array',
'view_count' => 'integer'
];

/**
* 默认属性值
*/
protected $attributes = [
'status' => 'draft',
'view_count' => 0
];
}

模型关联关系详解

一对一关联 (One To One)

2 一对一是最基本的关联关系,例如用户与用户资料的关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// User 模型
class User extends Model
{
/**
* 获取用户资料
*/
public function profile()
{
return $this->hasOne(UserProfile::class, 'user_id', 'id');
}

/**
* 获取用户的手机号
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}

// UserProfile 模型
class UserProfile extends Model
{
/**
* 获取资料所属的用户
*/
public function user()
{
return $this->belongsTo(User::class);
}
}

// 使用示例
$user = User::find(1);
$profile = $user->profile; // 获取用户资料
$phone = $user->phone; // 获取用户手机

// 反向关联
$profile = UserProfile::find(1);
$user = $profile->user; // 获取资料所属用户

一对多关联 (One To Many)

3 一对多关联用于定义单个模型拥有多个关联模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Post 模型
class Post extends Model
{
/**
* 获取文章的所有评论
*/
public function comments()
{
return $this->hasMany(Comment::class, 'post_id', 'id');
}

/**
* 获取已发布的评论
*/
public function publishedComments()
{
return $this->hasMany(Comment::class)
->where('status', 'published')
->orderBy('created_at', 'desc');
}
}

// Comment 模型
class Comment extends Model
{
/**
* 获取评论所属的文章
*/
public function post()
{
return $this->belongsTo(Post::class);
}

/**
* 获取评论的作者
*/
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
}

// 使用示例
$post = Post::find(1);
$comments = $post->comments; // 获取所有评论
$publishedComments = $post->publishedComments; // 获取已发布评论

// 创建关联记录
$post->comments()->create([
'content' => '这是一条评论',
'user_id' => auth()->id()
]);

多对多关联 (Many To Many)

多对多关联通过中间表来实现,例如用户与角色的关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// User 模型
class User extends Model
{
/**
* 获取用户的所有角色
*/
public function roles()
{
return $this->belongsToMany(
Role::class,
'user_roles', // 中间表名
'user_id', // 当前模型在中间表的外键
'role_id' // 关联模型在中间表的外键
)->withPivot('assigned_at', 'assigned_by')
->withTimestamps();
}

/**
* 获取活跃的角色
*/
public function activeRoles()
{
return $this->belongsToMany(Role::class, 'user_roles')
->wherePivot('status', 'active');
}
}

// Role 模型
class Role extends Model
{
/**
* 获取拥有此角色的所有用户
*/
public function users()
{
return $this->belongsToMany(User::class, 'user_roles')
->withPivot('assigned_at', 'assigned_by')
->withTimestamps();
}
}

// 使用示例
$user = User::find(1);
$roles = $user->roles; // 获取用户角色

// 访问中间表数据
foreach ($user->roles as $role) {
echo $role->pivot->assigned_at; // 分配时间
echo $role->pivot->assigned_by; // 分配者
}

// 附加角色
$user->roles()->attach($roleId, [
'assigned_at' => now(),
'assigned_by' => auth()->id()
]);

// 分离角色
$user->roles()->detach($roleId);

// 同步角色(替换所有角色)
$user->roles()->sync([1, 2, 3]);

多态关联 (Polymorphic Relations)

多态关联允许一个模型在单个关联中属于多个其他模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 多态一对多:评论系统
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');
}
}

// 使用示例
$post = Post::find(1);
$comments = $post->comments; // 获取文章评论

$video = Video::find(1);
$comments = $video->comments; // 获取视频评论

// 创建多态评论
$post->comments()->create([
'content' => '文章评论',
'user_id' => 1
]);

// 多态多对多:标签系统
class Tag extends Model
{
/**
* 获取所有可标记的模型
*/
public function taggables()
{
return $this->morphToMany(Post::class, 'taggable')
->union(
$this->morphToMany(Video::class, 'taggable')
);
}
}

class Post extends Model
{
/**
* 获取文章的所有标签
*/
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

高级查询技巧

预加载 (Eager Loading)

2 预加载可以解决 N+1 查询问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// N+1 问题示例(避免这样做)
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name; // 每次循环都会执行一次查询
}

// 使用预加载解决 N+1 问题
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name; // 只执行两次查询
}

// 多层预加载
$posts = Post::with('user.profile', 'comments.author')->get();

// 条件预加载
$posts = Post::with([
'comments' => function ($query) {
$query->where('status', 'approved')
->orderBy('created_at', 'desc')
->limit(5);
},
'user' => function ($query) {
$query->select('id', 'name', 'email');
}
])->get();

// 延迟预加载
$posts = Post::all();
$posts->load('user', 'comments');

// 条件延迟预加载
$posts->loadWhen($someCondition, 'user');

关联查询与过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 基于关联存在性查询
$posts = Post::has('comments')->get(); // 有评论的文章
$posts = Post::has('comments', '>=', 5)->get(); // 评论数 >= 5 的文章
$posts = Post::doesntHave('comments')->get(); // 没有评论的文章

// 基于关联条件查询
$posts = Post::whereHas('comments', function ($query) {
$query->where('status', 'approved');
})->get();

// 基于关联不存在条件查询
$posts = Post::whereDoesntHave('comments', function ($query) {
$query->where('status', 'spam');
})->get();

// 关联计数
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->comments_count; // 评论数量
}

// 条件计数
$posts = Post::withCount([
'comments',
'comments as approved_comments_count' => function ($query) {
$query->where('status', 'approved');
}
])->get();

// 关联聚合
$posts = Post::withSum('comments', 'votes')->get();
$posts = Post::withAvg('comments', 'rating')->get();
$posts = Post::withMax('comments', 'created_at')->get();

查询作用域 (Query Scopes)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Post extends Model
{
/**
* 本地作用域:已发布的文章
*/
public function scopePublished($query)
{
return $query->where('status', 'published');
}

/**
* 本地作用域:按分类筛选
*/
public function scopeOfCategory($query, $categoryId)
{
return $query->where('category_id', $categoryId);
}

/**
* 本地作用域:热门文章
*/
public function scopePopular($query, $threshold = 100)
{
return $query->where('view_count', '>=', $threshold)
->orderBy('view_count', 'desc');
}

/**
* 本地作用域:最近发布
*/
public function scopeRecent($query, $days = 7)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}

// 使用作用域
$posts = Post::published()->ofCategory(1)->get();
$popularPosts = Post::popular(200)->recent(30)->get();

// 全局作用域
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class PublishedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('status', 'published');
}
}

// 在模型中应用全局作用域
class Post extends Model
{
protected static function booted()
{
static::addGlobalScope(new PublishedScope);

// 或者使用闭包
static::addGlobalScope('published', function (Builder $builder) {
$builder->where('status', 'published');
});
}
}

模型事件与观察者

模型事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Post extends Model
{
protected static function booted()
{
// 创建前事件
static::creating(function ($post) {
$post->slug = Str::slug($post->title);
$post->user_id = auth()->id();
});

// 创建后事件
static::created(function ($post) {
// 发送通知
Notification::send(
User::where('role', 'admin')->get(),
new NewPostNotification($post)
);
});

// 更新前事件
static::updating(function ($post) {
if ($post->isDirty('title')) {
$post->slug = Str::slug($post->title);
}
});

// 删除前事件
static::deleting(function ($post) {
// 删除关联的评论
$post->comments()->delete();

// 删除关联的文件
Storage::delete($post->featured_image);
});
}
}

模型观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 创建观察者
php artisan make:observer PostObserver --model=Post

// PostObserver.php
class PostObserver
{
/**
* 监听模型创建事件
*/
public function creating(Post $post)
{
$post->slug = Str::slug($post->title);
$post->excerpt = Str::limit(strip_tags($post->content), 150);
}

/**
* 监听模型创建完成事件
*/
public function created(Post $post)
{
// 清除缓存
Cache::tags(['posts'])->flush();

// 记录日志
Log::info('新文章已创建', ['post_id' => $post->id]);
}

/**
* 监听模型更新事件
*/
public function updating(Post $post)
{
if ($post->isDirty('status') && $post->status === 'published') {
$post->published_at = now();
}
}

/**
* 监听模型更新完成事件
*/
public function updated(Post $post)
{
// 清除特定缓存
Cache::forget("post.{$post->id}");
}

/**
* 监听模型删除事件
*/
public function deleting(Post $post)
{
// 软删除时保留数据,硬删除时清理关联数据
if ($post->isForceDeleting()) {
$post->comments()->forceDelete();
Storage::delete($post->attachments->pluck('file_path')->toArray());
}
}
}

// 注册观察者(在 AppServiceProvider 中)
use App\Models\Post;
use App\Observers\PostObserver;

public function boot()
{
Post::observe(PostObserver::class);
}

高级特性与优化

批量操作

4 Eloquent 提供了多种批量操作方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 批量插入
Post::insert([
['title' => '文章1', 'content' => '内容1', 'created_at' => now()],
['title' => '文章2', 'content' => '内容2', 'created_at' => now()],
['title' => '文章3', 'content' => '内容3', 'created_at' => now()],
]);

// 批量更新
Post::where('status', 'draft')
->update(['status' => 'published', 'published_at' => now()]);

// 批量删除
Post::where('created_at', '<', now()->subYear())->delete();

// 使用 upsert 进行批量插入或更新
Post::upsert([
['id' => 1, 'title' => '更新标题1', 'view_count' => 100],
['id' => 2, 'title' => '更新标题2', 'view_count' => 200],
], ['id'], ['title', 'view_count']);

// 分块处理大量数据
Post::chunk(1000, function ($posts) {
foreach ($posts as $post) {
// 处理每个文章
$post->processContent();
}
});

// 游标分页(内存友好)
foreach (Post::cursor() as $post) {
// 逐个处理,不会加载所有记录到内存
$post->generateThumbnail();
}

自定义集合方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 创建自定义集合类
use Illuminate\Database\Eloquent\Collection;

class PostCollection extends Collection
{
/**
* 获取已发布的文章
*/
public function published()
{
return $this->filter(function ($post) {
return $post->status === 'published';
});
}

/**
* 按分类分组
*/
public function groupByCategory()
{
return $this->groupBy('category.name');
}

/**
* 计算总阅读量
*/
public function totalViews()
{
return $this->sum('view_count');
}
}

// 在模型中指定自定义集合
class Post extends Model
{
/**
* 创建新的 Eloquent 集合实例
*/
public function newCollection(array $models = [])
{
return new PostCollection($models);
}
}

// 使用自定义集合方法
$posts = Post::all();
$publishedPosts = $posts->published();
$totalViews = $posts->totalViews();
$groupedPosts = $posts->groupByCategory();

性能优化技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1. 选择特定字段
$users = User::select('id', 'name', 'email')->get();

// 2. 使用索引优化查询
$posts = Post::where('status', 'published')
->where('created_at', '>=', now()->subDays(30))
->orderBy('created_at', 'desc')
->get();

// 3. 避免 N+1 查询
$posts = Post::with(['user:id,name', 'category:id,name'])->get();

// 4. 使用查询缓存
$posts = Cache::remember('popular_posts', 3600, function () {
return Post::popular()->with('user')->take(10)->get();
});

// 5. 分页优化
$posts = Post::select('id', 'title', 'excerpt', 'created_at')
->with('user:id,name')
->latest()
->paginate(15);

// 6. 使用原生查询处理复杂统计
$stats = DB::select('
SELECT
category_id,
COUNT(*) as post_count,
AVG(view_count) as avg_views,
MAX(created_at) as latest_post
FROM posts
WHERE status = "published"
GROUP BY category_id
');

实际应用示例

博客系统的完整模型关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 用户模型
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}

public function comments()
{
return $this->hasMany(Comment::class);
}

public function profile()
{
return $this->hasOne(UserProfile::class);
}

public function roles()
{
return $this->belongsToMany(Role::class);
}
}

// 文章模型
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}

public function category()
{
return $this->belongsTo(Category::class);
}

public function tags()
{
return $this->belongsToMany(Tag::class);
}

public function comments()
{
return $this->hasMany(Comment::class);
}

public function approvedComments()
{
return $this->hasMany(Comment::class)
->where('status', 'approved');
}
}

// 复杂查询示例
class PostService
{
/**
* 获取热门文章及相关数据
*/
public function getPopularPostsWithDetails()
{
return Post::with([
'user:id,name,avatar',
'category:id,name,slug',
'tags:id,name',
'approvedComments' => function ($query) {
$query->with('user:id,name')
->latest()
->limit(3);
}
])
->withCount(['comments', 'approvedComments'])
->where('status', 'published')
->where('view_count', '>=', 100)
->orderBy('view_count', 'desc')
->paginate(10);
}

/**
* 获取用户的文章统计
*/
public function getUserPostStats($userId)
{
return User::with([
'posts' => function ($query) {
$query->select('id', 'user_id', 'status', 'view_count', 'created_at');
}
])
->withCount([
'posts',
'posts as published_posts_count' => function ($query) {
$query->where('status', 'published');
},
'posts as draft_posts_count' => function ($query) {
$query->where('status', 'draft');
}
])
->withSum('posts', 'view_count')
->find($userId);
}
}

总结

Eloquent ORM 提供了强大而优雅的数据库操作方式。5 通过合理使用关联关系、查询优化和模型事件,我们可以构建高效、可维护的应用程序。

关键要点:

  1. 关联关系设计:正确定义模型间的关联关系是构建复杂应用的基础
  2. 查询优化:使用预加载、作用域和适当的索引来提高查询性能
  3. 模型事件:利用模型事件和观察者来处理业务逻辑和数据一致性
  4. 批量操作:对于大量数据操作,使用批量方法和分块处理
  5. 缓存策略:合理使用缓存来减少数据库查询压力

掌握这些高级特性将帮助你构建更加健壮和高效的 Laravel 应用程序。

本站由 提供部署服务