使用可选参数实现 PHP 路由器

Implementing a PHP Router with Optional Parameters

提问人:Omar Abdulwahhab 提问时间:10/19/2023 更新时间:10/19/2023 访问量:33

问:

我一直在努力实现一个简单的PHP路由器,并设法使它使用所需的参数。但是,我正在努力实现可选参数,但我失败了。为了清楚起见,我将提供相关代码。

首先,这是我的索引.php:

<?php
$app->route->get("/user/:id/?post_id", [SiteController::class, "contact"]);
?>

在上面的代码中,我使用冒号 (:id) 作为必需参数,使用问号 (?post_id) 作为可选参数。

其次,这是我的 Router 类:

class Router {
    public function resolve() {
        $method = $this->request->getMethod();
        $url = $this->request->getUrl();
        foreach ($this->router[$method] as $routeUrl => $target) {
            $pattern = preg_replace('/\/:([^\/]+)/', '/(?P<$1>[^/]+)', $routeUrl);
            if (preg_match('#^' . $pattern . '$#', $url, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
                call_user_func([new $target[0], $target[1]], ...array_values($params));
                exit();
            }
        }
        throw new \Exception();
    }
}

我需要帮助来解决实现可选参数的谜团。任何帮助将不胜感激。谢谢!

php 正则表达式 预匹配

评论

1赞 KIKO Software 10/19/2023
请仔细阅读如何创建一个最小的、可重现的示例,因为不清楚你的两小段代码是如何结合在一起的。此外,如果您使用的是某种平台,那么提及它会很方便。简而言之:不要以为我们知道一切。

答:

0赞 ThW 11/21/2023 #1

下面是一个函数,用于将路由路径模式编译为具有命名捕获组的正则表达式。它使用回调函数根据指标字符返回不同的模式。

function compileRoutePattern(string $route): string {
    // replace parameter syntax with named capture groups
    return '(^'.preg_replace_callback(
      '((?<prefix>^|/)(?:(?<indicator>[:?])(?<name>[\w\d]+)|[^/]+))',
      function($match) {
          return match ($match['indicator'] ?? '') {
               // mandatory parameter 
              ':' => sprintf('(?:%s(?<%s>[^/]+))', $match['prefix'], $match['name']),
               // optional parameter 
              '?' => sprintf('(?:%s(?<%s>[^/]+))?', $match['prefix'], $match['name']),
              // escape anything else
              default => preg_quote($match[0], '(')
          };
      },
      $route
    ).')i';
}

下一步是匹配路径并仅返回命名的捕获组(而不是数字键)。

function matchRoute(string $path, string $route): ?array {
    // compile route into regex with named capture groups
    $pattern = compileRoutePattern($route);
    // match
    if (preg_match($pattern, $path, $match)) {
        // filter numeric keys from match
        return array_filter(
            $match, 
            fn($key) => is_string($key),
            ARRAY_FILTER_USE_KEY
        );   
    }
    return null;
}

提示:我使用模式分隔符来避免冲突。将它们视为组 0。()