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

2017-01-01 21:44:00
hainuo
原创 1592
摘要:简单分析了thinkphp3.2版本对于组合查询方法,并修正了一处因为parse_str()解包字符串引起的bug。
最近在使用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的表现为parse_str 会对`.`进行处理将之替换为`_`; 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中有这样的设置,我现在还没有仔细去看,等细看后会回来补充。