动态路由匹配是php web框架中处理用户请求的核心功能,它能够将不同的URL请求映射到对应的控制器方法,同时提取URL中的动态参数供后续逻辑使用。常见的实现方式包括正则匹配和Trie树匹配两种,下面分别介绍两种方式的实现逻辑和具体代码。

一、基于正则的动态路由匹配实现
正则匹配是最容易上手的路由解析方式,核心思路是先定义一组带参数的路由规则,每条规则对应一个处理函数,当请求URL到来时,依次遍历规则,用正则匹配URL,匹配成功则提取参数并执行对应处理函数。
1. 路由规则定义
我们可以先定义一个路由存储数组,每个元素包含路由模式、请求方法、处理函数三个部分,路由模式中使用:param的形式表示动态参数,后续会转换为正则匹配规则。
2. 核心实现代码
<?php
class RegexRouter {
// 存储路由规则
private $routes = [];
/**
* 添加路由规则
* @param string $method 请求方法 GET/POST等
* @param string $pattern 路由模式 如 /user/:id
* @param callable $handler 处理函数
*/
public function addRoute($method, $pattern, $handler) {
// 将:param替换为正则捕获组
$regexPattern = preg_replace('/:([a-zA-Z0-9_]+)/', '(?P<$1>[^/]+)', $pattern);
// 拼接成正则表达式,限制匹配整个路径
$regex = '/^' . str_replace('/', '/', $regexPattern) . '$/';
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $regex,
'handler' => $handler
];
}
/**
* 匹配请求路由
* @param string $method 请求方法
* @param string $uri 请求路径
* @return mixed 匹配成功返回处理函数执行结果,失败返回false
*/
public function dispatch($method, $uri) {
$method = strtoupper($method);
// 去掉路径末尾的斜杠
$uri = rtrim($uri, '/');
if (empty($uri)) {
$uri = '/';
}
foreach ($this->routes as $route) {
if ($route['method'] !== $method) {
continue;
}
if (preg_match($route['pattern'], $uri, $matches)) {
// 提取命名捕获的参数
$params = [];
foreach ($matches as $key => $value) {
if (!is_int($key)) {
$params[$key] = $value;
}
}
// 执行处理函数,传入参数
return call_user_func_array($route['handler'], [$params]);
}
}
return false;
}
}
// 使用示例
$router = new RegexRouter();
// 添加GET路由 /user/123
$router->addRoute('GET', '/user/:id', function($params) {
return "用户ID:" . $params['id'];
});
// 添加POST路由 /article/:category/:id
$router->addRoute('POST', '/article/:category/:id', function($params) {
return "分类:" . $params['category'] . ",文章ID:" . $params['id'];
});
// 模拟请求
echo $router->dispatch('GET', '/user/1001'); // 输出 用户ID:1001
echo "<br>";
echo $router->dispatch('POST', '/article/php/2002'); // 输出 分类:php,文章ID:2002
?>
3. 正则匹配方式的优缺点
优点是实现简单,不需要复杂的数据结构,适合路由数量较少的小型应用。缺点是当路由数量增多时,需要依次遍历所有规则匹配,时间复杂度为O(n),匹配效率会下降。
二、基于Trie树的动态路由匹配实现
Trie树也叫前缀树,是一种树形数据结构,适合存储和查找字符串前缀。将路由规则按路径分段存储到Trie树中,匹配时只需要按路径分段遍历树节点,不需要遍历所有路由,匹配效率更高,时间复杂度为O(m),m是路径分段数量。
1. Trie树节点结构设计
每个节点需要存储当前路径段、是否是动态参数、子节点、以及对应的路由信息(请求方法、处理函数)。动态参数节点用特殊标识标记,匹配时优先匹配静态节点,再匹配动态参数节点。
2. 核心实现代码
<?php
class TrieNode {
public $segment; // 当前路径段
public $isParam; // 是否是动态参数节点
public $paramName; // 动态参数名称
public $children = []; // 子节点
public $routeInfo = []; // 存储路由信息 key为请求方法,value为处理函数
public function __construct($segment = '', $isParam = false, $paramName = '') {
$this->segment = $segment;
$this->isParam = $isParam;
$this->paramName = $paramName;
}
}
class TrieRouter {
private $root;
public function __construct() {
$this->root = new TrieNode();
}
/**
* 添加路由规则
* @param string $method 请求方法
* @param string $pattern 路由模式 如 /user/:id
* @param callable $handler 处理函数
*/
public function addRoute($method, $pattern, $handler) {
$method = strtoupper($method);
// 分割路径为段,去掉空段
$segments = array_filter(explode('/', $pattern));
$node = $this->root;
foreach ($segments as $segment) {
if (strpos($segment, ':') === 0) {
// 动态参数节点
$paramName = substr($segment, 1);
$childKey = ':param';
if (!isset($node->children[$childKey])) {
$node->children[$childKey] = new TrieNode($segment, true, $paramName);
}
$node = $node->children[$childKey];
} else {
// 静态节点
if (!isset($node->children[$segment])) {
$node->children[$segment] = new TrieNode($segment);
}
$node = $node->children[$segment];
}
}
// 保存路由信息
$node->routeInfo[$method] = $handler;
}
/**
* 匹配请求路由
* @param string $method 请求方法
* @param string $uri 请求路径
* @return mixed 匹配成功返回处理函数执行结果,失败返回false
*/
public function dispatch($method, $uri) {
$method = strtoupper($method);
// 分割路径为段,去掉空段
$segments = array_filter(explode('/', $uri));
if (empty($segments)) {
$segments = [''];
}
$params = [];
$node = $this->root;
foreach ($segments as $segment) {
// 优先匹配静态节点
if (isset($node->children[$segment])) {
$node = $node->children[$segment];
} elseif (isset($node->children[':param'])) {
// 匹配动态参数节点
$paramNode = $node->children[':param'];
$params[$paramNode->paramName] = $segment;
$node = $paramNode;
} else {
// 没有匹配节点,返回失败
return false;
}
}
// 检查是否有对应请求方法的路由信息
if (isset($node->routeInfo[$method])) {
return call_user_func_array($node->routeInfo[$method], [$params]);
}
return false;
}
}
// 使用示例
$trieRouter = new TrieRouter();
$trieRouter->addRoute('GET', '/user/:id', function($params) {
return "Trie树匹配 用户ID:" . $params['id'];
});
$trieRouter->addRoute('GET', '/user/:id/profile', function($params) {
return "Trie树匹配 用户" . $params['id'] . "的个人资料";
});
$trieRouter->addRoute('POST', '/article/:category/:id', function($params) {
return "Trie树匹配 分类:" . $params['category'] . ",文章ID:" . $params['id'];
});
// 模拟请求
echo $trieRouter->dispatch('GET', '/user/1001'); // 输出 Trie树匹配 用户ID:1001
echo "<br>";
echo $trieRouter->dispatch('GET', '/user/1001/profile'); // 输出 Trie树匹配 用户1001的个人资料
echo "<br>";
echo $trieRouter->dispatch('POST', '/article/php/2002'); // 输出 Trie树匹配 分类:php,文章ID:2002
?>
3. Trie树匹配方式的优缺点
优点是匹配效率高,路由数量越多优势越明显,适合中大型web应用。缺点是实现复杂度比正则匹配高,需要处理动态参数、路径分段等多种逻辑,维护成本相对较高。
三、两种方式的选型建议
如果应用路由数量少于100条,优先选择正则匹配方式,实现简单开发成本低。如果应用路由数量多,或者对路由匹配性能要求高,建议选择Trie树实现方式。实际开发中也可以结合两种方式,将高频路由用正则前置匹配,剩余路由走Trie树匹配,兼顾开发效率和运行性能。