Php router, key extraction from uri

Hey guys! I need help with regular expressions, etc. I need to extract virtual keys from url, some kind of router in the application. Here are the options:

Rule: /books/:category/:id/:keyname Data: /books/php/12345/this-is-a-test-keyname 

The result should be something like this:

 array( 'category' => 'php', 'id' => '12345', 'keyname' => 'this-is-a-test-keyname' ); 

So the question is: how can I do this in php?

PS Rule combinations may vary. So, the main keys are the words with the symbol ":". eg:

 /book-:id/:category/:keyname /book/:id_:category~:keyname 

PS 2: This is a piece of code that I had before. It works, but is not flexible.

 function rule_process($rule, $data) { // extract chunks $ruleItems = explode('/',$rule); $dataItems = explode('/',$data); // remove empty items array_clean(&$ruleItems); array_clean(&$dataItems); // rule and data supposed to have the same structure if (count($ruleItems) == count($dataItems)) { $result = array(); foreach($ruleItems as $ruleKey => $ruleValue) { // check if the chunk is a key if (preg_match('/^:[\w]{1,}$/',$ruleValue)) { // ok, found key, adding data to result $ruleValue = substr($ruleValue,1); $result[$ruleValue] = $dataItems[$ruleKey]; } } if (count($result) > 0) return $result; unset($result); } return false; } function array_clean($array) { foreach($array as $key => $value) { if (strlen($value) == 0) unset($array[$key]); } } 

In fact, this version of the router may be enough for me, but they are just wondering how to make a flexible decision. By the way, some tests: (30 times out of 10,000 operations):

 TEST #0 => Time:0.689285993576, Failures: 0 TEST #1 => Time:0.684408903122, Failures: 0 TEST #2 => Time:0.683394908905, Failures: 0 TEST #3 => Time:0.68522810936, Failures: 0 TEST #4 => Time:0.681587934494, Failures: 0 TEST #5 => Time:0.681943893433, Failures: 0 TEST #6 => Time:0.683794975281, Failures: 0 TEST #7 => Time:0.683885097504, Failures: 0 TEST #8 => Time:0.684013843536, Failures: 0 TEST #9 => Time:0.684071063995, Failures: 0 TEST #10 => Time:0.685361146927, Failures: 0 TEST #11 => Time:0.68728518486, Failures: 0 TEST #12 => Time:0.688632011414, Failures: 0 TEST #13 => Time:0.688556909561, Failures: 0 TEST #14 => Time:0.688539981842, Failures: 0 TEST #15 => Time:0.689876079559, Failures: 0 TEST #16 => Time:0.689854860306, Failures: 0 TEST #17 => Time:0.68727684021, Failures: 0 TEST #18 => Time:0.686210155487, Failures: 0 TEST #19 => Time:0.687953948975, Failures: 0 TEST #20 => Time:0.687957048416, Failures: 0 TEST #21 => Time:0.686664819717, Failures: 0 TEST #22 => Time:0.686244010925, Failures: 0 TEST #23 => Time:0.686643123627, Failures: 0 TEST #24 => Time:0.685017108917, Failures: 0 TEST #25 => Time:0.686363935471, Failures: 0 TEST #26 => Time:0.687278985977, Failures: 0 TEST #27 => Time:0.688650846481, Failures: 0 TEST #28 => Time:0.688835144043, Failures: 0 TEST #29 => Time:0.68886089325, Failures: 0 

So, this is fast enough. Im testing on a regular laptop. So, for sure - this one can be used on a real site.

Any other solutions?

+6
php router
source share
5 answers

I don't think this is possible with just one regex. Zend Framework works just like your example. Look at their source code .

+1
source share

Try this simple solution:

  $data = Array ( "/book/:id/:category/:keyname" => "/book/12345/php/this-is-a-test-keyname", "/book-:id/:category/:keyname" => "/book-12345/php/this-is-a-test-keyname", "/book/:id_:category~:keyname" => "/book/12345_php~this-is-a-test-keyname", ); foreach ($data as $rule => $uri) { $reRule = preg_replace('/:([az]+)/', '(?P<\1>[^/]+)', $rule); $reRule = str_replace('/', '\/', $reRule); preg_match('/' . $reRule .'/', $uri, $matches); print_r($matches); } 

The only drawback is that at the moment you cannot have the correct data validation, so you need to do it elsewhere. Also, it can become messy if the rules conflict with the regular expression syntax (you need to do the hard work of escaping here).

+1
source share
+1
source share

I would start by defining some patterns for each element

 $element=array( 'id'=>'(\d+)', 'category'=>'([^/]+)' ); 

Then create a regex

 $rule="/book-:id/:category/:keyname"; $pattern=preg_quote($rule); $map=array(); $map[]=null; function initrule($matches) { //forgive the globals - quickest way to demonstrate this, in //production code I'd wrap this into a class... global $element; global $map; //remember the order we did these replacements $map[]=$matches[1]; //return the desired pattern return $element[$matches[1]]; } $pattern=preg_replace_callback('/:(\w+)/', "initrule", $pattern); 

Please note that you can use this template for your target data, and the array of matches that you get back must match the names of the elements in the $ map array - for example. name $ match [1] is in $ map [1], etc.

0
source share
 $Url = preg_replace("/^(.*)\\/\\/\/|(.*)\\/*.php\//i", "", $_SERVER['REQUEST_URI']); $Url = str_replace("index.php", "", $Url); $data = array(); $data["/ttt/:xyz/:id"] =(object) Array ( "default" => array("controller"=>"default","action"=>"detail"), "rule" => array("xyz"=>"([a-zA-Z0-9_\+\-%]+)","id"=>"([0-9]+)")); $data["/xid-:id"] =(object) Array ( "default" => array("controller"=>"default","action"=>"categori"), "rule" => array("id"=>"([az]+)")); function parsePath($match) { global $data; foreach($data as $router) { foreach($router->rule as $key=>$value) { if($match[1] == $key){ $str ='(?P<'.$match[1].'>'.$value.')'; } else { $str = '(?P<'.$match[1].'>[^/]+)'; } } } return $str; } foreach($data as $path => $router) { $o=preg_replace_callback('/:([az]+)/',"parsePath", $path); } $regex = str_replace('/', '\/',$o); $regex ='/^' . $regex . '$/'; preg_match($regex, $Url, $matches); $map = array(); foreach($matches as $key => $value) { foreach($data as $route) { if(array_key_exists($key,$route->rule)){ $map['controller'] = $route->default['controller']; $map['action']= $route->default['action']; $map[$key] = $value; } } } 
0
source share

All Articles