Laravel Eloquent查询作用域通过本地和全局作用域封装复用查询逻辑,提升代码可读性、维护性和安全性,支持链式调用、条件组合及关联查询,是构建清晰、高效数据访问层的核心工具。

Laravel Eloquent 查询作用域提供了一种极其优雅且高效的方式,将模型中常用的查询约束封装起来,实现逻辑的复用和代码的清晰化。它本质上就是把那些反复出现的 where、orWhere 等条件,抽象成一个可调用的方法,让你的查询语句读起来更像自然语言,大大提升了代码的可读性和可维护性。在我看来,掌握查询作用域是写出高质量 Laravel 应用的关键一步。
使用 Eloquent 查询作用域主要分为两种:本地作用域(Local Scopes)和全局作用域(Global Scopes)。
本地作用域 (Local Scopes)
本地作用域是最常用的一种。它允许你为模型定义一系列可复用的查询约束,并在需要时显式地调用它们。
定义本地作用域:
在 Eloquent 模型中,定义一个本地作用域的方法名必须以 scope 开头,后面跟着作用域的名称(驼峰命名)。这个方法会接收一个 Illuminate\Database\Eloquent\Builder 实例作为第一个参数。
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
// ... 其他属性和方法 ...
/**
* 查询所有活跃用户。
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* 查询指定角色的用户。
*/
public function scopeRole($query, $role)
{
return $query->where('role', $role);
}
/**
* 查询指定创建时间范围内的用户。
*/
public function scopeCreatedBetween($query, $from, $to)
{
return $query->whereBetween('created_at', [$from, $to]);
}
}使用本地作用域:
一旦定义了本地作用域,你就可以像调用模型上的普通方法一样调用它,但要省略 scope 前缀,并且作用域方法会自动接收 Builder 实例,你只需传递你定义的额外参数。
// 获取所有活跃用户
$activeUsers = User::active()->get();
// 获取所有管理员用户
$admins = User::role('admin')->get();
// 获取在特定日期范围内创建的活跃编辑
$editors = User::active()
->role('editor')
->createdBetween('2023-01-01', '2023-12-31')
->get();
// 作用域可以链式调用,非常灵活
$recentActiveAdmins = User::active()->role('admin')->latest()->get();全局作用域 (Global Scopes)
全局作用域允许你为模型添加一个始终适用的查询约束。这意味着,无论你对该模型执行任何查询,这个约束都会自动应用,除非你明确地将其移除。这对于像“软删除”或多租户应用中过滤租户ID等场景非常有用。
定义全局作用域:
全局作用域通常定义为一个单独的类,该类实现 Illuminate\Database\Eloquent\Scope 接口。这个接口要求你实现一个 apply 方法。
// app/Scopes/ActiveUserScope.php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveUserScope implements Scope
{
/**
* 将作用域应用于给定的 Eloquent 查询构建器。
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('is_active', true);
}
}应用全局作用域:
在模型的 boot 方法中注册全局作用域。
// app/Models/User.php
namespace App\Models;
use App\Scopes\ActiveUserScope; // 引入全局作用域类
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
// ... 其他属性和方法 ...
/**
* 模型“启动”时执行的任何引导逻辑。
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ActiveUserScope);
}
}现在,任何对 User 模型的查询都会自动包含 where('is_active', true) 条件:
// 这会获取所有 is_active 为 true 的用户
$users = User::all();
// 这也会获取所有 is_active 为 true 的用户,即使你没有显式调用 active()
$admins = User::where('role', 'admin')->get();移除全局作用域:
如果你需要临时移除一个或所有全局作用域,可以使用 withoutGlobalScope 或 withoutGlobalScopes 方法。
// 获取所有用户,包括 is_active 为 false 的用户 $allUsers = User::withoutGlobalScope(ActiveUserScope::class)->get(); // 移除所有全局作用域 $allUsersIncludingInactiveAndDeleted = User::withoutGlobalScopes()->get();
说实话,我个人觉得查询作用域是 Laravel 在处理数据查询方面最亮眼的设计之一。刚开始写项目的时候,我常常会在控制器或者服务层里堆砌一长串 where 条件,比如 User::where('is_active', true)->where('role', 'admin')->where('created_at', '>', now()->subDays(30))->get()。代码一多,这种重复简直让人抓狂,而且非常难以阅读和维护。
查询作用域完美地解决了这些痛点:
->where('is_active', true)。有了作用域,只需 ->active(),简洁明了。User::active()->admin()->recent()->get() 这样的代码,是不是比一堆 where 条件清晰得多?它让查询语句更接近业务逻辑的描述,提高了代码的“自文档化”能力。is_active = true 变成了 status = 'active'),你只需要修改作用域定义中的一处,而不是散落在各处的几十行代码。这大大降低了维护成本和引入bug的风险。我记得有一次,一个老项目需要紧急调整“产品上架”的逻辑,涉及到多个模块。如果不是因为之前使用了查询作用域封装了 scopePublished(),那次改动肯定会是一场灾难,需要逐个文件去查找和修改。所以,我认为它不仅仅是“方便”,更是一种“风险管理”的工具。
在选择使用全局作用域还是本地作用域时,我通常会问自己一个核心问题:这个查询条件是不是几乎所有对这个模型的查询都需要?
本地作用域:适用大多数场景,明确的、有选择性的约束。
Post::published()->get()。User::role('admin')->get()。Post::published() 时,你就知道它会过滤掉未发布的文章。这种明确性在代码阅读和调试时非常重要。如果我不确定某个条件是否总是需要,我通常会先定义成本地作用域。全局作用域:适用于模型层面的、普遍性的、默认的约束。
SoftDeletes 特性,它会自动过滤掉已软删除的记录。另一个常见场景是多租户应用,你可能希望所有对 Tenant 模型的查询都默认加上 where('tenant_id', current_tenant_id())。withoutGlobalScope() 或 withoutGlobalScopes() 临时移除。总的来说,本地作用域是你的首选,它提供了灵活性和透明度。全局作用域则是一个强大的工具,用于强制执行模型层面的默认行为,但使用时需要权衡其隐式性带来的潜在影响。
在面对复杂的业务逻辑时,查询作用域的威力远不止于简单的 where 条件封装。它能够与其他 Eloquent 特性以及一些编程技巧结合,构建出非常强大且可维护的查询系统。
1. 链式调用与组合:构建复杂的动态查询
查询作用域最大的魅力之一就是它的链式调用能力。你可以根据请求参数或业务状态,动态地组合多个作用域。
// 假设有一个商品列表,需要根据多种条件过滤
public function index(Request $request)
{
$products = Product::query();
// 根据分类ID过滤 (本地作用域)
if ($request->has('category_id')) {
$products->byCategory($request->category_id);
}
// 根据状态过滤 (本地作用域)
if ($request->has('status')) {
$products->status($request->status);
}
// 根据价格范围过滤 (本地作用域)
if ($request->has('min_price') && $request->has('max_price')) {
$products->priceRange($request->min_price, $request->max_price);
}
// 默认只显示有库存的 (全局作用域,或另一个本地作用域)
// 假设 Product 模型有一个 scopeInStock()
$products->inStock();
// 排序
$products->orderBy('created_at', 'desc');
return $products->paginate(15);
}通过这种方式,控制器逻辑变得非常清晰,它只负责判断哪些条件需要应用,具体的过滤逻辑则封装在模型的作用域中。
2. 结合 when() 方法实现条件性作用域
Laravel 的 when() 方法与查询作用域简直是天作之合,它允许你根据一个布尔条件来应用查询构建器上的操作。这对于处理可选的过滤条件非常有用,避免了大量的 if/else 语句。
// app/Models/Post.php
class Post extends Model
{
// ...
public function scopePublished($query) { return $query->where('is_published', true); }
public function scopeFeatured($query) { return $query->where('is_featured', true); }
public function scopeOfType($query, $type) { return $query->where('type', $type); }
}
// 在控制器或服务层中
public function getFilteredPosts(Request $request)
{
$posts = Post::query()
->when($request->has('type'), function ($query) use ($request) {
// 如果请求中包含 'type' 参数,则应用 ofType 作用域
$query->ofType($request->type);
})
->when($request->boolean('show_featured'), function ($query) {
// 如果请求中 'show_featured' 为 true,则应用 featured 作用域
$query->featured();
})
->when($request->boolean('show_published', true), function ($query) {
// 默认显示已发布的,除非明确指定不显示
$query->published();
})
->get();
return $posts;
}这种模式让动态查询的构建变得非常流畅和易读,我发现它能把很多原本需要复杂条件判断的逻辑简化成优雅的链式调用。
3. 作用域与关联关系 (Relationships) 的结合
查询作用域不仅可以应用于模型本身,也可以在查询关联模型时发挥作用。
查询拥有特定关联特征的父模型:
// 获取所有拥有至少一篇已发布文章的用户
$usersWithPublishedPosts = User::whereHas('posts', function ($query) {
$query->published(); // 在关联查询中应用 Post 模型的 published 作用域
})->get();加载关联模型时应用作用域:
// 获取所有用户,并加载他们已发布的文章
$users = User::with(['posts' => function ($query) {
$query->published(); // 在加载关联时过滤文章
}])->get();这在需要有条件地预加载关联数据时非常有用,避免加载不必要的数据。
4. 结合仓库模式 (Repository Pattern) 或服务层 (Service Layer)
在大型应用中,我们经常使用仓库模式或服务层来抽象数据访问。查询作用域在这里可以作为构建查询的“积木”,让仓库方法更加简洁和专注于业务意图。
// app/Repositories/PostRepository.php
class PostRepository
{
public function getPublishedPostsByType(string $type)
{
return Post::published()->ofType($type)->get();
}
public function getFeaturedPostsForAdmin()
{
// 假设有一个全局作用域用于管理员可见的帖子,或者在这里显式调用一个本地作用域
return Post::featured()->get();
}
}
// 在服务层或控制器中
$posts = $postRepository->getPublishedPostsByType('article');通过这种方式,查询作用域将底层的 where 逻辑隐藏在模型内部,而仓库或服务层则通过调用这些语义化的作用域来构建查询,进一步提升了整个应用架构的清晰度。
在我看来,查询作用域是 Eloquent 提供的一个核心工具,它鼓励我们以更声明式、更具表现力的方式来编写数据查询。掌握这些进阶用法,能够显著提升代码质量和开发效率。
以上就是Laravel Eloquent如何使用查询作用域_可复用的查询逻辑封装的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号