如何在 PHP 中动态绑定 mysqli bind_param 参数?

Posted

技术标签:

【中文标题】如何在 PHP 中动态绑定 mysqli bind_param 参数?【英文标题】:How to bind mysqli bind_param arguments dynamically in PHP? 【发布时间】:2022-01-14 20:19:53 【问题描述】:

我一直在学习为我的 sql 查询使用准备好的和绑定的语句,到目前为止我已经提出了这个,它工作正常,但是当涉及多个参数或不需要参数时,它根本不是动态的,

public function get_result($sql,$parameter)
    
        # create a prepared statement
    $stmt = $this->mysqli->prepare($sql);

        # bind parameters for markers
    # but this is not dynamic enough...
        $stmt->bind_param("s", $parameter);

        # execute query 
        $stmt->execute();

    # these lines of code below return one dimentional array, similar to mysqli::fetch_assoc()
        $meta = $stmt->result_metadata(); 

        while ($field = $meta->fetch_field())  
            $var = $field->name; 
            $$var = null; 
            $parameters[$field->name] = &$$var; 
        

        call_user_func_array(array($stmt, 'bind_result'), $parameters); 

        while($stmt->fetch()) 
         
            return $parameters;
            //print_r($parameters);      
        


        # close statement
        $stmt->close();
    

这就是我调用对象类的方式,

$mysqli = new database(DB_HOST,DB_USER,DB_PASS,DB_NAME);
$output = new search($mysqli);

有时我不需要传入任何参数,

$sql = "
SELECT *
FROM root_contacts_cfm
";

print_r($output->get_result($sql));

有时我只需要一个参数,

$sql = "
SELECT *
FROM root_contacts_cfm
WHERE root_contacts_cfm.cnt_id = ?
ORDER BY cnt_id DESC
";

print_r($output->get_result($sql,'1'));

有时我只需要多个参数,

$sql = "
SELECT *
FROM root_contacts_cfm
WHERE root_contacts_cfm.cnt_id = ?
AND root_contacts_cfm.cnt_firstname = ?
ORDER BY cnt_id DESC
";

print_r($output->get_result($sql,'1','Tk'));

所以,我认为这条线对于上面的动态任务来说不够动态,

$stmt->bind_param("s", $parameter);

要动态构建 bind_param,我在网上的其他帖子上找到了这个。

call_user_func_array(array(&$stmt, 'bind_params'), $array_of_params);

我试图从php.net 修改一些代码,但我无处可去,

if (strnatcmp(phpversion(),'5.3') >= 0) //Reference is required for PHP 5.3+ 
     
        $refs = array(); 
        foreach($arr as $key => $value) 
            $array_of_param[$key] = &$arr[$key]; 

       call_user_func_array(array(&$stmt, 'bind_params'), $array_of_params);

     

为什么?有什么想法可以让它发挥作用吗?

或者也许有更好的解决方案?

【问题讨论】:

【参考方案1】:

或者也许有更好的解决方案??

这个答案对你帮助不大,但你应该认真考虑从mysqli切换到PDO。

这样做的主要原因是因为 PDO 使用内置函数完成了您在 mysqli 中尝试执行的操作。除了 manual param binding 之外,execute method 还可以采用一组参数。

PDO 易于扩展,并且添加方便的方法来获取所有内容并返回而不是执行准备执行舞蹈非常容易。

【讨论】:

问题是PDO比mysqli慢很多。甚至比旧的 mysql 还要慢。 @Julian:说PDO 比is a bit much 慢很多。性能差异太小,不可能成为主要瓶颈(如果有的话) 感谢指正。我阅读了一个差异更显着的基准。而我自己的测试也有更显着的结果。 @Julian 每次我使用它时都会像圣诞节一样慢。 mysqli 快得多,我无法证明它的便利性。但我倾向于它。【参考方案2】:

找到了mysqli的答案:

public function get_result($sql,$types = null,$params = null)
    
        # create a prepared statement
        $stmt = $this->mysqli->prepare($sql);

        # bind parameters for markers
        # but this is not dynamic enough...
        //$stmt->bind_param("s", $parameter);

        if($types&&$params)
        
            $bind_names[] = $types;
            for ($i=0; $i<count($params);$i++) 
            
                $bind_name = 'bind' . $i;
                $$bind_name = $params[$i];
                $bind_names[] = &$$bind_name;
            
            $return = call_user_func_array(array($stmt,'bind_param'),$bind_names);
        

        # execute query 
        $stmt->execute();

        # these lines of code below return one dimentional array, similar to mysqli::fetch_assoc()
        $meta = $stmt->result_metadata(); 

        while ($field = $meta->fetch_field())  
            $var = $field->name; 
            $$var = null; 
            $parameters[$field->name] = &$$var; 
        

        call_user_func_array(array($stmt, 'bind_result'), $parameters); 

        while($stmt->fetch()) 
         
            return $parameters;
            //print_r($parameters);      
        


        # the commented lines below will return values but not arrays
        # bind result variables
        //$stmt->bind_result($id); 

        # fetch value
        //$stmt->fetch(); 

        # return the value
        //return $id; 

        # close statement
        $stmt->close();
    

然后:

$mysqli = new database(DB_HOST,DB_USER,DB_PASS,DB_NAME);
$output = new search($mysqli);

$sql = "
SELECT *
FROM root_contacts_cfm
ORDER BY cnt_id DESC
";
print_r($output->get_result($sql));

$sql = "
SELECT *
FROM root_contacts_cfm
WHERE root_contacts_cfm.cnt_id = ?
ORDER BY cnt_id DESC
";

print_r($output->get_result($sql,'s',array('1')));

$sql = "
SELECT *
FROM root_contacts_cfm
WHERE root_contacts_cfm.cnt_id = ?
AND root_contacts_cfm.cnt_firstname = ?
ORDER BY cnt_id DESC
";

print_r($output->get_result($sql, 'ss',array('1','Tk')));

mysqli 在这方面实在是太蹩脚了。我想我应该迁移到 PDO!

【讨论】:

params 数组中的 Null 值会破坏您的动态绑定调用,仅供参考 变量$bind_name实际上是未使用的,$$bind_name使代码混乱,而$$在PHP中没有有任何特殊含义。 $var$$var 也是如此。 注意:来自 PHP 手册:“在使用 mysqli_stmt_bind_param() 和 call_user_func_array() 时必须小心。注意 mysqli_stmt_bind_param() 需要通过引用传递参数,而 call_user_func_array() 可以接受可以代表引用或值的变量列表作为参数。”来源:php.net/manual/en/mysqli-stmt.bind-param.php【参考方案3】:

使用 PHP 5.6,您可以在 unpacking operator(...$var) 的帮助下轻松完成此操作,并使用 get_result() insted of bind_result()

public function get_result($sql,$types = null,$params = null) 
    $stmt = $this->mysqli->prepare($sql);
    $stmt->bind_param($types, ...$params);

    if(!$stmt->execute()) return false;
    return $stmt->get_result();


例子:

$mysqli = new database(DB_HOST,DB_USER,DB_PASS,DB_NAME);
$output = new search($mysqli);


$sql = "SELECT * FROM root_contacts_cfm WHERE root_contacts_cfm.cnt_id = ?
        AND root_contacts_cfm.cnt_firstname = ?
        ORDER BY cnt_id DESC";

$res = $output->get_result($sql, 'ss',array('1','Tk'));
while($row = res->fetch_assoc())
   echo $row['fieldName'] .'<br>';

【讨论】:

【参考方案4】:

使用 PHP 5.6 或更高版本

$stmt->bind_param(str_repeat("s", count($data)), ...$data);

使用 PHP 5.5 或更低版本,您可能(并且确实)期望以下工作:

call_user_func_array(
    array($stmt, "bind_param"),
    array_merge(array(str_repeat("s", count($data))), $data));

...但是mysqli_stmt::bind_param 期望它的参数是引用,而这会传递一个值列表。

您可以通过首先创建一个对原始数组的引用数组来解决这个问题(尽管这是一个丑陋的解决方法)。

$references_to_data = array();
foreach ($data as &$reference)  $references_to_data[] = &$reference; 
unset($reference);
call_user_func_array(
    array($stmt, "bind_param"),
    array_merge(array(str_repeat("s", count($data))), $references_to_data));

【讨论】:

【参考方案5】:

我通过应用类似于 PDO 的系统解决了这个问题。 SQL 占位符是以双点字符开头的字符串。例如:

:id, :name, or :last_name

然后,您可以直接在占位符字符串中指定数据类型,方法是在双点之后立即添加规范字母并在助记符变量之前附加下划线字符。例如:

:i_id (i=integer), :s_name or :s_last_name (s=string)

如果没有添加类型字符,则函数将通过分析保存数据的php变量来确定数据的类型。例如:

$id = 1 // interpreted as an integer
$name = "John" // interpreted as a string

该函数返回一个类型数组和一个值数组,您可以使用它们在循环中执行 php 函数 mysqli_stmt_bind_param()。

$sql = 'SELECT * FROM table WHERE code = :code AND (type = :i_type OR color = ":s_color")';
$data = array(':code' => 1, ':i_type' => 12, ':s_color' => 'blue');

$pattern = '|(:[a-zA-Z0-9_\-]+)|';
if (preg_match_all($pattern, $sql, $matches)) 
    $arr = $matches[1];
    foreach ($arr as $word) 
        if (strlen($word) > 2 && $word[2] == '_') 
            $bindType[] = $word[1];
         else 
            switch (gettype($data[$word])) 
                case 'NULL':
                case 'string':
                    $bindType[] = 's';
                    break;
                case 'boolean':
                case 'integer':
                    $bindType[] = 'i';
                    break;
                case 'double':
                    $bindType[] = 'd';
                    break;
                case 'blob':
                    $bindType[] = 'b';
                    break;
                default:
                    $bindType[] = 's';
                    break;
            
            
        $bindValue[] = $data[$word];
        
    $sql = preg_replace($pattern, '?', $sql);


echo $sql.'<br>';
print_r($bindType);
echo '<br>';
print_r($bindValue);

【讨论】:

【参考方案6】:

我通常使用mysqliprepared statements 方法,并且当我根据函数中包含的参数动态构建查询时经常遇到此问题(正如您所描述的)。这是我的方法:

function get_records($status = "1,2,3,4", $user_id = false) 
    global $database;

    // FIRST I CREATE EMPTY ARRAYS TO STORE THE BIND PARAM TYPES AND VALUES AS I BUILD MY QUERY
    $type_arr = array();
    $value_arr = array();

    // THEN I START BUILDING THE QUERY
    $query = "SELECT id, user_id, url, dr FROM sources";

    // THE FIRST PART IS STATIC (IT'S ALWAYS IN THE QUERY)
    $query .= " WHERE status IN (?)";

    // SO I ADD THE BIND TYPE "s" (string) AND ADD TO THE TYPE ARRAY
    $type_arr[] = "s";

    // AND I ADD THE BIND VALUE $status AND ADD TO THE VALUE ARRAY
    $value_arr[] = $status;

    // THE NEXT PART OF THE QUERY IS DYNAMIC IF THE USER IS SENT IN OR NOT
    if ($user_id) 
        $query .= " AND user_id = ?";

    // AGAIN I ADD THE BIND TYPE AND VALUE TO THE CORRESPONDING ARRAYS
        $type_arr[] = "i";
        $value_arr[] = $user_id;
    

    // THEN I PREPARE THE STATEMENT
    $stmt = mysqli_prepare($database, $query);

    // THEN I USE A SEPARATE FUNCTION TO BUILD THE BIND PARAMS (SEE BELOW)
    $params = build_bind_params($type_arr, $value_arr);
    
    // PROPERLY SETUP THE PARAMS FOR BINDING WITH CALL_USER_FUNC_ARRAY
    $tmp = array();
    foreach ($params as $key => $value) $tmp[$key] = &$params[$key];

    // PROPERLY BIND ARRAY TO THE STATEMENT
    call_user_func_array(array($stmt , 'bind_param') , $tmp);

    // FINALLY EXECUTE THE STATEMENT        
    mysqli_stmt_execute($stmt);
    $result = mysqli_stmt_get_result($stmt);
    mysqli_stmt_close($stmt);
    return $result;

这里是build_bind_params 函数:

// I PASS IN THE TYPES AND VALUES ARRAY FROM THE QUERY ABOVE
function build_bind_params($types, $values) 

// THEN I CREATE AN EMPTY ARRAY TO STORE THE FINAL OUTPUT
    $bind_array = array();

// THEN I CREATE A TEMPORARY EMPTY ARRAY TO GROUP THE TYPES; THIS IS NECESSARY BECAUSE THE FINAL ARRAY ALL THE TYPES MUST BE A STRING WITH NO SPACES IN THE FIRST KEY OF THE ARRAY
    $i = array();
    foreach ($types as $type) 
        $i[] = $type;
    
// SO I IMPLODE THE TYPES ARRAY TO REMOVE COMMAS AND ADD IT AS KEY[0] IN THE BIND ARRAY
    $bind_array[] = implode('', $i);

// FINALLY I LOOP THROUGH THE VALUES AND ADD THOSE AS SUBSEQUENT KEYS IN THE BIND ARRAY
    foreach($values as $value) 
        $bind_array[] = $value;
    
    return $bind_array;

build_bind_params 函数的输出如下所示:

Array ( [0] =&gt; isiisi [1] =&gt; 1 [2] =&gt; 4 [3] =&gt; 5 [4] =&gt; 6 [5] =&gt; 7 [6] =&gt; 8 )

您可以看到[0] 键是所有没有空格和逗号的绑定类型。其余的键代表相应的值。请注意,如果我有 6 个绑定值都具有不同的绑定类型,则上面的输出是一个示例。

不确定这是否是一种明智的做法,但它可以工作并且在我的用例中没有性能问题。

【讨论】:

这种方法在十年前就已经存在了。您可能想注意现有的答案,它需要的代码少了十倍,并且根本不需要额外的功能。从已经存在的答案中学习通常是个好主意。 太棒了,谢谢你;你真友好。我只是一个初学者,让我休息一下。 很抱歉再次打扰您,但我刚刚注意到您的代码中存在问题。为 IN 运算符绑定单个参数不会给您带来任何好的结果,有效地仅使用逗号分隔字符串中的第一个值。在您的情况下,WHERE status IN ("1,2,3,4") 实际上是WHERE status = 1。您需要拆分该字符串并使用单独的值,动态生成相等数量的占位符,如here 所示 @YourCommonSense 好的,现在这很有帮助,谢谢。我只是注意到查询没有返回所需的结果,也不知道为什么。我想我花了一些时间尝试其他一些提到的解决方案,尽管我无法完全理解它们,因为它们没有完全清楚地表达出来。将带我一些黑客通过它来弄清楚。但是谢谢,我很感激你阅读我的作品。 Here is your function rewritten 对数组进行正确的绑定。它使用的是我写的simple helper function to run mysqli queries

以上是关于如何在 PHP 中动态绑定 mysqli bind_param 参数?的主要内容,如果未能解决你的问题,请参考以下文章

在 $bind_param() 中动态绑定参数; mysqli

php,mysqli-stmt.bind-param]:类型定义字符串中的元素数与绑定变量数不匹配

bind_result 成数组 PHP mysqli 准备好的语句

mysqli_stmt::bind_result():绑定变量的数量与准备好的语句中的字段数量不匹配

PHP / MySQLi - 数组作为 bind_param

mysqli_stmt_bind_param():变量的数量与绑定参数中准备好的语句中的参数数量不匹配