Laravel的查询作用域通过封装复用查询逻辑提升代码可维护性,局部作用域需手动调用且以scope开头命名,全局作用域则自动应用于所有查询,适用于软删除等通用约束,两者在应用方式、场景和定义位置上存在差异,合理使用并遵循命名清晰、单一职责等最佳实践可避免常见误区。

Laravel的查询作用域,在我看来,真的是Eloquent模型里一个挺优雅的特性。它允许我们把那些经常重复使用的查询逻辑封装起来,放在模型内部,这样代码就干净多了,也更容易维护。而局部作用域,就是其中最常见的一种,直接在你的模型文件里定义就行。它不是那种一劳永逸的全局设置,而是需要你主动去调用的,非常灵活。
要定义一个局部作用域,其实非常直接。你只需要在你的Eloquent模型中创建一个方法,这个方法名必须以
scope
Builder
比如,我们有一个
Post
$publishedPosts = Post::where('published', true)->get();但如果这个条件在很多地方都会用到,每次都写一遍就显得有点冗余了。这时候,我们就可以定义一个局部作用域:
// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
/**
* 作用域:只查询已发布的文章。
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePublished(Builder $query): Builder
{
return $query->where('published', true);
}
/**
* 作用域:查询指定作者的文章。
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $userId
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeByAuthor(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}
}定义好之后,调用起来就非常简洁了:
// 获取所有已发布的文章 $publishedPosts = Post::published()->get(); // 获取指定作者的已发布文章 $authorId = 1; $authorPublishedPosts = Post::byAuthor($authorId)->published()->get();
你看,代码是不是清晰了很多?它把查询的意图表达得更明确,而不是一堆
where
这确实是一个常被问到的问题,因为它们虽然都叫“作用域”,但用途和行为逻辑上还是有挺大区别的。简单来说,全局作用域是“默认”的,而局部作用域是“可选”的。
全局作用域 (Global Scopes)
全局作用域就像它的名字一样,是作用于所有对该模型的查询。一旦你给模型应用了一个全局作用域,除非你明确地去移除它,否则每次你查询这个模型时,这个作用域的条件都会自动带上。
它最典型的应用场景就是“软删除”(Soft Deletes)。当你给模型启用了软删除,Laravel会自动添加一个全局作用域,在查询时过滤掉
deleted_at
null
where('tenant_id', current_tenant_id())定义全局作用域需要实现
Illuminate\Database\Eloquent\Scope
// app/Scopes/PublishedGlobalScope.php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class PublishedGlobalScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->where('published', true);
}
}
// 在模型中注册
// app/Models/Post.php
protected static function boot()
{
parent::boot();
static::addGlobalScope(new PublishedGlobalScope);
}这样,
Post::all()
Post::find(1)
where('published', true)withoutGlobalScope
withoutGlobalScopes
局部作用域 (Local Scopes)
前面已经详细讲了,局部作用域是定义在模型内部的方法,需要以
scope
主要区别总结:
选择哪种作用域,主要看你的业务需求。如果某个条件几乎总是需要,那就考虑全局作用域;如果只是特定场景下需要,局部作用域会是更好的选择,因为它给了你更多的灵活性。
这是局部作用域非常实用的一个地方,它不仅能封装固定逻辑,还能根据传入的参数动态调整查询条件。而且,Laravel的查询构建器本身就支持链式调用,所以作用域自然也继承了这一优势。
传递参数
当你定义局部作用域时,除了第一个
Builder
我们以上面的
scopeByAuthor
// app/Models/Post.php
public function scopeByAuthor(Builder $query, int $userId): Builder
{
return $query->where('user_id', $userId);
}调用时,直接把参数传进去就行:
$postsByUser1 = Post::byAuthor(1)->get(); // 查询 user_id 为 1 的文章 $postsByUser5 = Post::byAuthor(5)->get(); // 查询 user_id 为 5 的文章
这使得作用域变得非常灵活和通用,你可以根据不同的业务逻辑传入不同的值。
链式调用
局部作用域的设计初衷就是为了让查询构建器更具表现力。由于每个作用域方法都返回了
Builder
比如,我们想获取某个作者的所有已发布且按标题升序排列的文章:
// 假设 Post 模型中还有 scopeActive() 和 orderBy()
$authorId = 1;
$posts = Post::published() // 调用 published 作用域
->byAuthor($authorId) // 调用 byAuthor 作用域并传入参数
->orderBy('title', 'asc') // 调用原生的 orderBy 方法
->get();这段代码的可读性非常好,几乎一眼就能明白它的意图:获取已发布、由某个作者撰写、并按标题排序的文章。这种链式调用极大地提升了代码的表达力和简洁性,避免了大量重复的
where
在使用查询作用域时,虽然它能带来很多便利,但如果不注意,也可能会踩到一些小坑或者写出不够优雅的代码。
常见误区:
where
scopeFilterSomething
withoutGlobalScope
最佳实践:
scopePublished
scopeActive
scopePublished
scopeUnpublished
scopeLatest
scopeOlderThan
scopeByStatus(string $status)
Builder
遵循这些原则,你的Laravel查询作用域不仅能让代码更整洁,还能真正提升开发效率和项目的可维护性。
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号