本地作用域是Eloquent模型中以scope开头的public方法,用于封装可复用查询条件;调用时省略scope前缀,必须返回$query实例,支持链式调用与参数传入,适用于业务语义明确的固定条件筛选。

本地作用域(Local Scopes)在 Laravel 模型中不是“必须用”,但一旦需要复用查询逻辑,它就是最干净、最易维护的写法。
什么是本地作用域?
本地作用域是定义在 Eloquent 模型里的方法,以 scope 开头,用于封装常用查询条件。它不是全局生效的,只对调用它的模型实例起作用。
常见错误现象:Call to undefined method App\Models\User::popular() —— 忘记加 scope 前缀,或没用 public 修饰符。
使用场景:筛选「已启用的用户」、「最近 7 天创建的订单」、「状态为 pending 的文章」等固定组合条件。
- 方法名必须以
scope开头(如scopeActive),调用时去掉scope,直接写->active() - 必须返回
$this(即当前查询构造器Builder实例),否则链式调用会中断 - 参数不能依赖运行时上下文(比如不能直接读
request()),所有外部输入应显式传入
如何定义和调用一个基础本地作用域
以 User 模型为例,定义一个只查启用状态的范围:
class User extends Model
{
public function scopeActive($query)
{
return $query->where('status', 'active');
}
}
调用方式:
User::active()->get()-
User::where('name', 'like', '%john%')->active()->first()(可与其他查询混用) User::active()->orderBy('created_at', 'desc')->paginate(10)
注意:作用域方法第一个参数固定是 $query(Illuminate\Database\Eloquent\Builder),不要写成 $this 或漏掉。
带参数的本地作用域怎么写?
比如按时间范围筛选订单:
class Order extends Model
{
public function scopeWithinDays($query, int $days = 7)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}
调用:Order::withinDays(30)->get() 或 Order::withinDays()->get()(用默认值)。
容易踩的坑:
- 参数类型未声明(如
$days是字符串却当整数用),PHP 8+ 会报TypeError - 传入
null且没做空值判断,subDays(null)会抛异常 - 作用域里用了
$this->id这类实例属性 —— 错!本地作用域在构建阶段执行,模型实例尚未存在
本地作用域 vs 全局作用域 vs 查询构造器方法
三者定位不同:
-
本地作用域:按需显式调用,适合业务语义明确、不总被使用的条件(如->draft()、->byCategory($id)) -
全局作用域(GlobalScopes):自动附加,适合全表统一规则(如软删除、租户隔离),但调试困难、不易关闭 -
查询构造器方法(如whereNotNull()):Laravel 内置,不可扩展,不带业务含义
性能影响几乎为零 —— 本地作用域只是语法糖,最终都编译成普通 where 条件;但若在作用域里做了 N+1 查询(比如 foreach + load()),就违背了设计初衷。
真正容易被忽略的是:本地作用域无法被静态分析工具(如 PHPStan)准确推断返回类型,复杂链式调用时 IDE 可能提示“Method not found”,这时候建议补上 PHPDoc 注解或改用查询构造器对象显式传递。










