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


PHP Routing object: best way to match a GET route with parameters

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

有一个简单的路由对象,非常基本,我不需要这个特定项目的高级功能......它存储一个路由数组,唯一允许的方法是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. 遍历所有绑定的路由。 找到完全匹配项,如果找到,则停止。
    -> 因此,如果用户转到"路由/到/某物",我可以匹配第三条路由并将执行传递给正确的控制器。
  2. 如果未找到,请尽可能多地匹配路由,并将其余路径作为参数。
    -> 因此,如果用户转到"route/to/something/1/2",我可以匹配"route/to/something"并将array(1,2)作为参数
  3. 现在我可以简单地计算参数的数量,并与路由进行比较,以找到唯一具有相同参数数量的路由。

目前,我想不出一种在没有多个foreach循环的情况下管理此过程的方法。最好的方法是什么?有没有办法构建正则表达式?以及如何生成它?

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

经过一些编码,我设法创建了一个工作函数,棘手的部分是将 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.
}

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

如果你有一些想法,请告诉我。