thinkphp 模型组合查询方法解析分析及一处设计错误的修正

1,182 阅读2分钟

最近在使用thinkphp 3.2.3版本开发时遇到条件太多thinkphp有些不好处理。查询手册发现thinkphp在不是用query()方法时 可以用组合查询

组合查询的主体还是采用数组方式查询,只是加入了一些特殊的查询支持,包括字符串模式查询(_string)、复合查询(_complex)、请求字符串查询(_query),混合查询中的特殊查询每次查询只能定义一个,由于采用数组的索引方式,索引相同的特殊查询会被覆盖。

thinkphp对where组合查询 解析分析

在thinkphp源码中可以看到两个解析方法 (对应文件3.2.2是Db.class.php3.2.3 是Db/Driver.class.php )parseWhereItem,parseThinkWhere 第一个是普通方式解析就是字段=>条件 ,当字段名是_complex,_string,_query时,使用第二个进行解析。

通过源码分析得到的组合查询方式的缺点

经过查看这段代码,我们发现 当组合方法使用 ‘or’时, 我们仅能够添加三个 。如果再想添加就只能将它想法设法写入到_string中,需要特别说明,如果你的查询条件特别多,而且会遇到三个以上的or查询条件,那么请直接拼接sql,并使用model的query方式直接进行sql查询。

实际使用过程中发现组合查询的一处bug

  1. bug的表现为parsestr 会对.进行处理将之替换为``;
  2. 产生的位置是_query方式
  3. 产生必备条件使用了alias()方式并且使用left join类似方法,进行组合查询

案例

protected function getGoodsByTag($goodsTag, $p, $perPage, $brandId, $goodsCatId, $areaId)
        {
            $where = [
                'g.goodsStatus' => 1,
            ];
            if (!empty($goodsTag))
                $where['g.goodsTag'] = ['in', $goodsTag];
            if (!empty($brandId))
                $where['g.brandId'] = $brandId;
            if (!empty($goodsCatId))
                $where['_query'] = 'g.goodsCatId1=' . $goodsCatId . '&g.goodsCatId2=' . $goodsCatId . '&g.goodsCatId3=' . $goodsCatId . '&_logic=or';
            if (!empty($areaId))
                $where['_complex'] = [
                    'g.areaId1' => $areaId,
                    'g.areaId2' => $areaId,
                    'g.areaId3' => $areaId,
                    '_logic'  => 'or',
                ];
            $count = M('Goods')->alias('g')->where($where)->count();
            Log::write(M('Goods')->getLastSql(), 'DEBUG', 'file', C('LOG_PATH') . 'getGoods.log');
            $totalPage = ceil($count / $perPage);
            $data      = M('Goods')->alias('g')
                ->where($where)
                ->order('g.isAdminRecom DESC,g.createTime DESC')
                ->field('g.goodsId,g.goodsName,g.shopPrice,g.goodsThums,g.goodsUnit')
                ->page($p, $perPage)
                ->select();
            Log::write(M('Goods')->getLastSql(), 'DEBUG', 'file', C('LOG_PATH') . 'getGoods.log');
            return ['totalPage' => $totalPage, 'data' => $data];
        }

例子中如果同时传递了goodsCatId那么就会出现bug,通过指定的Log文件我们发现sql出错了

SELECT g.goodsId,g.goodsName,g.shopPrice,g.goodsThums,g.goodsUnit FROM tc_goods g WHERE g.goodsStatus = 1 AND g.goodsTag IN (0,1,2,3) AND g.brandId = 65 AND ( g_goodsCatId1 = '442' OR g_goodsCatId2 = '442' OR g_goodsCatId3 = '442' ) AND (  g.areaId1 = 370000 OR g.areaId2 = 370000 OR g.areaId3 = 370000 ) ORDER BY g.isAdminRecom DESC,g.createTime DESC LIMIT 0,1

由于暂时没有找到如何让parse_str不将.处理成_所以我在修复的pr中这样解决

case '_query':
                    $where = [];
                    // 字符串模式查询条件
                    if (strpos($val, '.') === false)
                        parse_str($val, $where);
                    else {
                        $tmpWhere = explode('&', $val);
                        foreach ($tmpWhere as $value) {
                            $tmpValue            = explode('=', $value);
                            $where[$tmpValue[0]] = $tmpValue[1];
                        }
                    }
                    if (isset($where['_logic'])) {
                        $op = ' ' . strtoupper($where['_logic']) . ' ';
                        unset($where['_logic']);
                    } else {
                        $op = ' AND ';
                    }
                    $array = [];
                    foreach ($where as $field => $data)
                        $array[] = $this->parseKey($field) . ' = ' . $this->parseValue($data);
                    $whereStr = implode($op, $array);
                    break;

咨询过几位大神说是php.ini中有这样的设置,我现在还没有仔细去看,等细看后会回来补充。