I've already written a PHP controller, but I'm rewriting my code so I can have Backbone Router-like JSON routes that map URI patterns to PHP Class::method combinations, or directly to HTML documents which are then delivered to the client, like so:
{
"/home" : "index.html",
"/podcasts": "podcasts.html",
"/podcasts/:param1/:param2": "SomeClass::someMethod"
}
Backbone dynamically creates regexes to match routes against URLs. I took a look into the backbone code, and I extracted the following code (it's a bit modified):
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 + '$');
}
When I pass a route like /podcasts/:param1/:param2
, to the code above, I get /^\/podcasts\/([^\/]+)\/([^\/]+)$/
. I was trying to write a PHP function to get exactly that same regex. I tried:
$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);
My output is:
/^\/podcasts\/([^\/]+)\/([^\/]+)$/ // The expected pattern
/^/podcasts/([^\/]+)/([^\/]+)$/ // echo "{$pattern}\n";
Array // print_r($matches);
(
)
Why is my regex output different? I can handle the rest of the mapping process and all, but I haven't figured out how to obtain exactly the same regex in PHP as in Javascript. Any suggestions?
The JS version does not in fact escape the / chars, it produces the same string representation as your PHP version /podcasts/([^/]+)/([^/]+)
but, and that's probably what tripped you, a console.log
of the regexp returned by _routeToRegExp
shows the full representation with the delimiting / and the inner ones escaped :
//Firefox output
RegExp /^\/podcasts\/([^\/]+)\/([^\/]+)$/
See http://jsfiddle.net/w6zP2/ for a demo.
What @ajshort noted in the comments and what Backbone does are the same solution : skip the / escaping and use an alternative syntax; Backbone does it with an explicit call to the Regexp constructor with new RegExp('^' + route + '$')
, you can do something similar with $pattern = "~^{$d}$~";
And a PHP Fiddle http://phpfiddle.org/main/code/4de-jf3 that seems to work as expected.
Note that in Javascript $&
in a replacement string inserts the matched substring1, not so in PHP2. The escapeRegExp should be modified and might look like :
$a = preg_replace('/[\-{}\[\]+?.,\\\^$|#~\s]/', '\\\\$0', $route);
which gives us an updated code:
$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);
1 See Specifying a string as a parameter in string.replace
2 See replacement in preg_replace
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With