PHP路由对象:将GET路由与参数匹配的最佳方法

时间:2015-01-05 14:01:13

标签: php regex routing routes

我面临的情况是我不知道如何实现一个功能, 我不确定什么是最好和更快的解决方案。

我有一个简单的路由对象,非常基本,我不需要这个特定项目的高级功能......它存储了一系列路由,唯一允许的方法是GET和POST,这大致是类结构:

class Router
{
    // Array of Route Objects
    private static $binded_routes = array();

    // Method used to register a GET route.
    public static function get() {}

    // Method used to register a POST route.
    public static function post() {}

    // Other methods here like redirect(), routeTo(), dispatch()
}

路由可以声明为:

Router::get('index', 'IndexController@method');
Router::get('users/{id}', 'UserController@showUser');
Router::get('route/to/something', 'Controller@method');
Router::get('route/to/something/{param1}', 'Controller@method1');
Router::get('route/to/something/{param1}/{param2}', 'Controller@method2');

存储GET路由的策略是:

  1. 仅注册没有params的路由(在此示例中:index,users, 路径/到/东西)
  2. 指定params的地方将它们存储为数组
  3. 不要使用相同数量的参数存储多个GET路由(在此示例中声明'users / {test}'将引发错误)
  4. 路线对象是这样的:

    class Route
    {
        private $route_type = 'GET';
        private $route_name = null;
        private $route_uri = null;
        private $route_params = array();
        private $route_controller = null;
        private $route_method = null;
    
        // Functions to correctly store and retrieve the above values
    }
    

    所以现在我在匹配GET请求方面遇到了麻烦,基于我可以做的事情 那样:

    1. 遍历所有绑定路线。找到完全匹配并在找到时停止 - >因此,如果用户转到'route / to / something',我可以匹配第三条路线并将执行传递给正确的控制器。
    2. 如果找不到,请尽可能多地匹配路线并将其余路线作为参数 - >因此,如果用户转到'route / to / something / 1/2',我可以匹配'route / to / something'并将数组(1,2)作为参数
    3. 现在我可以简单地计算参数的数量,并与路线进行比较,以找到唯一具有相同数量的参数的路径。
    4. 目前,如果没有多个foreach循环,我无法想到管理此过程的方法。 对此最好的方法是什么?有没有办法构建正则表达式?以及如何生成它?

      任何帮助都将受到高度赞赏,如果您需要更多信息,请告诉我。

1 个答案:

答案 0 :(得分:2)

经过一些编码后,我设法创建了一个工作函数,棘手的部分是将GET请求与params匹配。

例如,如果我有这些路线:

Router::get('user/{id}', 'UserController@showUser');
Router::get('route/path/{param1}', 'SomeController@someMethodA');
Router::get('route/path/{param1}/{param2}', 'SomeController@someMethodB');

用户可以通过浏览器发出如下请求:

site.com/user/10
site.com/route/path/10
site.com/route/path/10/20

知道这一点,我的脚本必须通过以下方式识别(遵循如何解析GET请求的策略)所请求的URI:

route1: user
params: array(10)

route2: route/path
params: array(10)

route3: route/path
params: array(10,20)

以下是代码的相关部分:

$index = 0;
$array_of_matches = array();

// $current_uri is urldecoded route path
$splitted_uri = explode('/', $current_uri);

foreach (self::$binded_routes as $route) 
{
    if ($route->getURI() === $current_uri && !$route->hasParams()) 
    {
        // Gotcha.
        $found_route = true;
        $route_index = $index;

        // No need to continue wasting time...
        break;
    }

    $number_of_matches = 0;
    $route_uri_split = explode('/', $route->getURI());

    if ($splitted_uri[0] == $route_uri_split[0] && $route->hasParams()) 
    {
        $number_of_matches++;

        // I need this to eliminate routes like
        // users/list when searching for users/{1}
        if (count($route_uri_split) > count($splitted_uri)) 
        {
            $number_of_matches = 0;
        }

        for($i = 1; $i < count($splitted_uri); $i++) 
        {
            if (isset($route_uri_split[$i])) 
            {
                if ($route_uri_split[$i] === $splitted_uri[$i])
                    $number_of_matches++;
                else
                    $number_of_matches--;
            }
        }
        $array_of_matches[$index] = $number_of_matches;
    }

    // Incrementing index for next array entry.
    $index ++;
}

// Now try to find the route with the same amount of params if I still don't have a match.
if (!$found_route) 
{
    $highest_matches = array_keys($array_of_matches, max($array_of_matches));
    foreach ($highest_matches as $match) 
    {
        $matched_route = self::$binded_routes[$match];
        $params_portion = ltrim(str_replace($matched_route->getURI(), '', $current_uri), '/');

        // If $params_portion is empty it means that no params are passed.
        $params_count = (empty($params_portion)) ? 0 : count(explode('/', $params_portion));

        if ($params_count == $matched_route->paramsCount()) 
        {
            $found_route = true;
            $route_index = $match;
            $route_params = explode('/', $params_portion);

            break;
        }
    }
}

if ($found_route) 
{    
    // If params are needed set them now.
    if (isset($route_params))
        self::$binded_routes[$route_index]->setParams($route_params);

    // Dispatch the route.
    self::$binded_routes[$route_index]->dispatch();
}
else 
{
    // Route not found... redirect to 404 or error.
}

现在,我知道它看起来很丑陋,我想在可能的情况下改进这个代码。 除了将代码提取到自己的类上下文,委托并使其更“甜蜜”,也许它可以更快,更高效或更聪明。

如果您有任何想法,请告诉我。