Stay hungry, Stay foolish

0%

ThinkPHP SQL预处理

SQL预处理已经不是什么新鲜的东西了,它可以很好的防sql注入。而ThinkPHP直到3.1中才在其orm里增加对其的支持。相关介绍在手册里的《14.3 防止SQL注入》这一节。

使用环境

where(), query(), execute()

使用方式

  • 变长参数
1
"id=%d and username='%s' and xx='%f'",$id,$username,$xx
  • 数组参数
1
"id=%d and username='%s' and xx='%f'",array($id,$username,$xx)

实现

接下来以query()在3.0及3.1的实现来看一下:

ThinkPHP 3.0(Lib\Core\Model.class.php):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function query($sql,$parse=false) {
$sql = $this->parseSql($sql,$parse);
return $this->db->query($sql);
}
protected function parseSql($sql,$parse) {
// 分析表达式
if($parse) {
$options = $this->_parseOptions();
$sql = $this->db->parseSql($sql,$options);
}else{
if(strpos($sql,'__TABLE__'))
$sql = str_replace('__TABLE__',$this->getTableName(),$sql);
}
$this->db->setModel($this->name);
return $sql;
}

ThinkPHP 3.1(Lib\Core\Model.class.php):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 public function query($sql,$parse=false) {
if(!is_bool($parse) && !is_array($parse)) {
$parse = func_get_args();
array_shift($parse);
}
$sql = $this->parseSql($sql,$parse);
return $this->db->query($sql);
}
protected function parseSql($sql,$parse) {
// 分析表达式
if(true === $parse) {
$options = $this->_parseOptions();
$sql = $this->db->parseSql($sql,$options);
}elseif(is_array($parse)){ // SQL预处理
$sql = vsprintf($sql,$parse);
}else{
if(strpos($sql,'__TABLE__'))
$sql = str_replace('__TABLE__',$this->getTableName(),$sql);
}
$this->db->setModel($this->name);
return $sql;
}

通过对比可以发现有两处变化:

  • query()

添加了对$parse参数的判断,如果$parse不是布尔型且不是数据,那么肯定就是实现中的第一种方式(变长参数),这时用func_get_args接收参数并用array_shift把这些参数拼成一个数组。

由此可见变长参数的内部实现也是转换成数组方式,所以建议使用数组参数方式(少了两次函数的开销)

  • parseSql()
1
if(is_array($parse)) $sql  = vsprintf($sql,$parse);

如果$parse是数组,用vsprintf()函数来完成$sql的预处理。

vsprintf()是什么呢?

它在手册里的定义是”把格式化字符串写入变量中”,即本例中的%d,%s和%f替换成$id,$username和$xx,这些以%开头转换字符如下:

%% - 返回百分比符号
%b - 二进制数
%c - 依照 ASCII 值的字符
%d - 带符号十进制数
%e - 可续计数法(比如 1.5e+3)
%u - 无符号十进制数
%f - 浮点数(local settings aware)
%F - 浮点数(not local settings aware)
%o - 八进制数
%s - 字符串
%x - 十六进制数(小写字母)
%X - 十六进制数(大写字母)
据说打赏我的人,代码没有BUG