如何在 PHP 8 中修复这个动态 SQL 查询功能?
Posted
技术标签:
【中文标题】如何在 PHP 8 中修复这个动态 SQL 查询功能?【英文标题】:How to fix this dynamic SQL query function in PHP 8? 【发布时间】:2021-07-03 06:43:34 【问题描述】:在我的旧项目中,我在进行查询时使用了一个函数来“缩短”我的代码。
而不是使用通常的方法
$conn = [...]
$stmt = $conn->prepare(...)
$stmt->bind_param(...)
$stmt->execute();
$stmt->close();
$conn->close();
我得到了一个函数来做这件事,叫做dynamic_db_reader($mysqli, $param, $qry)
。
它返回一个数组(或 null),如:$array[index]['column_name'] = value
或者至少,它在以前的版本中是这样做的。 (在 php 7.4.16 中工作)
这是我的函数的代码:
/**
* Dynamically executes a given sql statement as prepared statement (?-placeholder).
* Expects correct parameters as an array to replace ?.
* Returns an array with ($arr[index]['column_name'] = value), or null.
*
* @param $ms mysqli
* @param $params array
* @param $qry string
* @return array|null
*/
function dynamic_db_reader($ms, $params, $qry)
$fields = array();
$results = array();
// Replace prefix (DBPREF in: inc/config.php)
if (strpos($qry, 'prefix_') !== false)
$qry = str_replace('prefix', DBPREF, $qry);
// Set charset
mysqli_set_charset($ms, 'utf8mb4');
if ($stmt = $ms->prepare($qry))
// Dynamically bind parameters from $params
if (!isset($params) || !empty($params))
// Parameters are set
$types = '';
foreach($params as $param)
// Set parameter data type
if (is_string($param))
$types .= 's'; // Strings
else if (is_int($param))
$types .= 'i'; // Integer
else if (is_float($param))
$types .= 'd'; // Double/Float
else
$types .= 'b'; // Default: Blob and unknown types
$bind_names[] = $types;
for ($i = 0; $i < count($params); $i++)
$bind_name = 'bind' . $i;
$$bind_name = $params[$i];
$bind_names[] = &$$bind_name;
call_user_func_array(array($stmt, 'bind_param'), $bind_names);
$stmt->execute();
$meta = $stmt->result_metadata();
// Dynamically create an array to bind the results to
while ($field = $meta->fetch_field())
$var = $field->name;
$$var = null;
$fields[$var] = &$$var;
// Bind results
call_user_func_array(array($stmt, 'bind_result'), $fields); // --> Error :(
// Fetch results
$i = 0;
while ($stmt->fetch())
$results[$i] = array();
foreach($fields as $k => $v)
$results[$i][$k] = $v;
$i++;
// Close statement
$stmt->close();
if (sizeof($results) > 0)
return $results;
return NULL;
错误:
Fatal error: Uncaught ArgumentCountError: mysqli_stmt::bind_result() does not accept unknown named parameters in [...]\inc\db.php:87
Stack trace:
#0 [...]\root\inc\db.php(87): mysqli_stmt->bind_result(data_key: NULL, data_value: NULL)
#1 [...]\root\inc\func\common.php(76): dynamic_db_reader(Object(mysqli), Array, 'SELECT * FROM v...')
#2 [...]\root\www\index.php(22): getTestArray()
#3 main
thrown in [...]\root\inc\db.php on line 87
如何修复此代码,使其在 PHP 8 中也能正常工作?
【问题讨论】:
【参考方案1】:对于如此简单的事情来说,这是一个非常长的方法。 PHP 8 添加了命名参数。当您解压缩要用作参数的数组时,其键将用作参数名称。 mysqli_stmt::bind_result()
不接受您传递的命名参数。
如果我们简化这段代码,那么它应该看起来像这样:
/**
* Dynamically executes a given sql statement as prepared statement (?-placeholder).
* Expects correct parameters as an array to replace ?.
* Returns an array with ($arr[index]['column_name'] = value), or null.
*/
function dynamic_db_reader(mysqli $ms, array $params, string $qry): ?array
// Replace prefix (DBPREF in: inc/config.php)
if (strpos($qry, 'prefix_') !== false)
$qry = str_replace('prefix', DBPREF, $qry);
$stmt = $ms->prepare($qry);
// Dynamically bind parameters from $params
if ($params)
$stmt->bind_param(str_repeat('s', count($params)), ...$params);
$stmt->execute();
return $stmt->get_result()->fetch_all(MYSQLI_ASSOC) ?: null;
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'password', 'test');
$mysqli->set_charset('utf8mb4');
$results = dynamic_db_reader($mysqli, ['foo'], 'SELECT ?');
如果由于某种原因您使用的是从 libmysql 客户端编译的 mysqli,那么……是时候弄清楚如何启用 mysqlnd 或切换到 PDO。
附:请确保您已启用 mysqli 错误报告。 How to get the error message in MySQLi?。此外,每次都设置字符集是没有意义的。建立连接后立即设置。
【讨论】:
【参考方案2】:我看到的情况是 call_user_func_array
被传递 $fields
作为 ['array_key_txt'=>'array_value']
导致错误。
尝试将您的 $fields
数组包装在“array_values”中
call_user_func_array(array($stmt, 'bind_result'), array_values($fields));
更新:here 网站解释了 PHP8 中新的“命名参数”
它指出:
str_replace(time_limit: "mi");
会产生错误:
致命错误:未捕获错误:未知命名参数 $time_limit in...
https:php.watch 说的:
所有 call_user_func_array() 函数调用都必须注意 PHP 8.0 将关联数组和数值数组解释不同。
作为预防措施, call_user_func_array() 调用可以使用 如果参数数组可能包含非数字键,则为 array_values。
$params = [
'replace' => 'Iron Man',
'search' => 'Inevitable',
'subject' => 'I am Inevitable', ];
echo call_user_func_array('str_replace', array_values($params));
使用 array_values 调用,PHP 将始终使用位置调用 模式,确保 PHP 8.0 及更高版本中的结果相同。
【讨论】:
以上是关于如何在 PHP 8 中修复这个动态 SQL 查询功能?的主要内容,如果未能解决你的问题,请参考以下文章
如何修复这个内连接 SQL 查询?而不是 1 count() 返回 3
如何在 PHP 中动态绑定 mysqli bind_param 参数?