MySQLI使用call_user_func_array绑定参数

16

请看下面的代码。 我正在尝试将参数数组绑定到我的prepared statement。 我在网上找了一些资料,发现我必须使用call_user_func_array,但是无法让它正常工作。我得到的错误信息是: “第一个参数应该是一个有效的回调函数,而给出的是'Array'” 我可能错了,但我认为第一个参数可以是一个数组,也许这个错误信息有误导性。我认为问题可能在于我的数组有问题。 有人可以看出我做错了什么吗?谢谢。

$type = array("s", "s");
$param = array("string1","anotherstring");

$stmt = $SQLConnection->prepare("INSERT INTO mytable (comp, addl) VALUES (?,?)");

$params = array_merge($type, $param);

call_user_func_array(array(&$stmt, 'bind_param'), $params);
$SQLConnection->execute();

我看到我的错误应该在第一行: $type = array("ss"); - Columbo
1
这种情况让我坚信使用PDO比mysqli更好。 - Bill Karwin
1
现代方法:https://dev59.com/oWQm5IYBdhLWcg3w8Sne#58355651 和 https://dev59.com/P3A65IYBdhLWcg3wqAat#71718174 - mickmackusa
7个回答

20

必须是这样的:

//connect
$mysqli = new mysqli($host, $user, $password, $db_name);

//prepare
$stmt = $mysqli->prepare("SELECT * FROM the_table WHERE field1= ? AND Field2= ?");

//Binding parameters. Types: s = string, i = integer, d = double,  b = blob
$params= array("ss","string_1","string_2");

//now we need to add references
$tmp = array();
foreach($params as $key => $value) $tmp[$key] = &$params[$key];
// now us the new array
call_user_func_array(array($stmt, 'bind_param'), $tmp);

$stmt->execute();

/* Fetch result to array */
$res = $stmt->get_result();
while($row = $res->fetch_array(MYSQLI_ASSOC)) {
  $a_data[]=$row;
}
print_r($a_data);

$stmt->close();

14
自从PHP 5.6版本以后,您不必再使用call_user_func_array()来麻烦操作了。
相反地,您可以像这样使用splat运算符,而不是像以下这样: $stmt->bind_param($param_types, $my_params_array); 您只需使用以下代码即可: $stmt->bind_param($param_types, ...$my_params_array); // exact code

第一种方法更好。这个不好因为你必须维护两个数组。 - TheRealChx101
你可以为 bind_param() 构建一个单一的参数数组; 骗局是要将类型字符串连接在元素 [0] 中,而不是将新元素推入数组。 https://dev59.com/iazka4cB1Zd3GeqP72Wk#51036322 - mickmackusa

9

我不知道为什么你必须使用call_user_func_array,但这是另一回事。

在我看来,唯一可能出现的问题是你正在使用对对象的引用。假设你正在使用PHP 5.*,那是不必要的:

call_user_func_array(array($stmt, 'bind_param'), $params);

谢谢Franz,我正在使用call_user_func_array,因为要绑定的参数数量是不确定的。我正在使用PHP5,但删除引用仍会产生相同的结果。 - Columbo
那就意味着你的对象出了问题。请参考这个 PHP 的“bug”报告:http://bugs.php.net/bug.php?id=46229。在那个位置上,`var_dump($stmt)` 输出了什么? - Franz
是的,我只得到了bool(true)而不是语句。不确定为什么,我要去看一下。我想它给我一个TRUE来表示语句准备成功了。但我已经在几个例子中看到其他人做我正在做的事情。 - Columbo
你是对的,这是一个对象问题。我在创建$SQLConnection时使用了stmt_init,但其实不需要。我仍然期望从中得到一个对象,但是删除它就解决了问题。谢谢。 - Columbo
1
我注意到你的值类型是数组中的单个项目...我假设你正在使用mysqli_stmt::bind_param(),它将其第一个参数作为字符串,因此$type = array("s", "s")需要变成$type = "ss",因为该字符串由每个字符解析。请参阅手册。另外,我相信bind_param和bind_result仍然都需要通过引用传递值列表,但call_user_func_array()不再能够通过引用传递,因为这将导致调用时的pass_by_reference致命错误,所以这实际上破坏了动态引用传递到mysqli方法中的方式。 - user253780

1

由于我的问题被标记为重复,无法自己回答这个问题:此处。但是我认为我的最终解决方案(使用此问题中的答案)适用于我的用例,并可能对某些人有所帮助。

我的目标是获取发布的一组ID并在NOT IN MYSQL语句中使用它们。假设发布了5个ID的数组。

  1. Count the number posted ID's to build the ? placeholders for NOT IN statement. Using $params_count = substr(str_repeat(',?', count($array_of_ids)), 1); gives the result: (?,?,?,?,?) to be used in SQL statement.

  2. Make function that takes ID's and type i or s etc. For me, they were all i so my function is simpler. return array that looks like this $params= array("iiiii",1,2,3,4,5) where the first value is the set of i's and the subsequent values are the ID's depending on total ID's passed into function.

    function build_bind_params($values, $bind_type) {
        $s = substr(str_repeat($bind_type, count($values)), 0);
        $bind_array = array();
        $bind_array[] = $s;
        foreach($values as $value) {
            $bind_array[] = $value;
        }
        return $bind_array; 
    }
    

$params = build_bind_params($array_of_ids, "i");

  1. 然后使用foreach ($params as $key => $value) $tmp[$key] = &$params[$key];来正确格式化新创建的$params以便绑定。
  2. 然后使用call_user_func_array(array($stmt , 'bind_param') , $tmp);来正确地绑定数组。
  3. 然后执行$stmt

substr([string], 0) 的目的是什么?你正在尝试从零位置到字符串末尾隔离子字符串;因此,子字符串=完整字符串。请编辑以便其他人不会复制粘贴。 - mickmackusa
与问题中的那行进行比较,我认为点1应该是1而不是0 - Teepeemm

1
大多数情况下,在将类型与值集成到call_user_func_array()之前,它们并不是解决方案。这个解决方案对我有效:
/* create a database connection */
$db = new mysqli($host, $user, $password, $db_name);

/* setup the sql, values, and types */
$sql="SELECT * FROM languages 
         WHERE language_code = ?
           AND charset = ?
         ORDER BY native_name";
$values = array($langCode, $charset);
$types = "ss";   

/* pass those variables to the execute() function defined below */
if ($rows = execute($sql, $values, $types))
{
   return $rows[0];
}

function execute($sql, $values='', $types='')
{
   /* prepare the sql before binding values and types */
   $stmt = $db->prepare($sql);

   /*combine the values and types into $inputArray */
   $inputArray[] = &$types;
   $j = count($values);
   for($i=0;$i<$j;$i++){
     $inputArray[] = &$values[$i];
   }
   /* add the combined values and types to call_user_func_array() for binding */
   call_user_func_array(array($stmt, 'bind_param'), $inputArray);
   $result = $stmt->execute();
   return $result;
}

以下是本示例所基于的完整描述的参考链接: http://big.info/2015/08/php-use-call_user_func_array-for-variable-number-of-parameters-arrays-in-prepared-statements.html


1
如果出现错误,你应该尝试这样做:

call_user_func_array(array($stmt, 'bind_param'), refValues($params));

function refValues($arr){
    if (strnatcmp(phpversion(),'5.3') >= 0) {
        $refs = array();
        foreach($arr as $key => $value)
            $refs[$key] = &$arr[$key];
        return $refs;
    }
    return $arr;
}

0
为什么要调用call_user_func_array(array($statement, 'bind_param'), $bind_arguments)?因为$bind_arguments是一个数组。这样你就可以拥有一个函数,将语句绑定到其查询参数,无论你有多少个参数。
好代码的例子...
    <?php
            # link
        $dblink = new mysqli('HOSTNAME','USERNAME','PASSWORD','DATABASENAME');

            # example data
        $statement = $dblink->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
        $recordvalues = ['John', 'H.', 'Smith', 25];
        $sqlbindstring = "sssi";    # String, String, String, Integer example

            # make the references
        $bind_arguments = [];
        $bind_arguments[] = $sqlbindstring;
        foreach ($recordvalues as $recordkey => $recordvalue)
        {
            $bind_arguments[] = & $recordvalues[$recordkey];    # bind to array ref, not to the temporary $recordvalue
        }

            # query the db
        call_user_func_array(array($statement, 'bind_param'), $bind_arguments);     # bind arguments
        $statement->execute();  # run statement
        $result = $statement->get_result(); # get results

            # get the results
        if($result) {
            while ($row = $result->fetch_assoc()) {
                print("\n\nMy row is...");
                print_r($row);
            }
        }
    ?>

糟糕代码的示例...

    <?php

            # Same setup as above..

        $statement->prepare("SELECT * from Person WHERE FirstName = ? AND MiddleName = ? AND LastName = ? and Age = ?");
        $statement->bind('John', 'H.", 'Smith', 25);

    ?>

在第一个例子中:您可以将尽可能多或尽可能少的内容传递给绑定,以便在整个应用程序中仅调用一次bind()。这样可以很好地扩展。
在第二个例子中:您必须为数据库中每个可能的记录的每个可能的插入组编写一个bind()语句。这样扩展性就不好了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接