Laravel中模型与自定义过滤器类的优化连接方案咨询
我完全理解你的痛点——每次在控制器里手动注入过滤器确实有点繁琐,尤其是当多个模型都需要过滤功能时。我们可以通过约定大于配置的思路,让模型自动关联对应的过滤器类,彻底摆脱手动注入的麻烦。下面给你两种优雅的实现方案:
方案一:基于命名约定自动匹配过滤器
这个方案的核心是遵循「模型名+Filter」的命名规则(比如GasStation对应GasStationFilter),让模型自动找到并实例化对应的过滤器。
步骤1:修改模型的scopeFilter方法
更新GasStation模型的scopeFilter,去掉$filters参数,改为自动解析过滤器类:
namespace App\Models; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; class GasStation extends ListModel { public function region(): BelongsTo { return $this->belongsTo(Region::class); } public function scopeFilter($query, $params) : Builder { // 根据模型类名生成对应的过滤器类名 $filterClass = 'App\\Classes\\Filter\\' . class_basename($this) . 'Filter'; // 检查过滤器类是否存在,不存在则返回原查询(或抛出异常) if (!class_exists($filterClass)) { return $query; } // 实例化过滤器并应用 $filters = App::make($filterClass); return $filters->apply($query, $params); } }
步骤2:简化控制器的调用
现在你不需要再在控制器方法里注入过滤器了,直接传递验证后的参数即可:
public function index(GasStationIndexRequest $request) { $gasStations = GasStation::with('region') ->filter($request->validated()) ->take(10) ->get(); return GasStationSelect2Resource::collection($gasStations); }
方案二:自定义过滤器类属性(更灵活)
如果你的过滤器命名不遵循约定,或者需要给某个模型指定特殊的过滤器,可以在模型里添加一个$filterClass属性来明确指定:
步骤1:模型中添加过滤器属性
namespace App\Models; use App\Classes\Filter\GasStationFilter; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; class GasStation extends ListModel { // 明确指定对应的过滤器类 protected $filterClass = GasStationFilter::class; public function region(): BelongsTo { return $this->belongsTo(Region::class); } public function scopeFilter($query, $params) : Builder { if (!isset($this->filterClass) || !class_exists($this->filterClass)) { return $query; } $filters = App::make($this->filterClass); return $filters->apply($query, $params); } }
步骤2:控制器调用和方案一一致
同样不需要注入过滤器,直接传参数即可。
进阶:用Trait复用过滤逻辑
如果多个模型都需要这个自动关联过滤器的功能,我们可以把scopeFilter的逻辑抽成一个Trait,避免重复代码:
步骤1:创建Filterable Trait
namespace App\Models\Traits; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\App; trait Filterable { public function scopeFilter($query, $params) : Builder { // 优先使用模型自定义的$filterClass,否则用命名约定 $filterClass = $this->filterClass ?? 'App\\Classes\\Filter\\' . class_basename($this) . 'Filter'; if (!class_exists($filterClass)) { return $query; } $filters = App::make($filterClass); return $filters->apply($query, $params); } }
步骤2:在模型中使用Trait
namespace App\Models; use App\Models\Traits\Filterable; use App\Classes\Filter\GasStationFilter; class GasStation extends ListModel { use Filterable; // 可选:如果不遵循命名约定,就指定这个属性 // protected $filterClass = GasStationFilter::class; public function region(): BelongsTo { return $this->belongsTo(Region::class); } }
这样所有需要过滤功能的模型,只需要引入这个Trait即可,不用重复写scopeFilter方法,非常高效。
额外优化:给QueryFilter添加构造函数注入
我们还可以优化QueryFilter的构造,利用Laravel的依赖注入能力,比如如果过滤器需要用到其他服务,可以直接在构造函数里注入:
namespace App\Classes\Filter; use Illuminate\Database\Eloquent\Builder; class QueryFilter { protected $query; protected $params; // 可以在这里注入其他依赖,比如日志、缓存等 public function __construct() { // 示例:public function __construct(\Illuminate\Log\LoggerInterface $log) } public function apply($query, $params) { $this->query = $query; $this->params = $params; foreach ($this->filters() as $filter => $value){ if(method_exists($this, $filter)){ $this->$filter($value); } } return $this->query; } public function filters() { return $this->params; } }
这样你的过滤器类就可以更灵活地使用Laravel的服务容器了。
内容的提问来源于stack exchange,提问作者Александр Лихих
相关产品推荐
相关产品推荐

