提问人:kdft02 提问时间:7/13/2023 最后编辑:kdft02 更新时间:7/13/2023 访问量:70
PHP MYSQLI 预准备语句 - 是否可以使用占位符来动态生成 WHERE 子句的元素?[复制]
PHP MYSQLI Prepared Statements - Is it possible to use placeholders to dynamically generate elements of a WHERE clause? [duplicate]
问:
我最近一直在努力修复 PHP 遗留应用程序的安全漏洞。我们重构了所有 MYSQLI 查询,以使用预准备语句。这基本上是直截了当的,但我遇到了一些孤立的情况,在这些情况下,我需要对动态生成的 WHERE 子句使用占位符 (?),并且可以根据用户输入具有不同的元素。我无法找到显示如何实现此目的的文档,因此我可能需要更改我的方法。
在用户选择输入之前,我不知道需要将哪些项目添加到 where 子句中。解决这个问题的最佳方法是什么?
原始的、正常运行的查询的工作方式如下:
$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";
$where是一系列动态的 AND 语句。我只知道页面运行时 AND 子句/值的数量。例:
AND table1.business_line_id IN (2) AND table1.client_id IN (1,2,3,5) AND table1.application_id in (7,47) AND table1.region_id in (1,4,5,6,8,14,15,21)
与此项相关的其余代码:
$params = array($where);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
showerror($connection);
die();
}
// $stmt -> bind_param(get_types($params),...$params);
$stmt -> bind_param('s', $params);
$stmt -> execute();
参数数组返回一个长度为 1 的数组,其正确的$where值为 string。我试图实现一个占位符,但这传统上不遵循我们使用 field = placeholder (user_id = ?) 或 VALUES(?,?,?) 的占位符结构。是否可以生成一个查询,在其中我们使用具有此语法的 WHERE/AND 子句的占位符动态生成的部分?
下面是一个不起作用的示例。
// $query ="SELECT [fields] from table1, table2 WHERE (table1.id = table2.cc_id) ? order by num, name, due_date";
可以理解的是,MYSQLI 的 $stmt = $connection->prepare($query) 在占位符值 (?) 处对此有疑问。如果我尝试直接包含$where变量,则 bind_param() 调用会出现问题,因为它没有要绑定的参数(变量数量不匹配)。
这是一个确实有效的示例,但我担心它无法正确防止 SQL 注入。只需传递$where并删除任何参数绑定即可获得正确的输出。
$query ="SELECT [fields] FROM table1, table2 where (table1.id = table2.cc_id) $where order by num, name, due_date";
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
if(!$stmt = $connection->prepare($query)) {
// added this during debug to show the malformed query
showerror($connection);
die();
}
$stmt -> execute();
if (!$result = $stmt->get_result()) showerror($connection);
有没有办法使用占位符传递此子句?
编辑:为清楚起见$where添加构建逻辑。
$where = "and cc.business_line_id in ($business_line_ids)";
// some code
foreach($ar_select as $value) {
if (isset($_POST["${value}_list"])) {
$thisfield = ($value == "assignee_status_id") ? "table2.status_id" : "cc.$value";
$this_id_list = implode(",", $_POST["${value}_list"]);
$where .= " AND $thisfield in ($this_id_list) ";
}
}
// some code
$ar_dates = array("status_date","target_date","due_date");
foreach($ar_dates as $thisdate) {
if (strlen($_POST["$thisdate"])) {
$date = $_POST["$thisdate"];
$op_date = $_POST["op_$thisdate"];
$where .= " AND $thisdate " . date_operator($op_date) ." '$date' ";
}
}
if (strlen($_POST["keyword_name"])) {
$keyword_name = $_POST["keyword_name"];
$where .= " AND asn.name LIKE '%$keyword_name%' ";
}
答:
您只能参数化值,而不能参数化语句。
基本上,SQL 解释器编译 SQL 语句,然后插入参数值。此时,您无法重新调用编译来找出参数内部的内容,这将使之前的编译无效并使参数化变得毫无意义。我们想编译语句一次,从不插值参数。
也就是说,您可以修改构造语句的方式以包含参数堆栈,然后随时推动内容。
下面是一个简化/人为的例子:
$params = [];
$clauses = [];
$input = [
['foo = ?', ['fooval']],
['bar BETWEEN ? AND ?', ['start', 'end']],
['baz = 42', []],
['bof IS NOT ?', ['notbof']]
];
foreach($input as $item) {
$clauses[] = $item[0];
$params = array_merge($params, $item[1]);
}
$where = sprintf('WHERE %s', implode(' AND ', $clauses));
var_dump($where, $params);
输出:
string(67) "WHERE foo = ? AND bar BETWEEN ? AND ? AND baz = 42 AND bof IS NOT ?"
array(4) {
[0]=>
string(6) "fooval"
[1]=>
string(5) "start"
[2]=>
string(3) "end"
[3]=>
string(6) "notbof"
}
评论
$query
$where
$stmt->execute()
It sounds like what I need to do is separate these values out into an array with multiple indices so I can explicitly use bind params on each value
.是的,基本上。例如,你有这样,你可以添加一个单独的?将其中的每个项目放入字符串中,并将值放在单独的列表中进行绑定。正如 Barmar 所说,使用 PHP 8 或 PDO 要容易得多$_POST["${value}_list"]