PHP / MySQL:使用预处理语句在WHERE子句中使用数组元素

时间:2013-01-10 22:31:10

标签: php mysql prepared-statement

我想在查询中根据字符串数组创建一个“动态”WHERE子句。我想使用Mysqi的预处理语句运行创建的查询。

到目前为止我的代码,PHP:

$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
    $searchStr .= "OR tags.tag LIKE ? ";
}

我的查询:

SELECT tag FROM tags WHERE $searchStr;

更多PHP:

$stmt -> bind_param(str_repeat('s', count($searchArray)));

现在这显然给了我一个错误,因为bind_param部分只包含它需要的一半细节。

我该怎么办?

还有其他(更好)的方法吗?

安全吗?

3 个答案:

答案 0 :(得分:3)

关于问题的安全部分,使用占位符的预准备语句与填充这些占位符所涉及的验证机制一样安全。在mysqli准备语句的情况下,documentation说:

  

标记仅在SQL语句的某些位置是合法的。例如,允许它们在INSERT语句的VALUES()列表中(指定行的列值),或与WHERE子句中的列进行比较以指定比较值。

     

但是,不允许在SELECT列表中使用SELECT语句返回的列的标识符(例如表或列名),或者指定二元运算符的两个操作数,例如= equal标志。后一种限制是必要的,因为无法确定参数类型。不允许将标记与NULL进行比较?也是NULL。通常,参数仅在数据操作语言(DML)语句中是合法的,而在数据定义语言(DDL)语句中不合法。

这显然排除了修改查询的一般语义的任何可能性,这使得将其从原始意图转移出来要困难得多(但并非不可能)。

关于查询的动态部分,您可以在查询条件构建部分中使用str_repeat,而不是执行循环:

 $searchStr = 'WHERE tags.tag LIKE ?' . 
                        str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');

对于bind_param来电,您应该使用call_user_func_array,如下所示:

$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);

希望上面的代码段应该将$bindArray的每个值与查询中相应的占位符绑定。


Addenum:

但是,你应该警惕两件事:

  • call_user_func_array需要一个整数索引数组作为其第二个参数。我不确定它是如何用字典表现的。
  • mysqli_stmt_bind_param requires其参数将通过引用传递。

对于第一点,您只需要确保$bindArray使用整数索引,这是上面代码中的情况(或者检查call_user_func_array是否在数组上没有阻塞“提供它。”

对于第二点,如果您打算在调用$bindArray之后修改bind_param 中的数据,那将只是一个问题(即通过{{1}在执行查询之前 。 如果您希望这样做 - 例如通过在同一脚本中使用不同参数值多次运行相同的查询,那么您将必须使用相同的数组(call_user_func_array)来执行以下查询,并更新使用相同键的数组条目。复制另一个数组是行不通的,除非手工完成:

$bindArray

foreach($bindArray as $k => $v)
    $bindArray[$k] = some_new_value();

上面的方法是有效的,因为它不会破坏foreach($bindArray as &$v) $v = some_new_value(); 在之前调用时与语句绑定的数组条目的引用。同样,以下内容应该有效,因为它不会更改之前设置的引用。

bind_param

答案 1 :(得分:1)

准备好的声明需要有明确定义的参数;它不能具有任何动态功能元素。这意味着您必须生成所需的特定语句并准备好。

您可以做什么 - 如果您的代码在数据库连接存在期间实际被多次调用 - 请对这些预准备语句进行缓存,并根据您所参数的参数数量对其进行索引。这意味着第二次使用三个参数调用函数时,您已经完成了语句。但是,由于准备好的语句无论如何都无法在断开连接中存在,如果您在同一个脚本运行中执行多个查询,这实际上才有意义。 (我故意忽略持久连接,因为这会打开一种完全不同的蠕虫病毒。)

顺便说一句,我不是MySQL的专家,但是如果没有加入where条件,而是写WHERE tags in (tag1, tag2, tag3, tag4)会不会有所作为?

答案 2 :(得分:1)

here找到答案的帮助下解决了这个问题。

$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)

$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);

for ($i = 0; $i < count($searchArray); $i++){
   $bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
   $$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
   $bind_names[] = & $$bind_name; //put a link to this variable in array
}

call_user_func_array(array($stmt, 'bind_param'), &$bind_names);

$stmt -> execute();