Laravel中如何定义模型范围Scopes_Laravel本地作用域Scope使用方法【详解】

11次阅读

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

Laravel中如何定义模型范围Scopes_Laravel本地作用域Scope使用方法【详解】

本地作用域(Local Scopes)在 laravel 模型中不是“必须用”,但一旦需要复用查询逻辑,它就是最干净、最易维护的写法。

什么是本地作用域?

本地作用域是定义在 Eloquent 模型里的方法,以 scope 开头,用于封装常用查询条件。它不是全局生效的,只对调用它的模型实例起作用。

常见错误现象:Call to undefined method appModelsUser::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)

注意:作用域方法第一个参数固定是 $queryIlluminatedatabaseEloquentBuilder),不要写成 $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 注解或改用查询构造器对象显式传递。

text=ZqhQzanResources