【问题标题】:Prepared Statement when not knowing how many parameters不知道有多少参数时的准备语句
【发布时间】:2020-01-22 14:24:52
【问题描述】:

我正在尝试将我一年前制作的复杂 php 文件转换为准备好的语句。参数将像这样传入:

file.php?name=John&age=20

但是,可能会使用更多参数。工作、地址、电话号码等。这是我容易与准备好的陈述混淆的地方。

例如:

$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";

if (isset($_REQUEST['name'])) {
   $query  .= " and name = ?";
}

if (isset($_REQUEST['age'])) {
   $query  .= " and age = ?";
}

if (isset($_REQUEST['address'])) {
   $query  .= " and address = ?";
}

$stmt = $db->prepare($query);
$stmt->bind_param('sis', $_REQUEST['name'], $_REQUEST['age'], $_REQUEST['address']);
$stmt->execute();

这里的问题是bind_param,因为我不知道有多少参数可能可用。

我将如何以合乎逻辑的方式解决这个问题?

【问题讨论】:

  • 我不确定来自 mysqli 驱动程序的 bind_param(),但是使用 PDO,您可以调用 bindValue() 任意次数,每次都有一个值,因此您可以将其放入循环。

标签: php mysqli prepared-statement


【解决方案1】:

一个很好的问题。解决方案非常简单。

你需要的是argument unpacking operator。它将允许您在bind_param 中使用任意长度的数组。您只需将接受的参数收集到数组中,然后动态创建类型字符串,最后使用上述运算符:

$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();

if (isset($_REQUEST['name'])) {
   $query  .= " and name = ?";
   $params[] = $_REQUEST['name'];
}

if (isset($_REQUEST['age'])) {
   $query  .= " and age = ?";
   $params[] = $_REQUEST['age'];
}

if (isset($_REQUEST['address'])) {
   $query  .= " and address = ?";
   $params[] = $_REQUEST['address'];
}

if ($params)
    $stmt = $db->prepare($query);
    $types = str_repeat("s", count($params));
    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $result = $stmt->get_result();
} else {
    $result = $db->query($query);
}

【讨论】:

  • 好吧,这有点道理!如果数据类型不是字符串,那么 $types 将如何工作?例如我们也可以有一个整数?
  • Mysql 很乐意接受一个整数作为字符串。过去我们使用where id='1' 没有任何问题,所以使用“s”是一样的。
  • 刚刚在我的测试环境中运行它就可以了!有趣的是,整数很容易被接受为字符串。没想到。
【解决方案2】:

对于选择查询,我认为您不需要获取所有参数,获取 id 或 name 或作业名称就足以列出,我的意思是单个参数可能两个。

但是插入这么多值会很痛苦,所以我会使用这样的函数:

此函数计算数组和绑定中的值。

function bindQueryParams($sql, $param_type, $param_value_array) {
    $param_value_reference[] = & $param_type;
    for($i=0; $i<count($param_value_array); $i++) {
        $param_value_reference[] = & $param_value_array[$i];
    }
    call_user_func_array(array(
        $sql,
        'bind_param'
    ), $param_value_reference);
}

这个函数插入:

function insert($query, $param_type, $param_value_array) {
    $sql = $this->conn->prepare($query);
    $this->bindQueryParams($sql, $param_type, $param_value_array);
    $sql->execute();
}

美国:

 $query = "INSERT INTO tbl_token_auth (username, password_hash, selector_hash, expiry_date) values (?, ?, ?,?)";
        $result = insert($query, 'ssss', array($username, $random_password_hash, $random_selector_hash, $expiry_date));

更新:我的函数可用于这样的选择:

function runQuery($query, $param_type, $param_value_array) {

    $sql = $this->conn->prepare($query);
  //This function calling bindQueryParams function and auto creating binds
    $this->bindQueryParams($sql, $param_type, $param_value_array);
    $sql->execute();
    $result = $sql->get_result();

    if ($result->num_rows > 0) {
        while($row = $result->fetch_assoc()) {
            $results[] = $row;
        }
    }

    if(!empty($results)) {
        return $results;
    }
}

美国:

 $query = "Select * from your table where name = ?";
  //You just need to variables in array 
 $result = runQuery($query, 's', array($your_variables));

【讨论】:

  • 问题是关于选择,而不是插入。
  • @YourCommonSense 好吧,我的功能是关于插入和更新,也可以用于 select 很好,我在你的回答中看到了 :)
  • 仅供参考,PHP 中有参数解包运算符,mysqli 中有 fetch_all()。而且您的类型字符串不是动态的。
  • @YourCommonSense 我尊重您的知识,我有多年的时间来接近您的知识,但我确信 100% 我的功能确实运行良好并且自动创建 bind_params,免费测试 :) 我认为我已经更早地在这个网站上更正了这个功能。
  • 反过来,我尊重你的学习意愿。看,这里的问题是如何处理未知数量的参数。但是您的类型字符串是静态的。在您的示例中,它是“ssss”或“s”。但是“s”字母的数量应该是动态的,取决于$param_value_array中元素的数量
【解决方案3】:

嗯,这个过程将与您构建$query 变量的方式非常相似 - 即您一次添加一个参数列表。

考虑bind_param 需要两件事:

首先是一个简单字符串形式的数据类型列表。因此,您可以只为每个参数添加一个字符串变量,然后将其传递给最后的 bind_param。

其次是参数列表。这比较棘手,因为您不能只为 bind_param 提供一个数组。但是,您可以创建自己的数组,然后使用 call_user_func_array 将其转换为平面列表。

这是我之前写的(效果很好)。请注意,它会尝试检测参数类型并创建合适的字符串,但如果您愿意,您可以在 if 语句中手动构建一个字符串:

$query = "SELECT name, id, age, address, phone_number from person_db WHERE 1=1 ";
$params = array();

if (isset($_REQUEST['name'])) {
   $query  .= " and name = ?";
   $params[] = $_REQUEST['name'];
}

if (isset($_REQUEST['age'])) {
   $query  .= " and age = ?";
   $params[] = $_REQUEST['age'];
}

if (isset($_REQUEST['address'])) {
   $query  .= " and address = ?";
   $params[] = $_REQUEST['address'];
}

$stmt = $db->prepare($query);

if (!is_null($params))
{
  $paramTypes = "";

  foreach ($params as $param)
  {
    $paramTypes .= mysqliContentType($param);
  }

  $bindParamArgs = array_merge(array($paramTypes), $params);

  //call bind_param using an unpredictable number of parameters
  call_user_func_array(array(&$stmt, 'bind_param'), getRefValues($bindParamArgs));
}

$stmt->execute();

function mysqliContentType($value)
{
    if(is_string($value)) $type = 's';
    elseif(is_float($value)) $type = 'd';
    elseif(is_int($value)) $type = 'i';
    elseif($value == null) $type = 's'; //for nulls, just have to assume a string. hopefully this doesn't mess anything up.
    else throw new Exception("type of '".$value."' is not string, int or float");
    return $type;
}

function getRefValues($arr)
{
  $refs = array();

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

  return $refs;
}

【讨论】:

  • “因为你不能只为 bind_param 提供一个数组”。是的你可以。使用参数解包。
  • 我投了反对票,因为mysqliContentType。类型应该由列数据类型而不是由值决定。这会增加不必要的复杂性,并且在某些情况下可能会损害数据。
  • @Dharman 由程序员(和良好实践)为目标字段提供正确的类型,你不觉得吗?我编写它是为了节省额外的编码,因为每次将参数发送到数据库时都必须指定它。它从来没有引起问题(即使是 null 的情况)——但我不会再告诉 PHP 提供一个类型与目标列中的参数不匹配(或不兼容)的参数(例如,不会'不要将字符串发送到整数字段)。正如我在答案描述中所说,如果愿意,您可以轻松地将其替换为硬编码值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多