如何使用 mysqli API 制作完全动态的准备语句?
How to make a fully dynamic prepared statement using mysqli API?
我需要更改此查询以使用准备好的语句。可能吗?
查询:
$sql = "SELECT id, title, content, priority, date, delivery FROM tasks " . $op . " " . $title . " " . $content . " " . $priority . " " . $date . " " . $delivery . " ORDER BY " . $orderField . " " . $order . " " . $pagination . "";
在查询之前,有代码检查 POST 个变量并更改查询中变量的内容。
//For $op makes an INNER JOIN with or without IN clause depending on the content of a $_POST variable
$op = "INNER JOIN ... WHERE opID IN ('"$.opID."')";
//Or
$op = "INNER JOIN ... ";
//For $title (depends of $op):
$title = "WHERE title LIKE'%".$_POST["title"]."%'";
//Or
$title = "AND title LIKE'%".$_POST["title"]."%'";
//For $content:
$content = "AND content LIKE '%".$_POST["content"]."%'";
//For $priority just a switch:
$priority = "AND priority = DEPENDING_CASE";
//For $date and $delivery another switch
$d = date("Y-m-d", strtotime($_POST["date"]));
$date = "AND date >= '$d' 00:00:00 AND date <= '$d' 23:59:59";
//Or $date = "AND date >= '$d' 00:00:00";
//Or $date = "AND date <= '$d' 23:59:59";
//For $orderField
$orderField = $_POST["column"];
//For $order
$order= $_POST["order"];
//For $pagination
$pagination = "LIMIT ".$offset.",". $recordsPerPage;
如何使用准备好的语句执行此查询?
- 查询可以更静态,但这意味着根据 $_POST 检查生成不同的准备语句并执行它。
- 它取决于许多变量,因为此查询在 table 中显示结果,其中包含要排序的搜索字段和列。
完整的查询示例如下(取决于 $_POST 检查):
SELECT id, title, content, priority, date, delivery FROM tasks INNER JOIN op ON task.op = op.opId WHERE op IN (4851,8965,78562) AND title LIKE '%PHT%' AND content LIKE '%%' AND priority = '2' ORDER BY date DESC LIMIT 0, 10
一个很好的问题。并感谢您转向准备好的陈述。似乎经过这么多年的斗争,这个想法终于开始流行起来。
免责声明:将有指向我自己网站的链接,因为我帮助 PHP 的人 20 多年,并且痴迷于撰写有关最常见问题的文章。
是的,完全有可能。查看我的文章 How to create a search filter for mysqli 以获取功能齐全的示例。
对于 WHERE
部分,您只需创建两个单独的数组 - 一个包含带占位符的查询条件,另一个包含这些占位符的实际值,即:
WHERE
子句
$conditions = [];
$parameters = [];
if (!empty($_POST["content"])) {
$conditions[] = 'content LIKE ?';
$parameters[] = '%'.$_POST['content ']."%";
}
依此类推,适用于所有搜索条件。
然后你可以 implode
使用 AND
字符串作为胶水的所有条件,并得到 first-class WHERE
子句:
if ($conditions)
{
$where .= " WHERE ".implode(" AND ", $conditions);
}
所有搜索条件的套路都是一样的,但是对于IN()
子句会有点不同。
IN()
子句
有点不同,因为您需要更多的占位符和更多的值来添加:
if (!empty($_POST["opID"])) {
$in = str_repeat('?,', count($array) - 1) . '?';
$conditions[] = "opID IN ($in)";
$parameters = array_merge($parameters, $_POST["opID"]);
}
此代码将向 IN()
子句添加与 $_POST["opID"]
中的元素一样多的 ?
占位符,并将所有这些值添加到 $parameters
数组。解释可以在我网站同一部分的相邻文章中找到。
完成 WHERE
子句后,您可以转到查询的其余部分
ORDER BY
子句
您不能参数化 order by 子句,因为字段名称和 SQL 关键字不能用占位符表示。为了解决这个问题,我请求您使用我为此目的而写的 whitelisting function。有了它,您可以使您的 ORDER BY 子句 100% 安全但非常灵活。您只需要预定义一个数组,其中包含 order by 子句中允许的字段名称:
$sortColumns = ["title","content","priority"]; // add your own
然后使用这个方便的函数获取安全值:
$orderField = white_list($_POST["column"], $sortColumns, "Invalid column name");
$order = white_list($_POST["order"], ["ASC","DESC"], "Invalid ORDER BY direction");
这是一个智能功能,涵盖三种不同的场景
- 如果没有提供任何值(即 $_POST["column"] 为空),将使用白名单中的第一个值,因此它作为默认值
- 如果提供了正确的值,它将在查询中使用
- 如果提供的值不正确,则会抛出错误。
LIMIT
子句
LIMIT
值已完全参数化,因此您只需将它们添加到 $parameters
数组即可:
$limit = "LIMIT ?, ?";
$parameters[] = $offset;
$parameters[] = $recordsPerPage;
总装
最后,您的查询将是这样的
$sql = "SELECT id, title, content, priority, date, delivery
FROM tasks INNER JOIN ... $where ORDER BY `$orderField` $order $limit";
并且可以使用下面的代码执行
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters);
$stmt->execute();
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
其中 $data
是一个常规数组,包含查询返回的所有行。
我需要更改此查询以使用准备好的语句。可能吗?
查询:
$sql = "SELECT id, title, content, priority, date, delivery FROM tasks " . $op . " " . $title . " " . $content . " " . $priority . " " . $date . " " . $delivery . " ORDER BY " . $orderField . " " . $order . " " . $pagination . "";
在查询之前,有代码检查 POST 个变量并更改查询中变量的内容。
//For $op makes an INNER JOIN with or without IN clause depending on the content of a $_POST variable
$op = "INNER JOIN ... WHERE opID IN ('"$.opID."')";
//Or
$op = "INNER JOIN ... ";
//For $title (depends of $op):
$title = "WHERE title LIKE'%".$_POST["title"]."%'";
//Or
$title = "AND title LIKE'%".$_POST["title"]."%'";
//For $content:
$content = "AND content LIKE '%".$_POST["content"]."%'";
//For $priority just a switch:
$priority = "AND priority = DEPENDING_CASE";
//For $date and $delivery another switch
$d = date("Y-m-d", strtotime($_POST["date"]));
$date = "AND date >= '$d' 00:00:00 AND date <= '$d' 23:59:59";
//Or $date = "AND date >= '$d' 00:00:00";
//Or $date = "AND date <= '$d' 23:59:59";
//For $orderField
$orderField = $_POST["column"];
//For $order
$order= $_POST["order"];
//For $pagination
$pagination = "LIMIT ".$offset.",". $recordsPerPage;
如何使用准备好的语句执行此查询?
- 查询可以更静态,但这意味着根据 $_POST 检查生成不同的准备语句并执行它。
- 它取决于许多变量,因为此查询在 table 中显示结果,其中包含要排序的搜索字段和列。
完整的查询示例如下(取决于 $_POST 检查):
SELECT id, title, content, priority, date, delivery FROM tasks INNER JOIN op ON task.op = op.opId WHERE op IN (4851,8965,78562) AND title LIKE '%PHT%' AND content LIKE '%%' AND priority = '2' ORDER BY date DESC LIMIT 0, 10
一个很好的问题。并感谢您转向准备好的陈述。似乎经过这么多年的斗争,这个想法终于开始流行起来。
免责声明:将有指向我自己网站的链接,因为我帮助 PHP 的人 20 多年,并且痴迷于撰写有关最常见问题的文章。
是的,完全有可能。查看我的文章 How to create a search filter for mysqli 以获取功能齐全的示例。
对于 WHERE
部分,您只需创建两个单独的数组 - 一个包含带占位符的查询条件,另一个包含这些占位符的实际值,即:
WHERE
子句
$conditions = [];
$parameters = [];
if (!empty($_POST["content"])) {
$conditions[] = 'content LIKE ?';
$parameters[] = '%'.$_POST['content ']."%";
}
依此类推,适用于所有搜索条件。
然后你可以 implode
使用 AND
字符串作为胶水的所有条件,并得到 first-class WHERE
子句:
if ($conditions)
{
$where .= " WHERE ".implode(" AND ", $conditions);
}
所有搜索条件的套路都是一样的,但是对于IN()
子句会有点不同。
IN()
子句
有点不同,因为您需要更多的占位符和更多的值来添加:
if (!empty($_POST["opID"])) {
$in = str_repeat('?,', count($array) - 1) . '?';
$conditions[] = "opID IN ($in)";
$parameters = array_merge($parameters, $_POST["opID"]);
}
此代码将向 IN()
子句添加与 $_POST["opID"]
中的元素一样多的 ?
占位符,并将所有这些值添加到 $parameters
数组。解释可以在我网站同一部分的相邻文章中找到。
完成 WHERE
子句后,您可以转到查询的其余部分
ORDER BY
子句
您不能参数化 order by 子句,因为字段名称和 SQL 关键字不能用占位符表示。为了解决这个问题,我请求您使用我为此目的而写的 whitelisting function。有了它,您可以使您的 ORDER BY 子句 100% 安全但非常灵活。您只需要预定义一个数组,其中包含 order by 子句中允许的字段名称:
$sortColumns = ["title","content","priority"]; // add your own
然后使用这个方便的函数获取安全值:
$orderField = white_list($_POST["column"], $sortColumns, "Invalid column name");
$order = white_list($_POST["order"], ["ASC","DESC"], "Invalid ORDER BY direction");
这是一个智能功能,涵盖三种不同的场景
- 如果没有提供任何值(即 $_POST["column"] 为空),将使用白名单中的第一个值,因此它作为默认值
- 如果提供了正确的值,它将在查询中使用
- 如果提供的值不正确,则会抛出错误。
LIMIT
子句
LIMIT
值已完全参数化,因此您只需将它们添加到 $parameters
数组即可:
$limit = "LIMIT ?, ?";
$parameters[] = $offset;
$parameters[] = $recordsPerPage;
总装
最后,您的查询将是这样的
$sql = "SELECT id, title, content, priority, date, delivery
FROM tasks INNER JOIN ... $where ORDER BY `$orderField` $order $limit";
并且可以使用下面的代码执行
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters);
$stmt->execute();
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
其中 $data
是一个常规数组,包含查询返回的所有行。