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

时间:2011-02-24 03:26:35

标签: php arrays mysqli bind prepared-statement

我一直在学习为我的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);

     }

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

或许还有更好的解决方案?

7 个答案:

答案 0 :(得分:16)

找到了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!

答案 1 :(得分:6)

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

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>';
}

答案 2 :(得分:5)

  

或许还有更好的解决方案?

这个答案对你没什么帮助,但你应该认真考虑从mysqli切换到PDO

这主要是因为PDO通过内置函数完成了你在mysqli中所做的事情。除了manual param binding之外,execute method还可以使用一组参数。

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

答案 3 :(得分:3)

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));

答案 4 :(得分:2)

我发现了一个很好的mysqli类,它可以处理动态参数,并且易于使用

https://github.com/ajillion/PHP-MySQLi-Database-Class

您可以参考源代码动态构建查询的方式

https://github.com/ajillion/PHP-MySQLi-Database-Class/blob/master/MysqliDb.php

答案 5 :(得分:1)

我通过应用类似于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 :(得分:-2)

我通常使用 mysqli 准备好的语句方法,并且在我根据函数中包含的参数动态构建查询时经常遇到这个问题(正如您所描述的)。这是我的方法:

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] => isiisi [1] => 1 [2] => 4 [3] => 5 [4] => 6 [5] => 7 [6] => 8 )

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

不确定这是否是一种明智的做法,但它确实有效,而且在我的用例中没有性能问题。