本文目录导读:

- 方案一:简单封装(适合小型项目)
- 方案二:专用 Filter 类(推荐中大型项目)
- 方案三:使用 Laravel 管道(Pipeline)模式(高级/可扩展)
- 方案四:使用 Laravel 官方推荐的
Query Builder模式(Laravel 10+) - 企业级最佳实践总结
- 额外建议:过滤参数安全
设计 Laravel 的过滤服务(通常称为 Filter Service 或 Query Filter),核心目标是将复杂的查询条件(如 ?status=active&sort=created_at&category=tech)从控制器中解耦,使代码更干净、可测试且易于扩展。
下面提供一套从简单到企业级的渐进式设计方案,包含实战代码和最佳实践。
简单封装(适合小型项目)
如果条件较少,可以直接在模型上封装一个本地作用域。
定义 Trait
// app/Traits/Filterable.php
namespace App\Traits;
trait Filterable
{
public function scopeFilter($query, array $filters)
{
return $query
->when($filters['status'] ?? false, fn($q, $value) => $q->where('status', $value))
->when($filters['search'] ?? false, fn($q, $value) => $q->where('title', 'like', "%$value%"))
->when($filters['category'] ?? false, fn($q, $value) => $q->whereHas('category', fn($q) => $q->where('slug', $value)));
}
}
在模型中使用
class Post extends Model
{
use Filterable;
}
控制器调用
public function index(Request $request)
{
return Post::filter($request->only(['status', 'search', 'category']))
->latest()
->paginate();
}
缺点:条件多了以后 Trait 会膨胀,无法复用。
专用 Filter 类(推荐中大型项目)
将每个模型的过滤逻辑抽离为独立的类,支持依赖注入和单元测试。
创建抽象基类
// app/Services/Filter/QueryFilter.php
namespace App\Services\Filter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
abstract class QueryFilter
{
protected Builder $builder;
protected Request $request;
protected array $filters = [];
public function __construct(Request $request)
{
$this->request = $request;
}
public function apply(Builder $builder): Builder
{
$this->builder = $builder;
foreach ($this->getFilters() as $name => $value) {
if (method_exists($this, $name)) {
$this->$name($value);
}
}
return $this->builder;
}
protected function getFilters(): array
{
// 只返回在 $filters 白名单中的参数
return array_filter($this->request->only($this->filters));
}
}
创建具体的 Filter 类
// app/Services/Filter/PostFilter.php
namespace App\Services\Filter;
class PostFilter extends QueryFilter
{
protected array $filters = ['status', 'search', 'category', 'date_from', 'date_to'];
public function status($value)
{
$this->builder->where('status', $value);
}
public function search($value)
{
$this->builder->where(function ($q) use ($value) {
$q->where('title', 'like', "%$value%")
->orWhere('content', 'like', "%$value%");
});
}
public function category($slug)
{
$this->builder->whereHas('category', fn($q) => $q->where('slug', $slug));
}
public function dateFrom($date)
{
$this->builder->whereDate('created_at', '>=', $date);
}
public function dateTo($date)
{
$this->builder->whereDate('created_at', '<=', $date);
}
// 可以添加排序逻辑
public function sort($field)
{
$direction = $this->request->get('direction', 'asc');
$this->builder->orderBy($field, $direction);
}
}
在模型中使用
class Post extends Model
{
public function scopeFilter($query, PostFilter $filter)
{
return $filter->apply($query);
}
}
控制器中注入
use App\Services\Filter\PostFilter;
class PostController extends Controller
{
public function index(PostFilter $filter)
{
return Post::filter($filter)->latest()->paginate();
}
}
使用 Laravel 管道(Pipeline)模式(高级/可扩展)
适用于需要多个中间步骤处理查询的场景(如权限过滤、数据脱敏、缓存的查询)。
创建 Pipeline 类
// app/Services/Filter/PipelineFilter.php
namespace App\Services\Filter;
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
class PipelineFilter
{
protected Request $request;
protected array $pipelines = [];
protected array $filters = [];
public function __construct(Request $request)
{
$this->request = $request;
}
public function addFilter($filter): self
{
$this->filters[] = $filter;
return $this;
}
public function addPipeline(callable $pipeline): self
{
$this->pipelines[] = $pipeline;
return $this;
}
public function apply(Builder $builder): Builder
{
// 先应用所有过滤器
foreach ($this->filters as $filter) {
$builder = (new $filter($this->request))->apply($builder);
}
// 再运行管道
foreach ($this->pipelines as $pipeline) {
$builder = $pipeline($builder);
}
return $builder;
}
}
创建具体的管道步骤
// app/Services/Filter/Pipelines/SortPipe.php
namespace App\Services\Filter\Pipelines;
use Closure;
use Illuminate\Database\Eloquent\Builder;
class SortPipe
{
public function handle(Builder $builder, Closure $next, $order = 'created_at')
{
$sortedBuilder = $builder->orderBy($order, 'desc');
return $next($sortedBuilder);
}
}
在控制器中使用
use App\Services\Filter\PipelineFilter;
use App\Services\Filter\PostFilter;
use App\Services\Filter\Pipelines\SortPipe;
class PostController extends Controller
{
public function index(Request $request)
{
$pipeline = new PipelineFilter($request);
$pipeline->addFilter(PostFilter::class)
->addPipeline(fn($builder) => $builder->where('visible', true));
return Post::query()
->tap(fn($query) => $pipeline->apply($query))
->paginate();
}
}
使用 Laravel 官方推荐的 Query Builder 模式(Laravel 10+)
利用 Laravel 10 新增的 Builder 宏和 invokable 类。
创建 Invokable Filter 类
// app/QueryFilters/PostStatusFilter.php
namespace App\QueryFilters;
use Closure;
use Illuminate\Database\Eloquent\Builder;
class PostStatusFilter
{
public function __construct(protected string $status) {}
public function __invoke(Builder $query, Closure $next)
{
if ($this->status === 'active') {
$query->where('status', 'published');
}
return $next($query);
}
}
在控制器中链式调用
use App\QueryFilters\PostStatusFilter;
use App\QueryFilters\SearchFilter;
public function index(Request $request)
{
return Post::query()
->tap(new PostStatusFilter($request->status))
->tap(new SearchFilter($request->search))
->paginate();
}
企业级最佳实践总结
| 特性 | 简单 Trait | 专用 Filter 类 | Pipeline | Invokable |
|---|---|---|---|---|
| 代码量 | 少 | 中 | 多 | 中 |
| 可测试性 | 差 | 好 | 极好 | 好 |
| 扩展性 | 低 | 高 | 极高 | 高 |
| 复用性 | 低 | 高 | 极高 | 高 |
| 适合项目 | 小型CRUD | 中大型项目 | 复杂业务系统 | 微服务/Laravel10+ |
额外建议:过滤参数安全
- 白名单验证:在
$filters属性中只声明允许的参数。 - 类型转换:
$this->builder->where('price', '>=', (float) $value)。 - SQL 注入防护:使用参数绑定,避免拼接
orderBy(request('sort'))→ 使用白名单映射。
protected array $sortable = ['created_at', 'title', 'price'];
public function sort($field)
{
if (!in_array($field, $this->sortable)) {
return; // 忽略非法排序
}
$this->builder->orderBy($field, $this->request->get('direction', 'asc'));
}
选择哪种方案取决于你的项目阶段:
- 快速原型 → 方案一
- 标准商业项目 → 方案二
- 需要高度可扩展(如 SaaS) → 方案三 / 方案四
如果还有特定场景(如多表关联过滤、嵌套条件、API 资源过滤)需要更具体的代码,可以告诉我,我可以帮你扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播