使用call_user_func_array()动态构建预准备语句

时间:2016-05-01 07:52:00

标签: php mysql mysqli

我需要根据用户输入动态构建SQL语句和参数。 sql语句的长度和参数的数量根据用户输入而变化。我正在尝试使用this tutorial并将其应用于我的代码。这是代码:

$query = "SELECT p.*, s.* 
        FROM product p 
        INNER JOIN product_shop ps 
        ON ps.p_id = p.p_id 
        INNER JOIN shop s 
        ON s.s_id = ps.s_id 
        WHERE s.country = ?";
        $a_params[] = $place['country'];
        $a_param_type[] = "s";
    // prepare and bind

    $param_type = '';

    foreach ($place as $key => $value) {
        if ($key === "country") {
            continue;
        }
        $query .= " and s.$key = ?";
        $a_params[] = $value;
        $a_param_type[] = "s";
    }
    /* Prepare statement */
    $stmt = $conn->prepare($query);
    if($stmt === false) {
        trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->errno . ' ' . $conn->error, E_USER_ERROR);
    }

    $a_params[] =  $a_param_type;

    /* use call_user_func_array, as $stmt->bind_param('s', $param); does not accept params array */
    call_user_func_array(array($stmt, 'bind_param'), $a_params);

    /* Execute statement */
    $stmt->execute();   
    $meta = $stmt->result_metadata();

我知道$place['country']将永远填充。 sql语句是正确的。它是:

"SELECT p.*, s.* \n        FROM product p \n        INNER JOIN product_shop ps \n        ON ps.p_id = p.p_id \n        INNER JOIN shop s \n        ON s.s_id = ps.s_id \n        WHERE s.country = ? and s.suburb = ? and s.city = ? and s.province = ?"

不介意“\ n”字符,它们对sql语句没有影响。

在:

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

$a_params的值是:

0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"

在:

$meta = $stmt->result_metadata();

$ meta的值变为:

current_field:0
field_count:13
lengths:null
num_rows:0
type:1

表示没有从数据库中选择任何行。我已手动在数据库上执行此sql并返回行。我的代码出了什么问题,使得它不返回数据库中的行?

编辑:我看到this answer说要将“ssss”放在$params的开头,所以我这样做并在{{1}中出现此错误object:

$stmt

2 个答案:

答案 0 :(得分:2)

我不明白你尝试过的方法,但我会尽力回答:

根据bind_param manual

bind_param的第一个参数是字符串,例如'ssss'

第二个和其他参数 - 是要插入查询的值。

因此,您的$a_params数组应

0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:array(4)
0:"s"
1:"s"
2:"s"
3:"s"

可是:

0:"ssss"
1:"New Zealand"
2:"Grey Lynn"
3:"Auckland"
4:"Auckland"

请参阅?所有值都是字符串。和占位符'类型是第一个。

还要考虑$a_params中参数的顺序必须与bind_param中参数的顺序相同。这意味着,即$a_params喜欢

0:"New Zealand"
1:"Grey Lynn"
2:"Auckland"
3:"Auckland"
4:"ssss"

错了。因为$a_params的第一个元素将是bind_param的第一个参数,在这种情况下,它不是"ssss"字符串。

所以,这意味着在你用$a_params填充值,占位符'之后字符串应添加到$a_params的开头,例如array_unshift

// make $a_param_type a string
$str_param_type = implode('', $a_param_type);

// add this string as a first element of array
array_unshift($a_params, $str_param_type);

// try to call
call_user_func_array(array($stmt, 'bind_param'), $a_params);

如果这不起作用,您可以引用answer you provided的一部分,其中$a_params的值通过引用另一个数组$tmp传递,在您的情况下你可以尝试类似的东西:

// make $a_param_type a string
$str_param_type = implode('', $a_param_type);

// add this string as a first element of array
array_unshift($a_params, $str_param_type);

$tmp = array();
foreach ($a_params as $key => $value) {
    // each value of tmp is a reference to `$a_params` values
    $tmp[$key] = &$a_params[$key];  
}

// try to call, note - with $tmp, not with $a_params
call_user_func_array(array($stmt, 'bind_param'), $tmp);

答案 1 :(得分:2)

记住:需要引用call_user_func_array的第二个参数,而不仅仅是普通数组。这是关键。接受的答案是好的,但只缺少一件事,使参数参考:

    function makeValuesReferenced($arr){
    $refs = array();
    foreach($arr as $key => $value)
        $refs[$key] = &$arr[$key];
    return $refs;
    }

$stmt = $conn->prepare($query);
//$stmt->bind_param($queryParamTypes, $queryParams); 
call_user_func_array(array($stmt, 'bind_param'), makeValuesReferenced($queryParams));
$stmt->execute();