本文目录导读:

在 PHP 中实现基于正则表达式的路由匹配是一个常见的需求,以下是几种实现方式,从简单到进阶:
基础正则路由实现
<?php
class Router {
private $routes = [];
// 添加路由规则
public function add($pattern, $callback, $method = 'GET') {
$this->routes[] = [
'pattern' => $pattern,
'callback' => $callback,
'method' => $method
];
}
// 匹配并分发请求
public function dispatch($uri, $method = 'GET') {
// 去除查询字符串
$uri = strtok($uri, '?');
// 去除末尾的斜杠(保留根目录)
if ($uri !== '/' && substr($uri, -1) === '/') {
$uri = rtrim($uri, '/');
}
foreach ($this->routes as $route) {
// 检查请求方法
if ($route['method'] !== $method) {
continue;
}
// 正则匹配
if (preg_match($route['pattern'], $uri, $matches)) {
// 移除第一个完整匹配
array_shift($matches);
// 调用回调函数
return call_user_func_array($route['callback'], $matches);
}
}
// 404 处理
return $this->notFound();
}
private function notFound() {
return "404 Not Found";
}
}
// 使用示例
$router = new Router();
// 简单的首页路由
$router->add('#^/$#', function() {
return "首页";
});
// 带参数的路由
$router->add('#^/user/(\d+)$#', function($id) {
return "用户ID: " . $id;
});
// 多个参数
$router->add('#^/blog/(\d+)/(\w+)$#', function($id, $slug) {
return "文章: ID={$id}, 标题={$slug}";
});
// 可选参数
$router->add('#^/category/(?:(\w+)/)?$#', function($category = null) {
return "分类: " . ($category ?: "全部");
});
// 测试
echo $router->dispatch('/'); // 输出: 首页
echo $router->dispatch('/user/123'); // 输出: 用户ID: 123
echo $router->dispatch('/blog/456/my-post'); // 输出: 文章: ID=456, 标题=my-post
改进版:支持命名参数
<?php
class NamedRouter {
private $routes = [];
// 将命名参数转换为正则
private function compileRoute($route) {
// 将 {name} 转换为命名组 (?P<name>[^/]+)
$pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $route);
return '#^' . $pattern . '$#';
}
public function add($path, $callback, $method = 'GET') {
$this->routes[] = [
'path' => $path,
'regex' => $this->compileRoute($path),
'callback' => $callback,
'method' => $method
];
}
public function dispatch($uri, $method = 'GET') {
$uri = strtok($uri, '?');
foreach ($this->routes as $route) {
if ($route['method'] !== $method) {
continue;
}
if (preg_match($route['regex'], $uri, $matches)) {
// 只保留命名参数
$params = array_filter($matches, function($key) {
return !is_int($key);
}, ARRAY_FILTER_USE_KEY);
// 调用回调函数
return call_user_func($route['callback'], $params);
}
}
return $this->notFound();
}
private function notFound() {
http_response_code(404);
return "404 Not Found";
}
}
// 使用示例
$router = new NamedRouter();
// 命名参数路由
$router->add('/user/{id}', function($params) {
return "用户ID: " . $params['id'];
});
$router->add('/blog/{year}/{month}', function($params) {
return "归档: {$params['year']}-{$params['month']}";
});
// 可选参数需要用正则自定义
$router->add('/search/{query}/{page?}', function($params) {
$page = isset($params['page']) ? $params['page'] : 1;
return "搜索: ".$params['query']." 第".$page."页";
});
echo $router->dispatch('/user/123');
echo $router->dispatch('/blog/2024/12');
echo $router->dispatch('/search/php/2');
完整的正则路由框架示例
<?php
class Router {
private $routes = [];
private $errorHandler;
// 支持约束的正则路由
public function add($method, $pattern, $handler, $constraints = []) {
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $this->buildPattern($pattern, $constraints),
'handler' => $handler,
'dynamic' => $this->hasDynamicSegments($pattern)
];
}
private function buildPattern($pattern, $constraints) {
// 将 {param:regex} 或 {param} 替换
$pattern = preg_replace_callback('/\{(\w+)(?::([^}]+))?\}/', function($matches) use ($constraints) {
$param = $matches[1];
$regex = isset($matches[2]) ? $matches[2] : '[^/]+';
// 应用约束
if (isset($constraints[$param])) {
$regex = $constraints[$param];
}
return '(?P<' . $param . '>' . $regex . ')';
}, $pattern);
return '#^' . $pattern . '$#';
}
private function hasDynamicSegments($pattern) {
return strpos($pattern, '{') !== false;
}
public function get($pattern, $handler, $constraints = []) {
$this->add('GET', $pattern, $handler, $constraints);
}
public function post($pattern, $handler, $constraints = []) {
$this->add('POST', $pattern, $handler, $constraints);
}
public function dispatch($uri, $method = 'GET') {
// 解析 URL
$uri = parse_url($uri, PHP_URL_PATH);
$method = strtoupper($method);
// 缓存匹配结果(可以添加缓存逻辑)
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;
}
}
// 调用处理函数
$result = call_user_func($route['handler'], $params);
// 处理响应
if ($result instanceof Response) {
return $result->send();
}
return $result;
}
}
// 404 处理
if ($this->errorHandler) {
return call_user_func($this->errorHandler);
}
return "404 Not Found";
}
public function setErrorHandler($handler) {
$this->errorHandler = $handler;
}
public function getRoutes() {
return $this->routes;
}
}
// 简单响应类
class Response {
private $content;
private $status = 200;
private $headers = [];
public function __construct($content, $status = 200) {
$this->content = $content;
$this->status = $status;
}
public function send() {
http_response_code($this->status);
foreach ($this->headers as $key => $value) {
header("$key: $value");
}
echo $this->content;
}
public function withHeader($key, $value) {
$this->headers[$key] = $value;
return $this;
}
}
// 使用示例
$router = new Router();
// 路由规则
$router->get('/', function() {
return "首页";
});
// 数字约束
$router->get('/user/{id}', function($params) {
return "用户: " . $params['id'];
}, ['id' => '\d+']);
// 字母约束
$router->get('/user/name/{name}', function($params) {
return "用户名: " . $params['name'];
}, ['name' => '[a-zA-Z]+']);
// 复杂正则
$router->get('/date/{year}/{month}/{day}', function($params) {
return "日期: {$params['year']}-{$params['month']}-{$params['day']}";
}, [
'year' => '\d{4}',
'month' => '(0[1-9]|1[0-2])',
'day' => '(0[1-9]|[12]\d|3[01])'
]);
// 通配符路由
$router->get('/file/*', function($params) {
return "文件路径";
});
// 设置404处理
$router->setErrorHandler(function() {
return new Response("页面不存在", 404);
});
// 测试
echo $router->dispatch('/');
echo $router->dispatch('/user/123'); // 匹配成功
echo $router->dispatch('/user/abc'); // 不匹配(期望数字)
echo $router->dispatch('/date/2024/12/25'); // 匹配成功
高级功能:路由分组和中间件
<?php
class AdvancedRouter {
private $routes = [];
private $middlewares = [];
private $prefix = '';
public function group($prefix, $callback) {
$previousPrefix = $this->prefix;
$this->prefix = $previousPrefix . $prefix;
$callback($this);
$this->prefix = $previousPrefix;
}
public function use($middleware) {
$this->middlewares[] = $middleware;
}
public function add($method, $pattern, $handler, $middlewares = []) {
$pattern = $this->prefix . $pattern;
// 转换为正则
$regex = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $pattern);
$regex = '#^' . $regex . '$#';
$this->routes[] = [
'method' => strtoupper($method),
'regex' => $regex,
'handler' => $handler,
'middlewares' => array_merge($this->middlewares, $middlewares)
];
}
public function dispatch($uri, $method) {
$uri = parse_url($uri, PHP_URL_PATH);
foreach ($this->routes as $route) {
if ($route['method'] !== strtoupper($method)) {
continue;
}
if (preg_match($route['regex'], $uri, $matches)) {
// 提取参数
$params = [];
foreach ($matches as $key => $value) {
if (!is_int($key)) {
$params[$key] = $value;
}
}
// 执行中间件
$request = ['params' => $params, 'uri' => $uri];
foreach ($route['middlewares'] as $middleware) {
$request = call_user_func($middleware, $request);
if ($request === false) {
return "Middleware 拦截";
}
}
// 调用处理函数
return call_user_func($route['handler'], $request['params']);
}
}
return "404 Not Found";
}
}
// 使用示例
$router = new AdvancedRouter();
// 全局中间件(日志)
$router->use(function($request) {
// error_log("访问: " . $request['uri']);
return $request;
});
// 路由分组
$router->group('/api', function($router) {
$router->add('GET', '/users/{id}', function($params) {
return json_encode(['id' => $params['id'], 'name' => 'User ' . $params['id']]);
});
$router->add('POST', '/users', function() {
return "创建用户";
}, [
function($request) { // 路由特定中间件
// 检查权限等
return $request;
}
]);
});
// 测试
echo $router->dispatch('/api/users/123', 'GET');
echo $router->dispatch('/api/users', 'POST');
正则路由的优化技巧
class OptimizedRouter {
private $routes = [];
private $compiledRoutes = [];
public function add($pattern, $handler) {
$this->routes[] = [
'pattern' => $pattern,
'handler' => $handler
];
}
// 预编译路由(提升性能)
public function compileRoutes() {
foreach ($this->routes as $route) {
$this->compiledRoutes[] = [
'regex' => preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $route['pattern']),
'handler' => $route['handler']
];
}
}
// 使用缓存的正则匹配
public function match($uri) {
if (empty($this->compiledRoutes)) {
$this->compileRoutes();
}
// 使用数组缓存匹配尝试(如果有多个路由)
foreach ($this->compiledRoutes as $route) {
if (preg_match($route['regex'], $uri, $matches)) {
$params = array_filter($matches, function($key) {
return !is_int($key);
}, ARRAY_FILTER_USE_KEY);
return [
'handler' => $route['handler'],
'params' => $params
];
}
}
return null;
}
}
路由匹配测试工具
function testRouter(Router $router, array $tests) {
foreach ($tests as $test) {
$result = $router->dispatch($test['uri'], $test['method'] ?? 'GET');
$status = ($result === $test['expected']) ? '✓' : '✗';
echo "$status URI: {$test['uri']} 结果: " . var_export($result, true) . "\n";
if ($status === '✗') {
echo " 期望: " . var_export($test['expected'], true) . "\n";
}
}
}
- 基础正则路由:使用
preg_match直接匹配正则表达式 - 命名参数路由:将
{id}转为命名组(?P<id>...) - 约束路由:支持参数约束,如
{id:\d+} - 分组和中间件:支持路由分组和中间件处理
- 性能优化:预编译正则、缓存匹配结果
选择哪种实现取决于你的具体需求,对于简单的应用,基础实现就足够了;对于复杂的框架,可以考虑使用现成的路由库如 FastRoute、Symfony Routing 等。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播