Laravel Eloquent 进阶:自定义查询构建器和模型作用域
在使用 Laravel 时,Eloquent ORM 是最强大的工具之一。虽然使用 where()、orderBy() 和关系的基本查询可以满足 80% 的实际需求,但高级应用程序通常需要可重用、简洁且可扩展的查询逻辑。
这时,自定义查询构建器和模型作用域就派上了用场。它们允许你从控制器和服务中提取业务逻辑,保持查询的表达力,并提高可维护性。
指数
- 介绍
- 模型范围
- 自定义查询构建器
- 结合范围和构建器
- 何时使用什么
- 利用示波器进行性能优化
- 真实案例研究
- 要点总结
- 常见问题解答
- 结论
1. 模型范围
作用域是预定义的查询过滤器,可以在项目中重复使用。
示例:活跃用户范围
假设您有一个用户模型,其中用户可以是活跃用户或非活跃用户。
与其反复书写:
$activeUsers = User::where('status', 'active')->get();
您可以在 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);
}
}
现在你可以拨打:
$activeUsers = User::active()->get();
$inactiveUsers = User::status('inactive')->get();
更清洁、可重复使用、更具表现力。
“由于我在实际应用中频繁使用它,所以我往往也能很快发现框架中的不足之处。社区在很多方面推动着框架的发展,我也会不时地提出自己的见解。”——泰勒·奥特韦尔
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);
}
}
将其连接到您的模型上:
class Project extends Model
{
protected static function booted()
{
static::addGlobalScope(new TenantScope);
}
}
现在:
Project::all(); // always filtered by tenant_id automatically
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]);
}
}
步骤 2:连接到模型
use App\QueryBuilders\OrderBuilder;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
public function newEloquentBuilder($query)
{
return new OrderBuilder($query);
}
}
步骤三:在控制器中使用
$orders = Order::query()
->paid()
->status('shipped')
->dateBetween('2025-09-01', '2025-09-10')
->get();
清洁、可串联、高度可重复使用。
4. 合并范围和构建器
您可以混合使用本地作用域和自定义构建器,以获得最大性能。
例如:
$recentPaidOrders = Order::paid()
->status('delivered')
->dateBetween(now()->subDays(7), now())->get();
这里,paid() 和 status() 来自自定义构建器。
5. 何时使用什么?
- 局部作用域 ->用于小型、可重用的条件(active()、published())。
- G*全局作用域 -> * 用于租户筛选、软删除、始终开启条件。
- 自定义查询构建器 ->用于跨模型的复杂、可链式业务查询。
“如果你需要花费精力去查看一段代码并弄清楚它在做什么,那么你应该把它提取到一个函数中,并用它所做的事情来命名这个函数。”——马丁·福勒
6. 利用示波器进行性能优化
作用域还可以通过避免 N+1 查询来帮助优化查询。
例如:统计关系数,
而不是:
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count(); // N+1 problem
}
在 User 模型中定义作用域:
class User extends Model
{
public function scopeWithPostsCount($query)
{
return $query->withCount('posts');
}
}
现在:
$users = User::withPostsCount()->get();
foreach ($users as $user) {
echo $user->posts_count;//Single optimized query
}
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