PHP/MySQL:使用准备好的语句在WHERE子句中使用数组元素

PHP/MySQL:使用准备好的语句在WHERE子句中使用数组元素,php,mysql,prepared-statement,Php,Mysql,Prepared Statement,我想在基于字符串数组的查询中创建一个“dynamic”WHERE子句。我想使用Mysqi准备好的语句运行创建的查询 到目前为止,我的代码是PHP: $searchArray = explode(' ', $search); $searchNumber = count($searchArray); $searchStr = "tags.tag LIKE ? "; for($i=1; $i<=$searchNumber-1 ;$i++){ $searchStr .= "OR tags.

我想在基于字符串数组的查询中创建一个“dynamic”
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 ? ";
}
更多PHP:

$stmt -> bind_param(str_repeat('s', count($searchArray)));
这显然给了我一个错误,因为bind_param部分只包含它所需的一半细节

我应该如何进行

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


它安全吗?

一个准备好的语句需要有一个定义良好的参数数量;它不能有任何动态功能的元素。这意味着您必须生成所需的特定语句,并为此做好准备

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


顺便说一句,我不是MySQL专家,但是如果不加入where条件,而是在(tag1,tag2,tag3,tag4)中编写
where标记,这不会有什么区别吗?

关于问题的安全部分,准备好的带有占位符的语句与用值填充占位符所涉及的验证机制一样安全。就mysqli编写的声明而言,声明中说:

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

但是,它们不允许用于标识符(如表名或列名),也不允许用于命名select语句返回的列的select列表,也不允许用于指定二进制运算符(如=等号)的两个操作数。后一种限制是必要的,因为不可能确定参数类型。不允许通过将标记与NULL进行比较?也为空。通常,参数仅在数据操作语言(DML)语句中合法,而在数据定义语言(DDL)语句中不合法

这显然排除了修改查询的一般语义的任何可能性,这使得它更难(但并非不可能)偏离其原始意图

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

 $searchStr = 'WHERE tags.tag LIKE ?' . 
                        str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');
对于
bind_param
调用,应使用
call_user_func_数组
如下所示:

$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\u user\u func\u array
    需要一个整数索引数组作为其第二个参数。我不知道用字典会怎么样
  • mysqli\u stmt\u bind\u param
    通过引用传递其参数
对于第一点,您只需要确保
$bindArray
使用整数索引,这是上面代码中的情况(或者检查
调用用户函数数组
是否不会阻塞您提供的数组)

对于第二点,只有在调用
bind_参数
(即通过
call_user_func_array
函数)之后和执行查询之前想要修改
$bindary
中的数据时,才会出现问题。 如果您希望这样做-例如,通过在同一脚本中使用不同的参数值多次运行同一查询,那么您必须为以下查询执行使用相同的数组(
$bindArray
),并使用相同的键更新数组项。复制另一个阵列将无法工作,除非手动完成:

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

上述方法之所以有效,是因为它不会破坏数组项上的引用,这些数组项在前面调用语句时与语句绑定。同样,以下操作也应该有效,因为它不会更改先前设置的引用

array_walk($bindArray, function($k,&$v){$v = some_new_value();});

通过找到答案解决了这个问题

$query=“从标记中选择*,其中tags.tag类似CONCAT('%',?,'%')”。str_repeat(“或tags.tag,如CONCAT('%',?,'%')”,$searchNumber-1)
$stmt=$mysqli->prepare($query);
$bind_names[]=str_repeat('s',$searchNumber);
对于($i=0;$iexecute();

您不能创建一个数组作为第二个参数并调用
bind_param
吗?Tested
array_unshift($searchArray,str_repeat('s',$searchNumber));调用_user_func_数组(数组($stmt,'bind_param'),$searchArray)
虽然我得到了这个错误,但我不知道如何修复它:
错误消息:mysqli_stmt::bind_param()的参数2应该是一个引用,给定的值为
Tested
$bindArray[0]=str_repeat('s',$searchNumber);array_walk($searchArray,function($k,&$v){$bindArray[]=&$v;});调用_user_func_数组(数组($stmt,'bind_param'),$bindArray)并给出此错误
错误消息:错误参数co
foreach($bindArray as &$v)
    $v = some_new_value();
array_walk($bindArray, function($k,&$v){$v = some_new_value();});
$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();