在PHP中使用动态正则表达式的类骨干路由器

Ósc*_*ios 6 php regex router backbone.js

我已经编写了一个PHP控制器,但我正在重写我的代码,所以我可以使用类似Backbone路由器的JSON路由将URI模式映射到PHP Class ::方法组合,或直接转换为HTML文档然后传递给客户端,像这样:

{
  "/home" : "index.html",
  "/podcasts": "podcasts.html",
  "/podcasts/:param1/:param2": "SomeClass::someMethod"
}
Run Code Online (Sandbox Code Playgroud)

Backbone动态创建正则表达式以匹配URL的路由.我查看了主干代码,并提取了以下代码(它有点修改):

function _routeToRegExp (route) {
  var optionalParam = /\((.*?)\)/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
  route = route.replace(escapeRegExp, '\\$&')
               .replace(optionalParam, '(?:$1)?')
               .replace(namedParam, function(match, optional){
                 return optional ? match : '([^\/]+)';
               })
              .replace(splatParam, '(.*?)');
  return new RegExp('^' + route + '$');
}
Run Code Online (Sandbox Code Playgroud)

当我通过/podcasts/:param1/:param2上面的代码之类的路线时,我得到了/^\/podcasts\/([^\/]+)\/([^\/]+)$/.我试图编写一个PHP函数来获得完全相同的正则表达式.我试过了:

$route = '/podcasts/:param1/:param2';
$a = preg_replace('/[\-{}\[\]+?.,\\\^$|#\s]/', '\\$&', $route); // escapeRegExp
$b = preg_replace('/\((.*?)\)/', '(?:$1)?', $a);                // optionalParam
$c = preg_replace('/(\(\?)?:\w+/', '([^\/]+)', $b);             // namedParam
$d = preg_replace('/\*\w+/', '(.*?)', $c);                      // splatParam
$pattern = "/^{$d}$/";
echo "/^\/podcasts\/([^\/]+)\/([^\/]+)$/\n";
echo "{$pattern}\n";
$matches = array();
preg_match_all($pattern, '/podcasts/param1/param2', $matches);
print_r($matches);
Run Code Online (Sandbox Code Playgroud)

我的输出是:

/^\/podcasts\/([^\/]+)\/([^\/]+)$/    // The expected pattern
/^/podcasts/([^\/]+)/([^\/]+)$/       // echo "{$pattern}\n";
Array                                 // print_r($matches);
(
)
Run Code Online (Sandbox Code Playgroud)

为什么我的正则表达式输出不同?我可以处理映射过程的其余部分,但是我还没有弄清楚如何在PHP中获得与Javascript完全相同的正则表达式.有什么建议?

nik*_*shr 2

JS 版本实际上并没有转义 / 字符,它生成与 PHP 版本相同的字符串表示形式 /podcasts/([^/]+)/([^/]+),但是,这可能是让您绊倒的原因, aconsole.log返回的正则表达式 _routeToRegExp显示了带有分隔 / 的完整表示形式,并且内部转义了:

//Firefox output
RegExp /^\/podcasts\/([^\/]+)\/([^\/]+)$/ 
Run Code Online (Sandbox Code Playgroud)

请参阅http://jsfiddle.net/w6zP2/进行演示。

@ajshort 在评论中指出的和 Backbone 所做的是相同的解决方案:跳过 / 转义并使用替代语法;Backbone 通过显式调用 Regexp 构造函数来完成此操作new RegExp('^' + route + '$'),您可以执行类似的操作$pattern = "~^{$d}$~";

PHP Fiddle http://phpfiddle.org/main/code/4de-jf3似乎按预期工作。

请注意,在 Javascript 中$&,替换字符串会插入匹配的子字符串1 ,而在 PHP 2中则不然。escapeRegExp 应该被修改并且可能看起来像:

$a = preg_replace('/[\-{}\[\]+?.,\\\^$|#~\s]/', '\\\\$0', $route);
Run Code Online (Sandbox Code Playgroud)

这给了我们更新的代码:

$a = preg_replace('/[\-{}\[\]+?.,\\\^$|#~\s]/', '\\\\$0', $route); // escapeRegExp
$b = preg_replace('/\((.*?)\)/', '(?:$1)?', $a);                // optionalParam
$c = preg_replace('/(\(\?)?:\w+/', '([^\/]+)', $b);             // namedParam
$d = preg_replace('/\*\w+/', '(.*?)', $c);                      // splatParam
$pattern = "~^{$d}$~";

echo "/^\/podcasts\/([^\/]+)\/([^\/]+)$/\n";
echo "{$pattern}\n";
$matches = array();
preg_match_all($pattern, '/podcasts/param1/param2', $matches);
print_r($matches);
Run Code Online (Sandbox Code Playgroud)

1请参阅在string.replace中指定字符串作为参数2请参阅preg_replace中的替换