PHP 与 Laravel 解析器移植设计:code-review-graph 的 Composer PSR-4、Blade 模板与框架语义边实现
【免费下载链接】code-review-graphLocal-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.项目地址: https://gitcode.com/GitHub_Trending/co/code-review-graph
导读
本文是 code-review-graph 中 PHP/Laravel 解析能力的设计文档解读,对应 docs/superpowers/specs/2026-07-17-php-laravel-parser-design.md。它详细说明了如何在不替换既有通用解析路径的前提下,为 PHP 语言补充 trait/enum/new 语法识别、boot/register/__invoke入口点、Composer PSR-4 命名空间解析、Blade 模板引用抽取,以及基于显式证据的 Laravel 路由与 Eloquent 关系语义边。读完本文,你将理解该设计的取舍边界、源码级实现位置(parser.py、flows.py)与对应的红-绿测试矩阵(test_php_laravel.py),并能在自己的 PHP/Laravel 仓库中验证其行为。
设计背景:一次有选择的移植(Port)
Pull request #252 曾贡献 PHP、Composer、Blade 与 Laravel 的解析成果,但该分支早于main上已经合并的更完善 PHP 调用与use处理逻辑。因此本次移植只取 #252 中仍缺失的剩余行为,并保留当前通用解析路径不变:
- 源码致谢:原始实现出自 Minidoracat(
minidora0702@gmail.com),移植提交会保留该归属。 - 绝不回炉:不重新打开、合并或以其他方式修改源 PR #252。
设计目标(Goals)
- 识别 PHP trait、enum、对象创建与继承/接口子句,且不改变现有 PHP 调用或导入的格式化输出;
- 将 PHP
boot、register、__invoke方法视为语言作用域入口点,同时保持通用handle、up、down行为不变; - 通过 Composer PSR-4 映射安全、确定性地解析 PHP 命名空间;
- 解析 Blade 模板引用,同时忽略 Blade 注释与转义指令;
- 仅在 AST 包含显式框架与接收者证据时,添加 Laravel 路由到控制器、Eloquent 关系边;
- 保持串行与进程池构建行为等价。
非目标(Non-goals)
- 不替换现有 PHP 解析器或导入解析器;
- 不依据方法名单独推断 Laravel 语义;
- 不支持全部 Blade 指令或动态路由目标;
- 不解析仓库之外的 Composer 依赖;
- 不重开/合并/修改源 PR #252。
PHP 语法与入口点扩展
既有 Tree-sitter 语言表在 parser.py 中为 PHP 新增三类语法节点:trait_declaration、enum_declaration、object_creation_expression。同时:
_get_call_name(parser.py)分支仅针对对象创建场景扩展;_get_bases(parser.py)为 PHP 新增base_clause与class_interface_clause处理,从而正确抽取extends/implements目标。
语言作用域入口点
流检测(flows.py)为 PHP 增加专属模式集:
| 语言 | 入口点正则 | 说明 |
|---|---|---|
| PHP | ^(boot\|register)$ | Laravel 服务提供者常用生命周期方法 |
| PHP | ^__invoke$ | 可调用对象(callable class)入口 |
已属通用入口点的名称(如handle、up、down)不会被重复加入,从而避免重复边。这套模式只作用于 PHP 语言,不影响其他语言。
Composer PSR-4 解析
仓库根与搜索边界
CodeParser在构造时记录已解析的仓库根(repo_root,见 parser.py)。Composer 查找从调用方目录开始,向上直至仓库根(含根目录)停止;若未提供仓库根,则使用最近的 VCS 根(.git/.svn);若连 VCS 根也没有安全边界,则绝不爬到调用方目录之上(实现见_php_repository_boundary,parser.py)。
严格的 JSON 形状校验
_read_php_composer_psr4(parser.py)只有在每个容器符合预期 JSON 形状时才接受 Composer 数据:
document、autoload、autoload-dev必须是对象;psr-4必须是对象;- 前缀必须是字符串;
- 映射路径必须是字符串或字符串列表。
任何形状不符的composer.json都返回空映射,绝不抛错(对应测试test_composer_malformed_shapes_are_ignored)。
合并规则与最长前缀优先
autoload与autoload-dev的映射合并且不互相覆盖;- 所有合法映射目录按声明顺序保留(去重);
- 前缀经过
\归一化(去首尾反斜杠),按前缀长度降序、同长按字典序排序后搜索(见sorted(..., key=lambda item: (-len(item[0]), item[0]))); - 解析出的基目录与目标文件经
resolve()后必须位于仓库内;绝对路径逃逸与..逃逸一律忽略。
不可变解析与进程级缓存
解析结果是不可变元组(prefix, (destinations...)),并以functools.lru_cache(maxsize=128)按(Composer 路径、仓库根、文件 mtime、文件大小)为键做有界缓存——这让串行解析器与常驻进程池 worker 能复用配置,同时避免跨仓库的过期结果。修改composer.json会因 mtime/size 变化自动失效(测试见 test_php_laravel.py)。
兼容回退
现有“祖先目录逐级向上查找”的解析器(ancestor-walk resolver)仍作为 Composer 解析之后的兼容回退保留,且同样受仓库边界约束(test_php_ancestor_fallback_*系列测试覆盖越界、符号链接逃逸、软失败等场景)。
Blade 模板解析
复合扩展名.blade.php在普通.php后缀处理之前被检测(detect_language,parser.py),并交给一个专用轻量解析器_parse_blade(parser.py):
- 输出一个 File 节点(语言为
blade); @extends、@include、@component生成IMPORTS_FROM边;@livewire生成REFERENCES边;- 每个边带
extra={"blade_directive": directive}元数据。
注释掩码与转义指令
- 指令匹配前先通过
_mask_blade_comments(parser.py)将{{-- ... --}}注释区间替换为等长空白(保留换行与字符偏移); - 指令正则
(?<!@)@(extends|include|component|livewire)\s*\(...\)(parser.py)要求@未被转义,因此@@include及等价转义形式不产生边; - 边行号基于掩码后文本重新计算,与原始源码行号保持对齐。
行为边界
_parse_blade不用 Tree-sitter 语法即可完成抽取,普通 PHP 文件仍走完整 PHP 解析路径,互不干扰(test_blade_handling_does_not_change_regular_php)。
Laravel 语义证据门控
Laravel 分析在通用抽取之后作为独立的 PHP AST 后处理(post-pass)运行(_extract_php_laravel_edges,parser.py)。它从不消费 Tree-sitter 节点、从不重建普通 CALLS 边,因此现有目标(如Route::get、hasMany及所有嵌套调用)保持不变。后处理过程:
- 建立命名空间局部的类导入绑定(含别名与分组导入,
_php_import_bindings,parser.py); - 追踪包裹类(enclosing class)。
Route 到控制器边
仅当同时满足全部四个条件才产生 CALLS 边(_emit_laravel_route_edge):
- 作用域调用是受支持的路由动词(
_LARAVEL_ROUTE_VERBS:get/post/put/patch/delete/options/any/match/resource/apiResource,见 parser.py); - 接收者是从
Illuminate\Support\Facades\Route导入的别名,或直接使用该全限定类名; - 处理器采用静态数组形式
[Controller::class, 'method']; - 控制器名解析后(经 Composer)目标文件存在。
Eloquent 关系边
仅当同时满足全部四个条件才产生 REFERENCES 边:
- 调用使用受支持的关系方法(
_LARAVEL_RELATIONSHIPS:hasMany/hasOne/belongsTo/belongsToMany/morphTo/morphMany/morphOne/morphToMany/morphedByMany/hasManyThrough/hasOneThrough,parser.py); - 接收者恰好是
$this; - 包裹类继承自导入或全限定的
Illuminate\Database\Eloquent\Model(_php_class_extends_eloquent_model,parser.py); - 首个相关实参是
Target::class。
目标命名与降级
导入或全限定的控制器/模型名经 Composer 解析;当目标文件存在时,语义边使用图的真实限定名形状(路由为file.php::Class.method,模型为file.php::Class);否则保留稳定的短语义目标,而不是凭空编造文件(test_laravel_unresolved_model_keeps_stable_short_target)。
负面用例门控
设计明确拒绝以下情况(对应 test_php_laravel.py 中的负面用例):
- 无关的
Route(未从 Laravel Facade 导入); - 非模型的“关系”调用;
- 错误的接收者(非
$this); - 动态处理器(非
[Class::class, 'method']静态形式); - 缺少导入(短名 facade 但无对应 use 语句)。
测试策略:红-绿实现
每个表面都按红-绿(red-first)方式实现:
- PHP 语法:traits、enums、
new、基类子句、语言作用域入口点; - Composer:畸形形状、最长前缀、多目录映射、
autoload-dev合并、缓存失效/复用、仓库遍历、绝对路径与符号链接逃逸; - Blade:指令、行号、注释、转义指令、畸形输入、普通 PHP 隔离;
- Laravel:别名/FQCN 正向用例,以及无关
Route、非模型关系、错误接收者、动态处理器、缺失导入等负向用例; - 串行/进程池一致性:在文件数足够进入并行路径的 Composer PHP 项目上验证(
test_composer_process_pool_matches_serial_build,test_php_laravel.py)。
聚焦测试通过后,还需全量测试套件、Ruff、schema 生成检查、图变更评审与 GitHub CI 全部通过,才能开启 ready PR。
源码地图与验证路径
| 关注点 | 源码位置 |
|---|---|
| PHP 语法节点表 | parser.py |
| Blade 检测与解析 | parser.py、parser.py |
| Blade 注释掩码 | parser.py |
| Composer PSR-4 读取与形状校验 | parser.py |
| 仓库边界与祖先回退 | parser.py |
| Laravel 后处理入口 | parser.py |
| PHP 入口点模式 | flows.py |
| 专项测试 | test_php_laravel.py |
结语
这份设计文档体现了一种克制的移植哲学:对 PHP 生态的深度支持,建立在“不破坏通用解析路径、不虚构语义证据、严格边界约束”三个支柱之上。Composer 映射的确定性、Blade 的行号保真、Laravel 边的四条件证据门控,共同保证了在大型 PHP/Laravel 仓库上构建出的代码图谱既信息丰富又可复现,串行与并行构建行为始终一致。对于希望在代码评审工作流中利用这些语义边的开发者,可以从 test_php_laravel.py 的正负用例出发,快速验证自己仓库中的 Route、Eloquent 关系与 Blade 引用能否被正确识别。
【免费下载链接】code-review-graphLocal-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.项目地址: https://gitcode.com/GitHub_Trending/co/code-review-graph
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考