查询作用域(Query Scopes) 让查询更易读,但它使用的是魔术方法。
虽然查询作用域很实用,但自定义查询构造器提供了更多的灵活性和更好的开发体验。
使用自定义构建器可以让代码更容易维护,也更容易理解。
查询作用域
考虑以下代码:
<?php
use App\Models\User;
$users = User::query()
->where('votes', '>', 100)
->where('active', 1)
->orderBy('created_at')
->get();
当查询变得过于复杂或难以阅读时,可以使用查询作用域将它们抽象出来:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* @method static Builder popular()
* @method static Builder active()
*/
class User extends Model
{
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
现在可以这样使用它们:
$users = User::popular()
->active()
->orderBy('created_at')
->get();
自定义查询构造器
使用自定义查询构造器来实现相同的功能,但代码更加清晰:
- 创建一个自定义查询构造器类
App\Eloquent\Builders\UserQueryBuilder
<?php
namespace App\Eloquent\Builders;
use Illuminate\Database\Eloquent\Builder;
class UserQueryBuilder extends Builder
{
public function popular(): self
{
return $this->where('votes', '>', 100);
}
public function active(): self
{
return $this->where('active', 1);
}
}
- 在模型的
newEloquentBuilder
方法中使用它
<?php
namespace App\Models;
use App\Eloquent\Builders\UserQueryBuilder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function newEloquentBuilder($query): UserQueryBuilder
{
return new UserQueryBuilder($query);
}
}
- 现在可以像这样使用它
$users = User::query()
->popular()
->active()
->orderBy('created_at')
->get();
测试
使用 Pest 可以很容易的测试这些查询构造器。
<?php
use App\Models\User;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
uses(LazilyRefreshDatabase::class);
beforeEach(function () {
// 创建测试数据
User::factory()->count(3)->create(['votes' => 50, 'active' => 1]);
User::factory()->count(2)->create(['votes' => 150, 'active' => 1]);
User::factory()->create(['votes' => 150, 'active' => 0]);
});
it('can query popular users', function () {
$users = User::query()->popular()->get();
expect($users)->toHaveCount(3)
->each(fn ($user) => $user->votes->toBeGreaterThan(100));
});
it('can query active users', function () {
$users = User::query()->active()->get();
expect($users)->toHaveCount(5)
->each(fn ($user) => $user->active->toBeTrue());
});
it('can combine multiple query conditions', function () {
$users = User::query()
->popular()
->active()
->get();
expect($users)->toHaveCount(2)
->each(fn ($user) => $user->votes->toBeGreaterThan(100)
->active->toBeTrue()
);
});
这些测试确保了:
-
popular()
方法正确筛选出投票数超过 100 的用户 -
active()
方法正确筛选出活跃用户 - 可以正确组合多个查询条件
通过这些测试可以确保查询构造器的行为符合预期,并且在代码修改时能够及时发现问题。
优势
- 更好的 IDE 支持
- 更清晰的代码组织
- 没有魔术方法
- 更容易测试
- 更好的类型提示