我不擅长正则表达式。 我试着换一个:
public static function camelize($word) {
return preg_replace('/(^|_)([a-z])/e', 'strtoupper("\\2")', $word);
}
使用带有匿名函数的preg_replace_callback。 我不明白\\2在做什么。 或者说,preg_replace_callback是如何工作的。
实现这一目标的正确代码是什么?
在正则表达式中,可以用(括号)
“捕获”匹配字符串的部分; 在本例中,您捕获的是匹配的(^_)
和([a-z])
部分。 它们的编号是从1开始的,因此您有反向引用1和2。 匹配0是整个匹配字符串。
/e
修饰符接受一个替换字符串,并用适当的反向引用替换后跟数字(例如\1
)的反斜杠--但是因为您在字符串中,您需要转义反斜杠,所以您得到了'\\1'
。 然后它(有效地)运行eval
来运行结果字符串,就像它是PHP代码一样(这就是为什么不推荐使用它,因为很容易以不安全的方式使用eval
)。
preg_replace_callback
函数取而代之的是一个回调函数,并向它传递一个包含匹配的后向引用的数组。 因此,在编写'\\1'
的地方,您将访问该参数的元素1-例如,如果您有一个形式为function($matches){。。。}
的匿名函数,则第一个反向引用是该函数中的$matches[1]
。
因此/e
参数
'do_stuff(\\1) . "and" . do_stuff(\\2)'
可能成为
function($m) { return do_stuff($m[1]) . "and" . do_stuff($m[2]); }
或者在你的情况下
'strtoupper("\\2")'
可能成为
function($m) { return strtoupper($m[2]); }
请注意,$m
和$matches
不是魔术名称,它们只是我在声明回调函数时给出的参数名称。 另外,您不必传递匿名函数,它可以是一个字符串形式的函数名,也可以是数组($object,$method)
形式的东西,就像PHP中的任何回调一样,例如。
function stuffy_callback($things) {
return do_stuff($things[1]) . "and" . do_stuff($things[2]);
}
$foo = preg_replace_callback('/([a-z]+) and ([a-z]+)/', 'stuffy_callback', 'fish and chips');
与任何函数一样,默认情况下,您不能访问回调之外的变量(从周围的作用域)。 使用匿名函数时,可以使用use
关键字导入需要访问的变量,如PHP手册中所述。 例如,如果以前的论点是
'do_stuff(\\1, $foo)'
则新的回调可能如下所示
function($m) use ($foo) { return do_stuff($m[1], $foo); }
preg_replace_callback
代替了regex上的/e
修饰符,因此需要从“pattern”参数中删除该标志。 所以像/blah(.*)blah/mei
这样的模式将变成/blah(.*)blah/mi
./e
修饰符在参数上内部使用了addslashs()
的变体,因此一些替换使用stripslashs()
来删除它; 在大多数情况下,您可能希望从新回调中删除对stripslashes
的调用。
这是非常不可取的。 但是,如果您不是程序员,或者确实喜欢糟糕的代码,您可以使用替代的preg_replace
函数来暂时保持/e
标志的工作。
/**
* Can be used as a stopgap shim for preg_replace() calls with /e flag.
* Is likely to fail for more complex string munging expressions. And
* very obviously won't help with local-scope variable expressions.
*
* @license: CC-BY-*.*-comment-must-be-retained
* @security: Provides `eval` support for replacement patterns. Which
* poses troubles for user-supplied input when paired with overly
* generic placeholders. This variant is only slightly stricter than
* the C implementation, but still susceptible to varexpression, quote
* breakouts and mundane exploits from unquoted capture placeholders.
* @url: https://stackoverflow.com/q/15454220
*/
function preg_replace_eval($pattern, $replacement, $subject, $limit=-1) {
# strip /e flag
$pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
# warn about most blatant misuses at least
if (preg_match('/\(\.[+*]/', $pattern)) {
trigger_error("preg_replace_eval(): regex contains (.*) or (.+) placeholders, which easily causes security issues for unconstrained/user input in the replacement expression. Transform your code to use preg_replace_callback() with a sane replacement callback!");
}
# run preg_replace with eval-callback
return preg_replace_callback(
$pattern,
function ($matches) use ($replacement) {
# substitute $1/$2/… with literals from $matches[]
$repl = preg_replace_callback(
'/(?<!\\\\)(?:[$]|\\\\)(\d+)/',
function ($m) use ($matches) {
if (!isset($matches[$m[1]])) { trigger_error("No capture group for '$m[0]' eval placeholder"); }
return addcslashes($matches[$m[1]], '\"\'\`\$\\\0'); # additionally escapes '$' and backticks
},
$replacement
);
# run the replacement expression
return eval("return $repl;");
},
$subject,
$limit
);
}
实质上,您只需将该函数包含在代码库中,并在使用/e
标志的地方将preg_replace
编辑为preg_replace_eval
。
利弊:
PREG_REPLACE_CALLBACK
的正确转换复杂化。现在这有点多余了。 但可能会帮助那些仍然忙于手动将代码重组为preg_replace_callback
的用户。 虽然这实际上更耗时,但代码生成器将/e
替换字符串展开为表达式的麻烦较小。 这是一个非常不起眼的转换,但可能已经足够为最普遍的例子。
若要使用此函数,请编辑PREG_REPLACCE_EVAL_REPLACCE
中任何中断的PREG_REPLACCE
调用,并运行一次。 这将打印出相应的preg_replace_callback
块,用于替换它。
/**
* Use once to generate a crude preg_replace_callback() substitution. Might often
* require additional changes in the `return …;` expression. You'll also have to
* refit the variable names for input/output obviously.
*
* >>> preg_replace_eval_replacement("/\w+/", 'strtopupper("$1")', $ignored);
*/
function preg_replace_eval_replacement($pattern, $replacement, $subjectvar="IGNORED") {
$pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
$replacement = preg_replace_callback('/[\'\"]?(?<!\\\\)(?:[$]|\\\\)(\d+)[\'\"]?/', function ($m) { return "\$m[{$m[1]}]"; }, $replacement);
$ve = "var_export";
$bt = debug_backtrace(0, 1)[0];
print "<pre><code>
#----------------------------------------------------
# replace preg_*() call in '$bt[file]' line $bt[line] with:
#----------------------------------------------------
\$OUTPUT_VAR = preg_replace_callback(
{$ve($pattern, TRUE)},
function (\$m) {
return {$replacement};
},
\$YOUR_INPUT_VARIABLE_GOES_HERE
)
#----------------------------------------------------
</code></pre>\n";
}
请记住,仅仅复制和粘贴并不是编程。 您必须使生成的代码适应实际的输入/输出变量名或使用上下文。
if
中使用了前一个preg_replace
调用,则必须执行$output=
赋值。替换表达式可能需要更多的可读性改进或返工。
stripslashs()
在文字表达式中经常变得多余。use
或global
引用。“-$1-$2”
捕获引用最终将被简单转换为“-$m[1]-$m[2]
而在语法上中断。代码输出仅仅是一个起点。 是的,这将是更有用的在线工具。 这种代码重写方法(编辑,运行,编辑,编辑)有点不切实际。 但是对于那些习惯于以任务为中心的编码(更多的步骤,更多的发现)的人来说,它更容易接近。 因此,这个替代方案可能会减少一些重复的问题。
您不应该使用标志e
(或者通常使用eval
)。
还可以使用T-Regx库
pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');