2

我想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 部分只包含它需要的一半细节。

我应该如何进行?

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

它安全吗?

4

3 回答 3

3

关于问题的安全部分,带有占位符的准备好的语句与用值填充这些占位符所涉及的验证机制一样安全。对于 mysqli 准备好的语句,文档说:

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

但是,它们不允许用于标识符(例如表名或列名),在命名要由 SELECT 语句返回的列的选择列表中,或指定二元运算符(例如 = 等号)的两个操作数。后一个限制是必要的,因为不可能确定参数类型。不允许将标记与 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与其对应的占位符绑定。


附录:

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

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

对于第一点,您只需要确保它$bindArray使用整数索引,上面的代码就是这种情况(或者检查它call_user_func_array不会阻塞您提供的数组)。

对于第二点,如果您打算在调用$bindArray 之后bind_param(即通过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先前调用时与该语句绑定的数组条目上的引用。同样,以下应该有效,因为它不会更改之前设置的引用。

array_walk($bindArray, function($k,&$v){$v = some_new_value();});
于 2013-01-11T00:21:39.340 回答
1

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

你可以做的——如果你的代码在数据库连接存在期间被多次调用——是缓存那些准备好的语句,并根据你正在接受的参数数量对它们进行索引。这意味着您第二次使用三个参数调用该函数时,您已经完成了该语句。但是由于准备好的语句无论如何都不能在断开连接中幸存下来,因此只有在同一脚本运行中执行多个查询时,这才有意义。(我故意省略了持久连接,因为这会打开一个完全不同的蠕虫罐头。)

顺便说一句,我不是 MySQL 专家,但是没有 where 条件加入,而是写作不会有什么不同WHERE tags in (tag1, tag2, tag3, tag4)吗?

于 2013-01-10T22:37:30.543 回答
1

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

$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();
于 2013-01-11T20:09:14.220 回答