引言
在日常开发中,搜索功能是一个非常常见的需求。然而,很多开发者特别是初学者往往会采用一些不够优雅的实现方式。本文将带你一步步优化搜索功能的实现,从最基础的写法开始,逐步演进到一个优雅且易维护的解决方案。
常见的实现方式
作为初学者,你可能会写出这样的代码:
$conditions = [];
if (request('status')) {
$conditions[] = ['status', '=', request('status')];
}
if (request('email')) {
$conditions[] = ['email', '=', request('email')];
}
// 更多条件...
$users = User::where($conditions)->get();
这种实现方式存在以下问题:
- 代码冗长,充斥着大量的 IF 语句
- 可读性差,特别是在条件较多时
- 维护困难,每增加一个搜索条件都需要添加新的 IF 语句
- 代码结构混乱,不符合单一职责原则
优化思路一:使用查询构造器
Laravel 提供了丰富的查询构造器方法:
- where/orWhere
- whereIn/whereNotIn
- whereNull/whereNotNull
- whereDate/whereMonth/whereDay/whereYear 等等
你可能会这样改写:
User::where('status', request('status'))
->where('email', request('email'))
->get();
优化思路二:使用 scope 方法
进一步,你可能会想到在模型中定义 scope 方法:
User::search()->get();
优化思路三:使用 when 方法
使用 Laravel 的 when 方法可以让代码更加优雅:
User::query()
->when(request('status'), function($query, $status) {
return $query->where('status', $status);
})
->when(request('email'), function($query, $email) {
return $query->where('email', $email);
})
->get();
优化思路四:使用过滤器类
为了更好地组织代码,我们可以创建专门的过滤器类:
class UserFilters
{
protected $query;
public function __construct($query)
{
$this->query = $query;
}
public function apply($filters)
{
foreach ($filters as $filter => $value) {
if (method_exists($this, $filter)) {
$this->{$filter}($value);
}
}
return $this->query;
}
protected function status($value)
{
$this->query->where('status', $value);
}
protected function email($value)
{
$this->query->where('email', $value);
}
}
最终方案:使用 Pipeline 模式
通过使用 Laravel 的 Pipeline 特性,我们可以实现一个更加优雅的解决方案。
首先创建一个 Filterable trait:
<?php declare(strict_types=1);
namespace App\Traits;
use Illuminate\Pipeline\Pipeline;
trait Filterable
{
public function scopeFilter($query, array $through)
{
return app(Pipeline::class)
->send($query)
->through($through)
->thenReturn();
}
}
然后为每个搜索条件创建独立的过滤器类:
<?php declare(strict_types=1);
namespace App\QueryFilters\User;
use Illuminate\Database\Eloquent\Builder;
class Email
{
public function handle(Builder $query, $next)
{
$query->when(request('email'), function($query, $email) {
$query->where('email', $email);
});
return $next($query);
}
}
最后在控制器中优雅地使用:
$users = User::filter([
Email::class,
Status::class,
// 更多过滤器...
])->get();
总结
通过使用 Pipeline 模式,我们实现了以下目标:
- 移除了所有的 IF 语句
- 提高了代码的可读性和可维护性
- 实现了单一职责原则
- 使代码结构更加清晰
虽然这种实现方式会创建较多的文件,但每个类的职责都很明确,便于维护和扩展。对于文件数量的问题,我们可以通过编写脚本来自动生成过滤器类,从而提高开发效率。
最重要的是,这种实现方式让我们可以专注于业务逻辑,而不是纠结于代码结构。当你需要修改某个搜索条件的逻辑时,只需要找到对应的过滤器类进行修改即可,不会影响到其他代码。这就是一个优雅的解决方案应该具备的特质。
当然,目前这些方法是作者目前能想到的一些比较好的方案。之前看其它框架中封装有有类似的功能。但大多都需要遵循特定的规则,前端UI也是需要按特定的规则去实现。这在实际开发中如果遇到特殊的要求的话,前端跟后端改起来都很痛苦。
如果您有更好的解决方案,也欢迎与分联系分享。