PHP路由器,从uri中提取密钥

时间:2009-07-02 10:09:47

标签: php router

嘿伙计们!我需要一些正则表达式等帮助。我需要从url,应用程序中的某种路由器中提取虚拟密钥。以下是参数:

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

输出应该是这样的:

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

所以,问题是:我怎么能在PHP中这样做?

P.S规则的组合可能有所不同。因此,主键是带有':'符号的单词。例如:

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

P.S。 2:这是我以前的一段代码。它有效但不灵活。

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]);
        }   
    }

事实上这个版本的路由器对我来说已经足够了,但我只是对如何制作灵活的解决方案感兴趣。顺便说一下,一些测试:(10000次操作的30次):

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

所以,它足够快。我正常测试笔记本电脑。所以,当然 - 这个可以用在真实的网站上。

还有其他解决方案吗?

5 个答案:

答案 0 :(得分:1)

我认为这只有一个正则表达式是不可能的。 Zend Framework就像你的例子一样工作。看看他们的source code

答案 1 :(得分:1)

尝试这个简单的解决方案:

    $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('/:([a-z]+)/', '(?P<\1>[^/]+)', $rule);
            $reRule = str_replace('/', '\/', $reRule);

            preg_match('/' . $reRule .'/', $uri, $matches);
            print_r($matches);
    }

唯一的缺点是,此时您无法进行花式数据验证,因此您必须在其他地方进行此操作。如果规则与正则表达式语法冲突,它可能会变得混乱(你必须在这里做一些重要的逃避工作)。

答案 2 :(得分:1)

答案 3 :(得分:0)

我首先要为每个元素定义一些模式

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

然后建立一个正则表达式

$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);

请注意,您可以在目标数据上使用该模式,并且您获得的匹配数组应与$ map数组中的元素名称相对应 - 例如name $ match [1]在$ map [1]等中。

答案 4 :(得分:0)

$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"=>"([a-z]+)"));

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('/:([a-z]+)/',"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;
        }
    }
}