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

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

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

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

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. 只注册没有参数的路由(在这个例子中:索引,用户, route/to/something)
  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 循环的方法来管理这个过程。 最好的方法是什么?有没有办法构造正则表达式?以及如何生成它?

非常感谢任何帮助,如果您需要更多信息,请告诉我。

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

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

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