在 PHP 中的准备语句中将命名参数转换为未命名
Convert named parameters to unnamed in prepared statement in PHP
我有一个 SQL 使用命名占位符的查询,以及一个包含相应键和值的关联数组。
INSERT INTO table1 (a, b, c) VALUES (:a, :b, :c)
[
'a' => 'ValueA',
'c' => 'ValueC',
'b' => 'ValueB'
]
现在我需要将查询和相应的参数转换为使用未命名(问号)。
INSERT INTO table1 (a, b, c) VALUES (?, ?, ?)
[
0 => 'ValueA',
1 => 'ValueB',
2 => 'ValueC'
]
我该怎么做才能避免参数顺序错误?
我想到了下面的函数,其中考虑到了
- 部分匹配占位符名称,即
:b
和 :bar
- 参数在 SQL 与参数数组
中出现的顺序不同
function sqlNamedPlaceholdersToUnnamed(string &$sql, array &$params){
// Sort params based on the order their keys appear in $sql
$newParams = [];
foreach($params as $key => $value){
$result = preg_match("/:{$key}[^a-zA-Z0-9_]/", $sql, $matches, PREG_OFFSET_CAPTURE);
if( $result !== 1 ) throw new \Exception("Unexpected result from preg_match. Expected 1, but got $result");
$newParams[(int) $matches[0][1]] = $value;
}
ksort($newParams, SORT_NUMERIC);
$newParams = array_values($newParams);
// Replace named parameters with ? in $sql
$newSql = preg_replace("/:[a-zA-Z0-9_]+/", '?', $sql, -1, $count);
if( $newSql === null ) throw new \Exception('Error when executing preg_replace');
if( $count !== count($params) ) throw new \Exception("Number of placeholders not same as number of params");
// Replace arguments with results
$sql = $newSql;
$params = $newParams;
}
我会利用 preg_replace_callback
并将相应参数的值简单地附加到新数组:
/**
* @return mixed[] An array containing two elements: the modified SQL query (string),
* and the modified params (array).
*/
function unnameSqlParameters(string $sql, array $params): array
{
$newParams = [];
$newSql = preg_replace_callback(
'/:(\w+)/',
static function (array $matches) use ($params, &$newParams): string {
$name = $matches[1];
if (!array_key_exists($name, $params)) {
throw new \RuntimeException("Cannot find parameter value for :{$name}.");
}
$newParams[] = $params[$name];
return '?';
},
$sql
);
return [$newSql, $newParams];
}
用法:
[$sql, $params] = unnameSqlParameters(
'INSERT INTO table1 (a, b, c) VALUES (:a, :b, :c)',
['a' => 'ValueA', 'c' => 'ValueC', 'b' => 'ValueB']
);
请注意,使用的正则表达式也将匹配字符串中的参数(如 :a
)。如果需要,有一些方法可以防止这种情况发生。
我有一个 SQL 使用命名占位符的查询,以及一个包含相应键和值的关联数组。
INSERT INTO table1 (a, b, c) VALUES (:a, :b, :c)
[
'a' => 'ValueA',
'c' => 'ValueC',
'b' => 'ValueB'
]
现在我需要将查询和相应的参数转换为使用未命名(问号)。
INSERT INTO table1 (a, b, c) VALUES (?, ?, ?)
[
0 => 'ValueA',
1 => 'ValueB',
2 => 'ValueC'
]
我该怎么做才能避免参数顺序错误?
我想到了下面的函数,其中考虑到了
- 部分匹配占位符名称,即
:b
和:bar
- 参数在 SQL 与参数数组 中出现的顺序不同
function sqlNamedPlaceholdersToUnnamed(string &$sql, array &$params){
// Sort params based on the order their keys appear in $sql
$newParams = [];
foreach($params as $key => $value){
$result = preg_match("/:{$key}[^a-zA-Z0-9_]/", $sql, $matches, PREG_OFFSET_CAPTURE);
if( $result !== 1 ) throw new \Exception("Unexpected result from preg_match. Expected 1, but got $result");
$newParams[(int) $matches[0][1]] = $value;
}
ksort($newParams, SORT_NUMERIC);
$newParams = array_values($newParams);
// Replace named parameters with ? in $sql
$newSql = preg_replace("/:[a-zA-Z0-9_]+/", '?', $sql, -1, $count);
if( $newSql === null ) throw new \Exception('Error when executing preg_replace');
if( $count !== count($params) ) throw new \Exception("Number of placeholders not same as number of params");
// Replace arguments with results
$sql = $newSql;
$params = $newParams;
}
我会利用 preg_replace_callback
并将相应参数的值简单地附加到新数组:
/**
* @return mixed[] An array containing two elements: the modified SQL query (string),
* and the modified params (array).
*/
function unnameSqlParameters(string $sql, array $params): array
{
$newParams = [];
$newSql = preg_replace_callback(
'/:(\w+)/',
static function (array $matches) use ($params, &$newParams): string {
$name = $matches[1];
if (!array_key_exists($name, $params)) {
throw new \RuntimeException("Cannot find parameter value for :{$name}.");
}
$newParams[] = $params[$name];
return '?';
},
$sql
);
return [$newSql, $newParams];
}
用法:
[$sql, $params] = unnameSqlParameters(
'INSERT INTO table1 (a, b, c) VALUES (:a, :b, :c)',
['a' => 'ValueA', 'c' => 'ValueC', 'b' => 'ValueB']
);
请注意,使用的正则表达式也将匹配字符串中的参数(如 :a
)。如果需要,有一些方法可以防止这种情况发生。