PHP 路由实现正则匹配

wen PHP项目 2

本文目录导读:

PHP 路由实现正则匹配

  1. 基础正则路由实现
  2. 改进版:支持命名参数
  3. 完整的正则路由框架示例
  4. 高级功能:路由分组和中间件
  5. 正则路由的优化技巧
  6. 路由匹配测试工具

在 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";
        }
    }
}
  1. 基础正则路由:使用 preg_match 直接匹配正则表达式
  2. 命名参数路由:将 {id} 转为命名组 (?P<id>...)
  3. 约束路由:支持参数约束,如 {id:\d+}
  4. 分组和中间件:支持路由分组和中间件处理
  5. 性能优化:预编译正则、缓存匹配结果

选择哪种实现取决于你的具体需求,对于简单的应用,基础实现就足够了;对于复杂的框架,可以考虑使用现成的路由库如 FastRoute、Symfony Routing 等。

抱歉,评论功能暂时关闭!