Releases: route-forge/php-common
Release list
v1.2.0
未命名路由不再静默消失,严格模式一次报全「缺名字 / 缺层级」两类问题。
根因:RouteAnalyzer 与 RouteRepository 都在 resolve() 之前把 name 为空的路由跳过,且告警条件只看原始显式 tier——于是「靠 config match 规则或 classifier 回调归入层级却没写名字」的路由既不进元信息、也不进 list 行、更不进 tier_counts,一条信号都不留;SPEC 承诺的 strict_mode 500(RF_BE_005)因此在生产里从不触发,是死代码。
修复
- 未命名路由不再静默消失:现逐条探测层级归属并全量收集,被层级规则命中的未命名路由按层级分组进入
warnings(含 URI、HTTP 方法、中间件与命中来源,可直接 grep 定位)。 - 历史文案兼容点:「显式
->tier()无名」的整句Route (uri) has tier [x] but no route name assigned; …原样保留(方法/中间件追加为句尾标签),适配包 Feature 测试与 CI 脚本的 substring / grep 断言不受影响。 RF_BE_005不再是死代码:严格模式的「有层级归属却无路由名」由StrictViolationScanner在取数入口做全量预扫描接管(见下方「变更」)。
变更
破坏性提示:以下两项改变 strict_mode=true 项目的对外错误形态,适配包需按新文案同步 Feature 测试与 SPEC §3.2 / §6.1。
- HTTP 端点:
strict_mode=true时,任一读路径(层级端点 / 摘要 / 全量 / unassigned)取数前先全量扫描,发现「有层级归属却无路由名」或「命名路由未归级」即抛新错误码RF_BE_009(RouteStrictViolationException,HTTP 500),message 分组列出两类全量清单,violations()另给结构化清单供页面高亮定位。取代原先「第一条RF_BE_001/RF_BE_005就中断」的修一条刷一条模式;RF_BE_001/RF_BE_005两个异常类与错误码保留(TierResolver::resolve()的单条语义不变)。层级名拼错、classifier 抛错等更精确的配置错误仍走各自错误码(RF_BE_002/004/006),只在聚合消息里作信息附录。 - 命令行:
route:forge:list/types在strict_mode=true下不再因未归级路由直接以退出码 1 中断(命令本身在排查场景必须还能跑),改为经analyze()['violations']全量输出「缺名字 / 缺层级 / 无法判定」三组,由命令层统一渲染成红色清单并决定退出码。宽松模式输出形态完全不变:表格、tier_counts、--json契约一字未动。 - 未命名且不命中任何层级的路由不算违规:它压根不在 forge 管辖范围内(否则 vendor 与框架内部路由会把严格模式报错刷满)。
新增
Support\StrictViolationScanner:严格模式违规的唯一判定与措辞来源,scan()返回{missing_name, unassigned, unresolved}(按实例记忆,一次请求只扫一遍),count()明确排除unresolved,format()生成 HTTP 错误体与命令行红色清单共用的清单文本。Exception\RouteStrictViolationException(RF_BE_009,500):fromViolations()构造、violations()输出结构化清单。RouteAnalyzer::analyze()输出新增violations键(仅严格模式非空);RouteRepository在infos()入口挂预扫描。Support\HttpMethods::withoutHead():HEAD 过滤的单点实现,RouteAnalyzer::withoutHead()保留为委托入口,公开签名不变。TierResolver::probe(RouteInfo):与resolve()共享同一套优先级求值(抽出classifierTier()/matchedTier()私有 helper 防口径漂移)的只读探测,返回{level, source, requested, error},source ∈ explicit | classifier | match | none | explicit-unknown-level | classifier-unknown-tier | classifier-error。与resolve()的一致性由单测钉住。TierResolver::configuredLevels()/isStrict():按配置顺序返回层级名、暴露严格模式开关,命令层与分析器不再各自读配置。RouteAnalyzer::unnamedWarnings()/RouteAnalyzer::formatUnnamed():未命名路由的告警文本与--unnamed分组视图,文案在 common 单点生成,两个适配包不再各写一份。RouteNameFilter新增按 URI 前缀排除的第二维度(isUriExcluded()/ 不可变withUriPrefixes()):未命名路由没有名字可判,而 forge 自身的层级端点(如_forge/routes/manage带manage中间件)会被match规则命中,不排除就是包把自己报成配置错误。URI 按段匹配,_forge/routes不误伤_forge/routeship。
测试
- 全量 109 tests / 291 assertions 通过(发布前 78 例 → 新增 31 例),CI 矩阵 PHP 8.2 / 8.3 双绿。
- 新增
UnnamedRouteVisibilityTest(未命名路由归类、聚合文案、strict 不新增失败模式、计数口径、URI 排除、漂移守卫)与StrictViolationScanTest(扫描器三分、RF_BE_009聚合抛出、宽松静默、精确错误码不被冒名)。
版本
新增公开 API + 严格模式对外错误码变化(RF_BE_001/005 → RF_BE_009),宽松模式零变化,按 minor 发 1.2.0;schemeVersion 保持 1。两个适配包(php-laravel / php-thinkphp)的 --unnamed 选项、红色 violations 渲染与 withUriPrefixes() 接线,以及 .docs/SPEC.md §3.1.4 / §3.2 / §6.1 的同步尚未跟进。
1.1.2...1.2.0 · 完整变更见 CHANGELOG.md
v1.1.2
修复
ConfigFileGenerator::formatMatchArray与formatLevelEntry对match.prefix/match.middleware及 level 级endpoint_middleware统一(array)归一:传单值字符串(如'prefix' => 'admin')时,保存生成config/forge.php不再在exportInlineArray(array)的类型声明上抛TypeError,落盘规范化为单元素数组字面量,语义与TierResolver::matchConfig(1.1.1)对齐。此前手写配置的字符串写法能被读取、却无法经管理器保存回写,属两侧不对称。
测试
- 新增
ConfigFileGeneratorTest:单值 string 与数组写法逐条生成等价(prefix / middleware / level endpoint_middleware)+ 空值边界回归;全量 78 tests / 201 assertions 通过。
版本
纯缺陷修复,无 API / 载荷形状变化,schemeVersion 保持 1。
v1.1.1
修复
TierResolver::matchConfig对 match 规则统一类型归一化:match.prefix/match.middleware传单值字符串(如'prefix' => 'admin')不再抛count(): Argument #1 ($value) must be of type Countable|array, string given,行为与单元素数组完全等价(按段匹配、空字符串不命中等语义不变)。middleware_match增加类型守卫:传入非 string/array 类型时回落'any'并记录 warning(此前直接 TypeError 崩溃)。
测试
- 新增 5 个单测:string / 数组两种写法逐条等价(prefix、middleware、两者同时)+ 空值边界回归 + 非法
middleware_match类型回落;全量 72 tests / 189 assertions 通过。
版本
纯缺陷修复,无 API / 载荷形状变化,schemeVersion 保持 1。
v1.1.0
修复
- JsSafeEncoder 补齐
JSON_UNESCAPED_UNICODE:与Illuminate\Support\Js::from逐位对齐。@forgeSummary内嵌载荷中的中文(如levels.*.description)不再被转成\uXXXX,避免首屏<head>载荷体积按字符数成倍膨胀;JS 解析结果本就等价。 - 别名跟随 target 解析到的每一个层级:同一目标路由名以不同层级重复注册时,别名此前只随末次层级,导致
route:forge:list --level=<另一层级>与types的d.ts缺该条目。现在端点 / list / types / 管理器四处一律跟随目标解析到的每个层级;route_count、tier_counts口径不变(一别名只计一次)。
提示
- 路由名跨层级重复注册时,
RouteAnalyzer输出 warning 点名歧义,而非静默择一。
完整变更
v1.0.0
route-forge/common 首个正式版本 —— 从 route-forge-laravel 拆分出的框架无关核心,供各框架适配器(Laravel / ThinkPHP / Symfony)复用。
核心能力
- Tier 分级解析:5 优先级解析链(显式声明 > 分类器 > 配置匹配 > 未分配兜底),支持 middleware_match any / all / DNF
- 别名系统:宏声明 + 配置双来源合并,冲突丢弃并告警,悬空目标快速失败(RF_BE_008)
- 统一路由仓库:按层级的元信息 / 摘要 / 未分配 / 全量索引,全部走缓存
- TS 类型生成:d.ts / JSON 双输出,
collectTargets()支持各框架 types 命令复用(空层级保留、HEAD 过滤、body 方法识别) - 摘要内嵌:
__ROUTE_FORGE__一次性嵌入,JsSafeEncoder 语义与安全JSON.parse等价 - 配置文件生成:PHP 源码级生成,动态值全量转义
- 缓存层:
SUMMARY_LEVEL+forgetLevel()保证层级失效必同步失效摘要
分析管道(供框架命令复用)
RouteAnalyzer::analyzeRoutes():raw 路由集合 + normalizer 直入分析管道filterRows()/withoutHead()/listPayload():SPEC §3.2list --json契约组装下沉RouteRepository::normalizeEndpointPrefix():端点前缀规范化公共工具
工程保障
- 仅依赖
psr/log,零框架引用(红线见 AGENTS.md) - 60 个框架无关单元测试,131 断言
- 独立 CI:PHP 8.2 – 8.5 全矩阵