发布于 2026-01-06 2 阅读
0

Advanced Laravel Eloquent: Custom Query Builders and Model Scopes

Laravel Eloquent 进阶:自定义查询构建器和模型作用域

在使用 Laravel 时,Eloquent ORM 是最强大的工具之一。虽然使用 where()、orderBy() 和关系的基本查询可以满足 80% 的实际需求,但高级应用程序通常需要可重用、简洁且可扩展的查询逻辑。

这时,自定义查询构建器和模型作用域就派上了用场。它们允许你从控制器和服务中提取业务逻辑,保持查询的表达力,并提高可维护性。

指数

  1. 介绍
  2. 模型范围
  3. 自定义查询构建器
  4. 结合范围和构建器
  5. 何时使用什么
  6. 利用示波器进行性能优化
  7. 真实案例研究
  8. 要点总结
  9. 常见问题解答
  10. 结论

1. 模型范围

作用域是预定义的查询过滤器,可以在项目中重复使用。

示例:活跃用户范围
假设您有一个用户模型,其中用户可以是活跃用户或非活跃用户。

与其反复书写:

$activeUsers = User::where('status', 'active')->get();

Enter fullscreen mode Exit fullscreen mode

您可以在 User.php 中创建作用域:

class User extends Model
{
    // Local Scope
    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }

    // Dynamic Scope Example
    public function scopeStatus($query, $status)
    {
        return $query->where('status', $status);
    }
}

Enter fullscreen mode Exit fullscreen mode

现在你可以拨打:

$activeUsers = User::active()->get();
$inactiveUsers = User::status('inactive')->get();

Enter fullscreen mode Exit fullscreen mode

更清洁、可重复使用、更具表现力。

“由于我在实际应用中频繁使用它,所以我往往也能很快发现框架中的不足之处。社区在很多方面推动着框架的发展,我也会不时地提出自己的见解。”——泰勒·奥特韦尔

2. 全球视野

有时您需要在所有地方应用自动查询条件。
例如,在多租户 SaaS 应用中,每个模型都应该根据 tenant_id 自动筛选数据。

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

将其连接到您的模型上:

class Project extends Model
{
    protected static function booted()
    {
        static::addGlobalScope(new TenantScope);
    }
}
Enter fullscreen mode Exit fullscreen mode

现在:

Project::all(); // always filtered by tenant_id automatically

Enter fullscreen mode Exit fullscreen mode

3. 自定义查询构建器

对于更复杂的逻辑,您可以创建自定义查询构建器类。

实时示例:筛选订单

假设您正在构建一个电子商务控制面板,管理员可以按状态、日期或支付方式筛选订单。
与其在控制器中编写多个 if/else 查询,不如创建一个自定义查询构建器。

步骤 1:创建自定义构建器

namespace App\QueryBuilders;
use Illuminate\Database\Eloquent\Builder;

class OrderBuilder extends Builder
{
    public function paid()
    {
        return $this->where('payment_status', 'paid');
    }

    public function status($status)
    {
        return $this->where('status', $status);
    }
    public function dateBetween($start, $end)
    {
        return $this->whereBetween('created_at', [$start, $end]);
    }
}

Enter fullscreen mode Exit fullscreen mode

步骤 2:连接到模型

use App\QueryBuilders\OrderBuilder;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    public function newEloquentBuilder($query)
    {
        return new OrderBuilder($query);
    }
}
Enter fullscreen mode Exit fullscreen mode

步骤三:在控制器中使用

$orders = Order::query()
    ->paid()
    ->status('shipped')
    ->dateBetween('2025-09-01', '2025-09-10')
    ->get();
Enter fullscreen mode Exit fullscreen mode

清洁、可串联、高度可重复使用。

4. 合并范围和构建器

您可以混合使用本地作用域和自定义构建器,以获得最大性能。
例如:

$recentPaidOrders = Order::paid()
    ->status('delivered')
    ->dateBetween(now()->subDays(7), now())->get();

Enter fullscreen mode Exit fullscreen mode

这里,paid() 和 status() 来自自定义构建器。

5. 何时使用什么?

  • 局部作用域 ->用于小型、可重用的条件(active()、published())。
  • G*全局作用域 -> * 用于租户筛选、软删除、始终开启条件。
  • 自定义查询构建器 ->用于跨模型的复杂、可链式业务查询。

“如果你需要花费精力去查看一段代码并弄清楚它在做什么,那么你应该把它提取到一个函数中,并用它所做的事情来命名这个函数。”——马丁·福勒

6. 利用示波器进行性能优化

作用域还可以通过避免 N+1 查询来帮助优化查询。
例如:统计关系数,
而不是:

$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count(); // N+1 problem
}
Enter fullscreen mode Exit fullscreen mode

在 User 模型中定义作用域:

class User extends Model
{
    public function scopeWithPostsCount($query)
    {
        return $query->withCount('posts');
    }
}

Enter fullscreen mode Exit fullscreen mode

现在:

$users = User::withPostsCount()->get();
foreach ($users as $user) {
    echo $user->posts_count;//Single optimized query
}
Enter fullscreen mode Exit fullscreen mode

7. 主要收获

  • 本地作用域 >最适合小型、可重用的查询过滤器(例如,已发布用户、活跃用户)
  • 全局范围 >自动应用系统范围规则(例如,多租户应用程序中的 tenant_id)。
  • 自定义查询构建器 >非常适合复杂的、可链式的业务查询(例如,按日期、状态、付款方式筛选订单)。
  • 动态范围 >接受参数以进行灵活筛选(例如,category($id))。
  • 性能作用域 >在作用域内使用 withCount() 和预加载来防止 N+1 查询。
  • 可读性和可维护性 >控制器和服务保持简洁,查询变得一目了然。

当你的应用不断发展壮大时,使用作用域和构建器来组织查询逻辑就不是可选项了——它对于可扩展性和可维护性至关重要。

“我一直希望我的电脑能像我的电话一样好用;我的愿望实现了,因为我再也搞不懂怎么用我的电话了。”——比雅内·斯特劳斯特鲁普

8. 真实案例研究

在我们其中一个项目(一个多租户SaaS CRM)中,我们:

  • 使用全局作用域自动按 tenant_id 限制所有查询。
  • 定义了局部作用域,例如 scopeActiveLeads() 和 scopeConvertedLeads()。
  • 我构建了一个自定义查询生成器,用于按行业、线索评分和来源筛选线索。这使控制器复杂度降低了 70%,并使查询对新开发人员来说一目了然。

结果:

  • 更清洁的控制器
  • 重复查询条件导致的错误更少
  • 更快的 API 响应

9. 常见问题解答

1. Laravel 中的模型作用域是什么?
模型中可重用的查询过滤器,用于保持查询的简洁性和 DRY 原则。

2. 何时应该使用自定义查询构建器?
用于超出简单筛选条件的复杂、可链式查询。

3. 作用域和构建器可以组合使用吗?
可以,组合使用可以实现简单和高级的查询逻辑。

4. 它们如何提高性能?
它们通过优化加载来减少重复查询并防止 N+1 问题。

10. 结论

Laravel 的 Eloquent ORM 不仅仅只有 where 和 get 这两个属性。借助局部作用域、全局作用域和自定义查询构建器,您可以构建简洁、DRY(Don't Repeat Yourself,不要重复自己)且可扩展的查询逻辑,而且代码读起来就像纯英文一样。
使用局部作用域可以创建小型可重用过滤器。

  • 使用全局作用域实现系统范围内的自动条件设置。
  • 使用自定义构建器处理复杂、可链式调用的业务查询。
  • 使用 withCount()、预加载和基于作用域的预取来优化性能。

作者简介: Manoj 是AddWeb Solution的高级 PHP Laravel 开发人员,致力于构建安全、可扩展的 Web 应用程序和 REST API,同时分享有关简洁、可重用的后端代码的见解。

文章来源:https://dev.to/addwebsolutionpvtltd/advanced-laravel-eloquent-custom-query-builders-and-model-scopes-3j9e