ShowDoc 内置 FastRoute:基于正则的高性能 PHP 路由库原理与实战指南
文档知识库后端前端【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址https://gitcode.com/gh_mirrors/sh/showdoc点击查看免费下载FastRoute 是 ShowDoc 项目依赖链中一个轻量而高效的正则表达式路由库vendored 于server/vendor/nikic/fast-route它通过“编译期聚合正则 运行时哈希查找”的设计把路由匹配的开销降到最低。本指南以该库的官方 README 为骨架结合仓库内源码逐层剖析其路由语法、缓存机制、分发流程与可扩展架构帮助你在 ShowDoc 乃至任何 PHP 项目中正确使用并理解 FastRoute。FastRoute 是什么FastRoute 提供了一种基于正则表达式的高速请求路由实现。与逐个路由顺序遍历匹配的传统方式不同它把一组路由编译为少数几条合并后的正则表达式运行时先用哈希表命中静态路由再对动态路由做批量正则匹配从而在路由数量增长时依然保持稳定、可预期的性能。在 ShowDoc 仓库中该库位于 server/vendor/nikic/fast-route版本为 v1.3.0见 composer.lock由 Slim 框架以nikic/fast-route: ^1.3引入属于间接依赖但其本身完全可以独立安装、独立使用。安装与运行环境通过 Composer 安装composer require nikic/fast-route该库要求PHP 5.4 或更高版本。从 composer.lock 可以看到其require仅为php: 5.4.0无任何扩展级硬依赖包使用 PSR-4 自动加载FastRoute\映射到src/并将 src/functions.php 注册为自动加载的files因此FastRoute\simpleDispatcher()等全局函数开箱即用。快速上手simpleDispatcher 基础用法FastRoute 的核心用法非常简单用FastRoute\simpleDispatcher()传入一个回调来收集路由得到一个 Dispatcher再调用其dispatch($httpMethod, $uri)完成匹配。?php require /path/to/vendor/autoload.php; $dispatcher FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { $r-addRoute(GET, /users, get_all_users_handler); // {id} must be a number (\d) $r-addRoute(GET, /user/{id:\d}, get_user_handler); // The /{title} suffix is optional $r-addRoute(GET, /articles/{id:\d}[/{title}], get_article_handler); }); // Fetch method and URI from somewhere $httpMethod $_SERVER[REQUEST_METHOD]; $uri $_SERVER[REQUEST_URI]; // Strip query string (?foobar) and decode URI if (false ! $pos strpos($uri, ?)) { $uri substr($uri, 0, $pos); } $uri rawurldecode($uri); $routeInfo $dispatcher-dispatch($httpMethod, $uri); switch ($routeInfo[0]) { case FastRoute\Dispatcher::NOT_FOUND: // ... 404 Not Found break; case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: $allowedMethods $routeInfo[1]; // ... 405 Method Not Allowed break; case FastRoute\Dispatcher::FOUND: $handler $routeInfo[1]; $vars $routeInfo[2]; // ... call $handler with $vars break; }两个需要注意的工程细节URI 的预处理由调用方负责FastRoute 刻意不绑定任何 PHP Web SAPI因此去除查询串、rawurldecode解码等归一化工作需要在调用dispatch()前自行完成正如示例代码所示。$vars是占位符名到值的映射例如请求/user/nikic/42命中{name}/{id}路由时返回[FOUND, handler0, [name nikic, id 42]]。从源码看simpleDispatcher的默认装配在 src/functions.php默认使用FastRoute\RouteParser\Std解析路由、FastRoute\DataGenerator\GroupCountBased生成数据、FastRoute\Dispatcher\GroupCountBased执行分发且都可通过 options 数组替换。定义路由addRoute 与三种语法要素路由通过RouteCollector::addRoute($method, $routePattern, $handler)定义签名如下$r-addRoute($method, $routePattern, $handler);1. HTTP 方法单方法与多方法$method是路由要匹配的大写 HTTP 方法字符串也可以用数组一次注册多个方法// These two calls $r-addRoute(GET, /test, handler); $r-addRoute(POST, /test, handler); // Are equivalent to this one call $r-addRoute([GET, POST], /test, handler);2. 占位符与自定义正则默认路由语法中{foo}表示名为foo的占位符默认匹配[^/]不包含/的一个或多个字符常量DEFAULT_DISPATCH_REGEX定义于 src/RouteParser/Std.php。通过{bar:[0-9]}语法可覆盖默认匹配规则// Matches /user/42, but not /user/xyz $r-addRoute(GET, /user/{id:\d}, handler); // Matches /user/foobar, but not /user/foo/bar $r-addRoute(GET, /user/{name}, handler); // Matches /user/foo/bar as well $r-addRoute(GET, /user/{name:.}, handler);重要限制占位符的自定义正则不能包含捕获组。例如{lang:(en|de)}是非法路由因为()是捕获组应改用{lang:en|de}或非捕获组{lang:(?:en|de)}。这一限制在数据生成阶段会被强制检查——src/DataGenerator/RegexBasedAbstract.php 中buildRegexForRoute()会对每个占位符正则调用regexHasCapturingGroups()检测命中即抛出BadRouteException。3. 可选段[...]用方括号包裹的部分是可选段/foo[bar]同时匹配/foo与/foobar。可选段只能出现在路由末尾不能出现在路由中间// This route $r-addRoute(GET, /user/{id:\d}[/{name}], handler); // Is equivalent to these two routes $r-addRoute(GET, /user/{id:\d}, handler); $r-addRoute(GET, /user/{id:\d}/{name}, handler); // Multiple nested optional parts are possible as well $r-addRoute(GET, /user[/{id:\d}[/{name}]], handler); // This route is NOT valid, because optional parts can only occur at the end $r-addRoute(GET, /user[/{id:\d}]/{name}, handler);从解析器实现看src/RouteParser/Std.php 的parse()会先剥离末尾的]统计可选段数量再按[切分若发现]出现在路由中间或开闭括号数量不匹配会分别抛出Optional segments can only occur at the end of a route与Number of opening [ and closing ] does not match异常。一个可选段在解析后会被展开成多条完整路由数据/user/{id:\d}[/{name}]最终生成两条路由记录。4. handler 的语义自由$handler不一定是回调函数也可以是控制器类名或任意你希望与路由关联的数据。FastRoute 只负责告诉你“哪个 handler 对应哪个 URI”至于如何解释 handler 完全由应用层决定。5. 常见方法的快捷方法对GET、POST、PUT、PATCH、DELETE、HEAD提供了别名方法实现于 src/RouteCollector.php$r-get(/get-route, get_handler); $r-post(/post-route, post_handler);等价于$r-addRoute(GET, /get-route, get_handler); $r-addRoute(POST, /post-route, post_handler);6. 路由分组 addGroup分组内的所有路由共享同一前缀$r-addGroup(/admin, function (RouteCollector $r) { $r-addRoute(GET, /do-something, handler); $r-addRoute(GET, /do-another-thing, handler); $r-addRoute(GET, /do-something-else, handler); });等价于$r-addRoute(GET, /admin/do-something, handler); $r-addRoute(GET, /admin/do-another-thing, handler); $r-addRoute(GET, /admin/do-something-else, handler);分组支持嵌套嵌套时各层前缀会按顺序拼接。实现上src/RouteCollector.php 的addGroup()保存并恢复currentGroupPrefix而addRoute()在解析前会把前缀拼接到路由字符串前部。路由缓存cachedDispatchersimpleDispatcher之所以接受回调定义路由是为了无缝支持缓存。改用cachedDispatcher可以把编译好的路由数据落盘下次启动时直接读取?php $dispatcher FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) { $r-addRoute(GET, /user/{name}/{id:[0-9]}, handler0); $r-addRoute(GET, /user/{id:[0-9]}, handler1); $r-addRoute(GET, /user/{name}, handler2); }, [ cacheFile __DIR__ . /route.cache, /* required */ cacheDisabled IS_DEBUG_ENABLED, /* optional, enabled by default */ ]);options 数组的关键项选项是否必填说明cacheFile必填缓存文件路径缺失时抛出LogicException见 src/functions.phpcacheDisabled可选默认false为true时跳过缓存读写等价于每次重新编译适合开发调试环境缓存实现要点见 src/functions.php缓存未禁用且缓存文件存在时直接require该文件并校验返回值为数组否则抛RuntimeException未命中缓存时照常完成路由收集与数据生成然后以?php return . var_export($dispatchData, true) . ;的形式写入缓存文件即缓存文件本身就是合法的 PHP 返回数组省去了序列化/反序列化开销由于缓存保存的是数据生成器的输出在缓存命中路径上根本不需要 RouteCollector 与 RouteParser这也是架构上把“数据生成”与“分发”拆成两个组件的原因之一。分发 URIdispatch 的三种返回状态dispatch($httpMethod, $uri)返回一个数组首元素是状态码取值来自FastRoute\Dispatcher接口的三个常量定义于 src/Dispatcher.phpDispatcher::NOT_FOUND404未匹配到任何路由Dispatcher::METHOD_NOT_ALLOWED405URI 存在但 HTTP 方法不允许此时第二元素是该 URI 支持的方法列表例如[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, [GET, POST]]Dispatcher::FOUND匹配成功第二元素是 handler第三元素是占位符名到值的字典例如/* Routing against GET /user/nikic/42 */ [FastRoute\Dispatcher::FOUND, handler0, [name nikic, id 42]]HTTP 规范提醒README 原话HTTP 规范要求405 Method Not Allowed响应必须包含Allow:头以列出该资源允许的方法。使用 FastRoute 的应用应当把返回数组的第二元素用于构造这个响应头。HEAD 请求的自动降级HTTP 规范要求所有通用服务器同时支持GET与HEADRFC 2616 Section 5.1.1。为避免用户为每个资源手动注册 HEAD 路由FastRoute 在匹配不到 HEAD 路由时会自动回退匹配同 URI 的 GET 路由。PHP Web SAPI 会自动剥离 HEAD 响应的实体内容因此这一行为对绝大多数用户无感。但 README 特别强调在非 Web SAPI 环境如自建服务器中使用 FastRoute 的实现者绝不能把 GET 生成的响应体发给 HEAD 请求——这是使用方自己的责任FastRoute 无法替你阻止这类违反 HTTP 规范的行为。当然应用也可以为某资源显式注册自己的 HEAD 路由来绕过降级逻辑。该逻辑在 src/Dispatcher/RegexBasedAbstract.php 中实现HEAD 未命中时依次尝试静态 GET 路由表与动态 GET 路由数据。源码级剖析三大组件如何协同路由过程由三个组件协作完成分别对应以下接口定义于 src/RouteParser.php、src/DataGenerator.php、src/Dispatcher.php?php namespace FastRoute; interface RouteParser { public function parse($route); } interface DataGenerator { public function addRoute($httpMethod, $routeData, $handler); public function getData(); } interface Dispatcher { const NOT_FOUND 0, FOUND 1, METHOD_NOT_ALLOWED 2; public function dispatch($httpMethod, $uri); }RouteParser路由字符串 → 结构化路由数据RouteParser 把路由模式字符串解析为“路由信息数组”。以/user/{id:\d}[/{name}]为例解析结果如下[ [ /user/, [id, \d], ], [ /user/, [id, \d], /, [name, [^/]], ], ]即字符串片段与[占位符名, 正则]交替排列可选的[/{name}]段展开为第二条完整路由。默认实现Std的占位符识别正则见 src/RouteParser/Std.php它允许占位符名以字母或下划线开头后续可含字母、数字、下划线与连字符同时支持在{...}内嵌套花括号用于正则。DataGenerator把路由数据编译为匹配数据解析后的路由数据交给 DataGenerator 的addRoute()全部添加完后调用getData()得到 Dispatcher 所需的匹配数据。数据格式没有硬性规定它与对应 Dispatcher 紧密耦合。以默认的GroupCountBased数据生成器src/DataGenerator/GroupCountBased.php为例纯静态路由无占位符进入哈希表staticRoutes实现 O(1) 查找动态路由按“近似块大小 10”getApproxChunkSize()返回 10见 同文件 L7-L10分组每块内用分支重置组(?|...)把多条路由的正则合并成一条大正则并按“捕获组数量”建立routeMap见processChunk()合并前会做多项冲突检查重复注册同一路由、静态路由被已有动态路由“遮蔽”src/DataGenerator/RegexBasedAbstract.php、同一占位符名重复使用、占位符正则在捕获组同文件 L126-L156违反任一项都会抛出BadRouteException生成正则时所有字面量片段都会经preg_quote(..., ~)转义确保/等字符不会被误解释。这正是 FastRoute“快”的核心把 N 条路由的 N 次正则匹配压缩为每块一次的合并正则匹配。Dispatcher运行时匹配Dispatcher 通过构造函数接收生成的数据并提供dispatch()。默认的GroupCountBased分发器继承自RegexBasedAbstractsrc/Dispatcher/RegexBasedAbstract.php完整匹配流程为静态路由哈希命中staticRouteMap[$httpMethod][$uri]直接返回 FOUND零正则开销动态路由正则匹配对$httpMethod对应的动态路由块逐一preg_match命中即根据捕获组数量查routeMap得到 handler 与变量名再组装$vars见 GroupCountBased::dispatchVariableRoute()HEAD 降级到 GET见上文通配方法*回退若注册过*方法的路由则用其兜底同文件 L50-L59405 判定遍历所有方法的路由数据尝试匹配收集可用的方法列表返回METHOD_NOT_ALLOWED若无任何方法可匹配才返回NOT_FOUND。Route值对象src/Route.php承载httpMethod、regex、variables、handler四个字段其matches()方法用于冲突检测时判断变量路由是否覆盖静态路由。覆盖与替换自定义路由组件三个组件可以单独或成对替换以适配不同的路由语法或分发策略RouteParser 可以单独替换用于支持不同的路由模式语法DataGenerator 与 Dispatcher 必须成对替换因为前者的输出与后者的输入紧密耦合也正是因为只有生成器的输出会被缓存两者才被拆开通过simpleDispatcher/cachedDispatcher的 options 数组即可完成覆盖?php $dispatcher FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { /* ... */ }, [ routeParser FastRoute\\RouteParser\\Std, dataGenerator FastRoute\\DataGenerator\\GroupCountBased, dispatcher FastRoute\\Dispatcher\\GroupCountBased, ]);上述 options 即为默认值。把GroupCountBased换成GroupPosBased即可切换到另一种分发策略仓库中还提供了MarkBased、CharCountBased等实现见 src/DataGenerator 与 src/Dispatcher分别以“位置”“标记”“字符计数”等不同方式编码捕获组与路由的对应关系供在不同性能特征场景下取舍。完整默认装配可在 src/functions.php 与 src/functions.php 中核对。在 ShowDoc 项目中的定位与验证在 ShowDoc 仓库中FastRoute 以 vendor 形式存在于 server/vendor/nikic/fast-route版本 v1.3.0许可协议为 BSD-3-Clause。从 composer.lock 的依赖图可以确认slimphp/Slim声明nikic/fast-route: ^1.3见 composer.lock因此 FastRoute 是 Slim 路由层的底层实现服务于 ShowDoc 基于 Slim 构建的 API 应用模块server/app目录。该库自带完整的单元测试套件位于 server/vendor/nikic/fast-route/testtest/RouteParser/StdTest.php覆盖占位符、可选段、非法语法中间可选段、括号不匹配、捕获组等的解析行为test/Dispatcher/DispatcherTest.php 及GroupCountBasedTest.php、GroupPosBasedTest.php、CharCountBasedTest.php、MarkBasedTest.php以同一组路由数据分别验证四种分发器并覆盖 404、405含Allow方法列表与 HEAD 降级等 HTTP 合规场景test/RouteCollectorTest.php验证addGroup前缀拼接、快捷方法等价性与*通配方法。这些测试文件是理解该库行为边界的第一手资料——例如“静态路由被动态路由遮蔽”“同一占位符不能重复”等BadRouteException场景都能在测试中找到对应断言。如果你在 ShowDoc 中调试 Slim 路由相关问题FastRoute 的这套测试可以作为路由语义的权威参考。总结FastRoute 的设计哲学可以概括为三点定义期重编译、运行期零浪费——静态路由走哈希、动态路由走聚合正则架构可插拔——Parser / DataGenerator / Dispatcher 三件套可独立或成对替换语义简单而严格——可选段只在末尾、占位符正则禁捕获组、冲突路由直接报错把模糊性消灭在定义阶段而非运行阶段。在 ShowDoc 这类同时承载文档站与 API 服务的项目中理解这层路由底座有助于在排查 404/405 异常、评估路由缓存策略或扩展自定义路由语法时做到心中有数。赞分享文档知识库后端前端【免费下载链接】showdocShowDoc is a tool greatly applicable for an IT team to share documents online一个非常适合IT团队的在线API文档、技术文档工具项目地址https://gitcode.com/gh_mirrors/sh/showdoc点击查看免费下载相关推荐FastRoute 2.0 终极指南PHP 高性能路由的未来展望FastRoute 2.0 终极指南PHP 高性能路由的未来展望 FastRoute 是一款专为 PHP 设计的快速请求路由库它通过基于正则表达式的高效实现后端Prompt Engineering Guide从基础到智能体的大模型提示词实用指南Prompt Engineering Guide从基础到智能体的大模型提示词实用指南 你一定遇到过这种情况同一个问题换个问法模型的回答就差了一大截。Pr文档教程提示工程大模型人工智能RAGAI AgentAppFlowy 10分钟上手完整入门介绍AppFlowy 10分钟上手完整入门介绍 AppFlowy 是一个开源免费的「Notion 替代品」用 Flutter 和 Rust 写成。它提供文档、数前端后端企业应用内容协同知识管理AI 应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考