最近我修复了一个bug,这个bug是用户能看到所有用户的数据,经过排查发现是where条件丢失,导致查询语句直接查了所有数据。
但是代码并没有问题,然后查到了 ThinkPHP/Library/Think/Model.class.php 的 _parseOptions 方法:
/** * 分析表达式 * @access protected * @param array $options 表达式参数 * @return array */ protected function _parseOptions($options=array()) { if(is_array($options)) $options = array_merge($this->options,$options); if(!isset($options['table'])){ // 自动获取表名 $options['table'] = $this->getTableName(); $fields = $this->fields; }else{ // 指定数据表 则重新获取字段列表 但不支持类型检测 $fields = $this->getDbFields(); } // 数据表别名 if(!empty($options['alias'])) { $options['table'] .= ' '.$options['alias']; } // 记录操作的模型名称 $options['model'] = $this->name; // 字段类型验证 if(isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { // 对数组查询条件进行字段类型检查 foreach ($options['where'] as $key=>$val){ $key = trim($key); if(in_array($key,$fields,true)){ if(is_scalar($val)) { $this->_parseType($options['where'],$key); } }elseif(!is_numeric($key) && '_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'(') && false === strpos($key,'|') && false === strpos($key,'&')){ if(!empty($this->options['strict'])){ E(L('_ERROR_QUERY_EXPRESS_').':['.$key.'=>'.$val.']'); } unset($options['where'][$key]); } } } // 查询过后清空sql表达式组装 避免影响下次查询 $this->options = array(); // 表达式过滤 $this->_options_filter($options); return $options; }
这是tp3封装的方法,在调用这个方法时,如果where条件中的字段,在表中不存在的话,就会删除这个条件,接着往下走,为什么明明表里有这个字段,却被判断为没有?
因为开启了字段缓存(不了解的可以去搜一下tp3字段缓存)。
正常来说开启字段缓存并不会出现这种问题,问题是出问题的这个model,和另一个接口模块的model重名了,开启字段缓存后,tp框架会根据model名将缓存文件存入 ApplicationRuntimeData 目录下,结果虽然模块不同,但还是造成了缓存文件冲突,当 A model 先缓存时,在 A model 缓存文件未过期之前,B model 检测字段时会从 A model 缓存文件取,当没有检测到 B model 用到的字段时,就把where条件给删了。
解决办法:
1.重名model重命名。
2.关闭字段缓存(一般不会为了这种问题就关掉字段缓存,基本不考虑)。
3.在model内定义好要用的字段,这样这个model就不会再执行字段缓存,也不会再从字段缓存文件取:
新发现,tp3中 M('A') 和 D('A') 也会共享缓存文件,因此请尽量注意model命名,最好用表全称。