痛饮狂歌

Laravel 中的查询作用域详解

2025-06-05
10 分钟阅读
入门教程

在 Laravel 的 Eloquent ORM 中,查询作用域(Query Scopes)是强大的工具,允许你封装常用的查询逻辑,使代码更简洁、可复用且易于维护。

#Laravel #查询作用域 #PHP #后端开发

Laravel 中的查询作用域详解

在 Laravel 的 Eloquent ORM 中,查询作用域(Query Scopes)是强大的工具,允许你封装常用的查询逻辑,为指定模型的所有查询添加约束,使代码更简洁、可复用且易于维护。Laravel 自身的软删除功能正是利用全局作用域,确保仅从数据库检索”未删除”的模型。

查询作用域分为两种类型:局部作用域和全局作用域。

局部作用域 (Local Scopes)

局部作用域是最常用的类型,它们允许你定义可链式调用的查询约束。

基本特征

  • 命名约定:方法名以 scope 开头(如 scopeActive)
  • 调用方式:调用时去掉 scope 前缀,使用驼峰命名(如 active())
  • 参数:可以接受额外参数
  • 返回值:必须返回查询构建器实例

定义局部作用域

// 在模型中定义
public function scopeActive($query)
{
    return $query->where('status', 'active');
}

public function scopeOfType($query, $type)
{
    return $query->where('type', $type);
}

使用局部作用域

// 基本使用
$users = User::active()->get();

// 带参数
$admins = User::ofType('admin')->get();

// 链式调用
$users = User::active()->ofType('premium')->orderBy('name')->get();

动态作用域

你可以创建更灵活的动态作用域:

public function scopeWhereLike($query, $column, $value)
{
    return $query->where($column, 'like', '%'.$value.'%');
}

// 使用
User::whereLike('email', '@gmail.com')->get();

全局作用域 (Global Scopes)

全局作用域会自动应用到模型的所有查询上,非常适合添加全局约束(如多租户隔离、软删除等)。

生成全局作用域

从 Laravel 11 开始,我们可以调用 make:scope Artisan 命令生成的查询作用域,生成的文件保存在应用程序的 app/Models/Scopes 目录中:

php artisan make:scope AncientScope

Laravel 11 之前的版本须手动创建查询作用域,且未对文件保存位置做出要求。

定义全局作用域

方法 1:使用闭包

// 在模型的 boot 方法中注册
protected static function booted()
{
    static::addGlobalScope('active', function (Builder $builder) {
        $builder->where('active', true);
    });
}

方法 2:使用 Scope 类

  1. 创建 Scope 类:
// app/Scopes/TenantScope.php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model)
    {
        $builder->where('tenant_id', auth()->user()->tenant_id);
    }
}
  1. 在模型中注册:
protected static function booted()
{
    static::addGlobalScope(new TenantScope);
}

移除全局作用域

// 移除特定作用域
User::withoutGlobalScope(TenantScope::class)->get();

// 移除闭包作用域
User::withoutGlobalScope('active')->get();

// 移除所有全局作用域
User::withoutGlobalScopes()->get();

// 移除多个作用域
User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get();

查询作用域的最佳实践

1. 保持作用域单一职责

// 好:单一职责
public function scopeActive($query) {
    return $query->where('active', true);
}

public function scopeRecent($query) {
    return $query->orderBy('created_at', 'desc');
}

// 避免:职责过多
public function scopeActiveRecentUsers($query) {
    return $query->where('active', true)->orderBy('created_at', 'desc');
}

2. 组合使用作用域

// 控制器中
public function index()
{
    return User::active()
               ->verified()
               ->withRole('admin')
               ->orderByName()
               ->paginate(10);
}

3. 在作用域中使用条件逻辑

public function scopeFilter($query, array $filters)
{
    return $query->when($filters['search'] ?? null, function ($query, $search) {
        $query->where(function ($query) use ($search) {
            $query->where('name', 'like', "%{$search}%")
                  ->orWhere('email', 'like', "%{$search}%");
        });
    })->when($filters['role'] ?? null, function ($query, $role) {
        $query->whereRole($role);
    });
}

4. 作用域中的关系处理

public function scopeWithPostsCount($query)
{
    return $query->withCount(['posts' => function ($query) {
        $query->where('published', true);
    }]);
}

// 使用
$users = User::withPostsCount()->get();

查询作用域 vs 普通方法

特性查询作用域普通方法
调用方式Model::scopeName()$model->method()
返回值查询构建器实例任意类型
链式调用支持不一定
应用位置查询过程中模型实例上
典型用途构建查询约束业务逻辑处理

常见应用场景

1. 状态过滤:

public function scopePending($query) {
    return $query->where('status', 'pending');
}

2. 时间范围:

public function scopeCreatedBetween($query, $from, $to) {
    return $query->whereBetween('created_at', [$from, $to]);
}

3. 排序逻辑:

public function scopePopular($query) {
    return $query->orderBy('views', 'desc');
}

4. 软删除处理:

public function scopeWithTrashed($query) {
    return $query->withTrashed();
}

5. 权限限制:

public function scopeVisibleTo($query, User $user) {
    return $query->where('organization_id', $user->organization_id);
}

总结

Laravel 的查询作用域是优化数据库查询的强大工具:

  • 局部作用域:用于创建可复用的查询片段,通过链式调用组合复杂查询
  • 全局作用域:用于自动应用全局查询约束
  • 提高代码质量:减少重复代码,提高可读性和可维护性
  • 灵活的参数支持:可以接受各种参数创建动态查询
  • 组合使用:多个作用域可以链式组合构建复杂查询

合理使用查询作用域可以显著提升 Laravel 应用的代码质量和开发效率,是 Eloquent ORM 的核心特性之一。