Laravel 框架中 一个搜索框搜多个字段解决方案优化(whereRaw like 参数绑定)

Laravel 框架中 一个搜索框搜多个字段解决方案优化(whereRaw like 参数绑定)

1. 缘由#

业务需要一个输入框框搜多个字段引起

  • name,phone,status 为示例字段

一般情况下,下方写法可满足要求(谢谢下方评论提示,一开始我也是这样写的)
demo1:

$list = $list->where(function($query)use($keyword){
       $query->where('name', 'like', '%'.$keyword.'%');
       $query->orWhere('phone', 'like', '%'.$keyword.'%');
});

但我们的需求中,存储为 int 数值的字段,在模糊查询时,也要查出来

  • 例:状态 status 字段,数据库中存储为 tinyint 类型,值为 1/2 等,表示为‘开始 / 关闭’,在输入‘关闭’后,也可以查出来

故调整为下方写法
demo2:

$list->whereRaw("concat(name,phone, (CASE
            WHEN status = ".Model::is_open_no." THEN
            '关闭'
            WHEN status = ".Model::is_open_yes." THEN
            '开启'
            END),) like '%".$keywords."%'");
  • 为提高文章的可读性,CASE 部分代码在下方 demo 中不再出现

2. 问题#

使用 demo2 实现,没有对 sql 注入做任何处理,仅输入 like ‘ 或 ‘ 就会报错
,要使用类似于 PDO 参数绑定进行传参,以避免 SQL 注入的风险

3. 解决#

Laravel 中 whereRaw 方法默认传参为 1 个参数,查看底层实现如下

    public function whereRaw($sql, $bindings = [], $boolean = 'and')
    {
        $this->wheres[] = ['type' => 'raw', 'sql' => $sql, 'boolean' => $boolean];

        $this->addBinding((array) $bindings, 'where');

        return $this;
    }

第二参数为要绑定的参数。同时在社区 wiki 中,summer 大佬指出要避免 sql 注入和第二参数的使用方式

3.1 遇到的问题一:参数绑定不生效#

对于 like 模糊查询,按社区 wiki 中写的如下代码,不生效(打印查看 sql 应该是没解析成功):
失败demo1:

$list = $list->whereRaw("concat(name,phone) like '%?%'",[$keywords]);

在查看 PHP PDO 之 MySQL 参数绑定后,实现如下:
成功demo1:

$list = $list->whereRaw("concat(name,phone) like ?",["%$keywords%"]);

3.2 遇到的问题二:数据中的某个字段为 null 时搜索不生效#

按 demo1 实现,在测试过程中发现:如果遇到一条数据某个字段为 null 的情况下,搜该条数据其他字段,会出现搜不到的情况
示例:[name=’王三’,phone=’’], 输入‘王三’时搜不到该条数据
处理:判断字段是否为 null,实现如下:
最终demo2:

$list = $list->whereRaw("concat(IFNULL(name,''),IFNULL(phone,'')) like ?",["%$keywords%"]);

4. 总结#

还是自己对基础掌握不牢,虽然之前都学过,但慢慢的不用也就忘了。
从使用框架的东西后,一些框架封装好的东西直接上手用,但底层的实现都没注意过

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 12

盲猜字符的符号 laravel 自己给你添加了,所以你手动写‘%?%’就等于是‘%'xxxxxx'%’

4年前 评论

假如某条记录姓名为 null,搜索手机号会找不到这条记录吧。

4年前 评论
请多多指教 (楼主) 4年前

为什么不这样呢

$list = $list->where(function($query)use($keyword){
       $query->where('name', 'like', '%'.$keyword.'%');
       $query->orWhere('phone', 'like', '%'.$keyword.'%');
});
4年前 评论
zpers 4年前
请多多指教 (楼主) 4年前
请多多指教 (楼主) 4年前

我觉得做的复杂了。

Model::where(function($query){
      $value = trim(request('search_name',''));
      $query->where('name','like',"%$value%")
                 ->orWhere('phone','like',"%$value%")
                 ->orWhere(function($query)use($value){
                      $map = collect([
                          '关闭' => 1,
                          '开启' => 2,
                      ]);
                      if(is_int((real)$value)){
                          $query->where('status',$value);
                      }elseif($map->has($value)){
                          $query->where('status',$map->get($value);
                      }else{
                          $query->where('status','like',"$value")
                      }                     
        })
    })->latest()->paginate(request('size',10));

这里仅供参考毕竟伪代码,直接使用 mysql 函数感觉太复杂了。name,phone 应该是 string 类型的列所以应该不能为 null, 默认值设置为 empty string,就不会出现 null 使用 like 查不出来。另外,你那个 sql 的弊端是,如果用户输入 1,2 你原生的 status 字段存在的值你反而查不出来。

4年前 评论
zpers 4年前

这业务看起来八成是后台管理系统的搜索业务,多字段查询一个值我觉得非常不好,意义不大,有时候我只想查 139 开头的手机号,你把用户名称包含 139 的都搜索出来了,数据量大的时候翻几页你都找不到 139 开头的手机号。毕竟没有权重的概念,第二就是查询效率问题。

4年前 评论
请多多指教 (楼主) 4年前

不同字段咋用呢?

2年前 评论