【问题标题】:PHP Routing object: best way to match a GET route with parametersPHP 路由对象:将 GET 路由与参数匹配的最佳方法
【发布时间】:2015-01-05 14:01:13
【问题描述】:

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

我有一个简单的 Routing 对象,非常基本,我不需要这个特定项目的高级功能......它存储了一组路由,唯一允许的方法是 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. 只注册不带参数的路由(在本例中:索引、用户、 路线/到/某事)
  2. 在指定参数的地方将它们存储为数组
  3. 不要使用相同数量的参数存储多个 GET 路由(在此示例中声明 'users/{test}' 将引发错误)

路由对象是这样的:

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. 现在我可以简单地计算参数的数量并与路线进行比较以找到唯一具有相同数量的参数的路线。

目前我想不出一种方法来管理这个过程而无需多个 foreach 循环。 什么是最好的方法?有没有办法构造一个正则表达式?以及如何生成?

我们将不胜感激任何帮助,如果您需要更多信息,请告诉我。

【问题讨论】:

    标签: php regex routing routes


    【解决方案1】:

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

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

    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.
    }
    

    现在,我知道它看起来很丑,我想尽可能改进这段代码。 除了将代码提取到自己的类上下文中、委托并使其更“甜”之外,也许它可以变得更快、更高效或更智能。

    如果你有什么想法,请告诉我。

    【讨论】:

      猜你喜欢
      • 2015-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-26
      • 2016-05-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多