环境:PHP 5.3
我正在尝试编写自己的查询参数替换方法。基本上我想采取这个:
select * from xxx where a=? and b>?
并将其转换为
select * from xxx where a=1 and b>2
当然,假设所有?
参数的值都是已知的。好的,这有点简化了,但对于这个问题来说已经足够了。
所以,我需要做的是找到给定字符串中的所有?
标记。很简单,对吧?但有一个问题:我不想找到字符串或注释中的标记。因此,在这个字符串中:
select * -- I know * is bad, but just once can't hurt, right?
from xxx /* ? */ where a=? and b='Question?'
只应更换其中一个?
标记。
我的直觉告诉我PHP的preg_replace()
应该能够胜任任务。。。但是我的正则表达式知识使我无法构建适当的模式(我也可以"手动"解析它,但我担心性能会受到不适当的影响。
那么,这可以通过正则表达式快速完成吗(如果可以,模式是什么),还是应该逐个字符手动解析?
您可以尝试先删除注释中的所有问号并记住它们,然后在查询中放置占位符,然后通过preg_replace()解析查询,然后在注释中有占位符的地方插入问号。我指的是之类的东西
$matches = array();
preg_match_all('/'/'*.*?.*'*'//U', $query, $matches);
preg_replace('/'/'*.*?.*'*'//U', $arrayWithIndicesOfParameters, $query);
preg_replace(/*your replacement of parameters*/);
preg_replace($arrayWithIndicesOfParameters, $matches, $query); //str_replace should be sufficient here
这对正则表达式来说是个难题。解析器会更合适,但只要满足某些约束,正则表达式就可能正常工作。限制条件是:
- 没有嵌套注释
- 字符串中没有转义引号
- 评论中没有单引号
- 字符串中没有注释分隔符
- 所有引号和注释都正确平衡
- 您可以指定最大线路长度
如果是这样的话,那么你可以简单地寻找一个?
,它是
- 不在CCD_ 6之前(在同一行上)
- 不跟在
*/
之后,除非首先出现/*
并且 - 后跟偶数个引号
假设最大线路长度为100,则会得到
$result = preg_replace(
'%(?<!--.{0,100}) # Assert no -- preceding on this line
'? # Match a ?
(?! # Assert that it''s impossible to match...
(?s: # (allowing the dot to match newlines here):
(?!/'*) # (and making sure there is no intervening /*)
. # any character
)* # zero or more times, if that string is followed by
'*/ # */
) # End of lookahead
(?= # Assert that it *is* possible to match
(?: # the following regex:
[^'']*''[^'']*'' # a string containing exactly two quotes
)* # repeated zero or more times
[^'']* # followed by only non-quote characters
$ # until the end of the string.
) # End of lookahead.
%x',
'REPLACE', $subject);
其他人可能有一个更优雅的解决方案,但我的第一直觉是使用regex完全去除注释,然后进行模式匹配参数。
$expressions = array(
"#/'*(.*)'*/#",
"#[-]{2}(.*)''n#",
);
$query = preg_replace($expressions, "", $query);
我可能会这样做:
- 为每个字符串语法和注释语法找到一个正则表达式
- 将它们组合以匹配字符串语法、注释语法或"其他任何语法"
- 对每个"其他任何"零件进行更换
下面是一个示例实现:
$escapeSequence = "(?:''''[0''"bnrtZ''''%_])";
$singleQuoted = "'(?:[^''''']|{$escapeSequence}|'')*'";
$doubleQuoted = "'"(?:[^'"'''']|{$escapeSequence}|'"'")*'"";
$string = "(?:{$singleQuoted}|{$doubleQuoted})";
$lineEndComment = "(?:#[^''r''n]*|--''s[^''r''n]*)";
$multiLineComment = "(?:'/''*(?:.|[''r''n])*?''*'/)";
$comment = "(?:{$lineEndComment}|{$multiLineComment})";
$pattern = "/({$string}|{$comment})/";
$parts = preg_split($pattern, $query, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i=0, $n=count($parts)-1; $i<$n; $i+=2) {
$part = $parts[$i]; // non-string, non-comment part
// perform replacement of ?
}
$query = implode('', $parts);
这个模式可能不完整,但应该足以让人产生想法。