Laravel中模型静态搜索方法是否符合设计模式与SOLID原则?
从设计模式与SOLID原则看Laravel模型静态search方法的设计
嘿,你的静态search方法能正常运行确实很棒,但从SOLID原则和常见的设计模式角度来看,这并不是最优的实现方式,咱们来拆解下问题,再聊聊更好的方案:
为什么静态方法不是最优选择?
1. 违反单一职责原则(SRP)
模型的核心职责应该是封装与数据库表对应的业务实体属性和行为,而搜索过滤这种查询逻辑属于数据检索的范畴,把它塞到模型里会让模型承担过多职责。后续如果要加更多过滤条件(比如按球队、比赛状态),模型会变得越来越臃肿,维护成本飙升。
2. 违反依赖倒置原则(DIP)
静态方法直接依赖了request()全局助手,这让你的查询逻辑和HTTP请求上下文强耦合了——如果后续想在CLI命令、队列任务或者其他非HTTP场景下调用这个搜索功能,就会因为没有请求上下文而报错,灵活性大打折扣。
3. 可测试性差
静态方法很难被mock或者替换,在写单元测试的时候,你没法轻松地模拟这个search方法的返回结果,只能依赖真实的数据库查询,测试速度慢而且不够灵活。
4. 扩展性不足
如果后续需要添加不同的搜索策略(比如模糊搜索、高级筛选组合),静态方法的结构很难支持这种扩展,只能不断往里面加when条件,最终变成一团难以维护的“面条代码”。
更优的实现方案:使用查询对象模式(Query Object Pattern)
咱们可以把搜索查询逻辑抽离到专门的查询对象类中,这样既符合SOLID原则,又能提升代码的可维护性和扩展性。
步骤1:创建查询对象类
在app/Queries目录下创建GameSearchQuery.php(如果没有Queries目录就新建一个):
namespace App\Queries; use App\Models\Game; use Illuminate\Database\Eloquent\Builder; class GameSearchQuery { protected $seasonId; protected $weekId; public function __construct($seasonId = null, $weekId = null) { $this->seasonId = $seasonId; $this->weekId = $weekId; } public function handle() { return Game::with('season', 'week', 'homeTeam', 'awayTeam') ->when($this->seasonId, function (Builder $q) { return $q->where('season_id', $this->seasonId); }) ->when($this->weekId, function (Builder $q) { return $q->where('week_id', $this->weekId); }) ->paginate(15); } }
步骤2:在控制器中调用查询对象
use App\Queries\GameSearchQuery; // ... $games = (new GameSearchQuery(request('season'), request('week')))->handle();
这种方案的优势:
- 符合单一职责原则:模型专注于实体本身,查询逻辑由专门的查询对象负责,各司其职。
- 解耦依赖:不再直接依赖
request(),查询对象接收的是明确的参数,在任何场景下都能调用。 - 可测试性强:测试时可以轻松创建查询对象的实例,传入不同的参数验证结果,甚至可以mock底层的Eloquent查询。
- 扩展性好:后续要加新的过滤条件,只需要给查询对象添加新的属性和对应的
when条件;如果需要不同的搜索策略,直接新建一个查询对象类即可。
进阶优化:使用服务容器(可选)
如果想进一步提升灵活性,可以利用Laravel的服务容器来注入查询对象,或者把handle方法改成返回Builder实例,这样还能在控制器里继续链式调用其他查询条件:
// 修改查询对象的handle方法 public function handle(): Builder { return Game::with('season', 'week', 'homeTeam', 'awayTeam') ->when($this->seasonId, function (Builder $q) { return $q->where('season_id', $this->seasonId); }) ->when($this->weekId, function (Builder $q) { return $q->where('week_id', $this->weekId); }); } // 控制器中调用 $games = (new GameSearchQuery(request('season'), request('week'))) ->handle() ->where('status', 'finished') // 额外添加过滤条件 ->paginate(15);
内容的提问来源于stack exchange,提问作者Tartar
相关产品推荐
相关产品推荐

