mcpField
P
phpgraph

phpgraph

本地服务
github.com/hbenabdallah开发工具3今天更新

关于 phpgraph

phpgraph 对 PHP 项目进行静态分析,为编码 Agent 构建代码知识图谱。它以带置信度标签的事实映射调用、路由、消息、依赖和架构,不执行代码,也不会将代码发送到本机之外。

主要功能

  1. 01通过类型推断构建调用图
  2. 02映射路由、消息和处理器
  3. 03追踪依赖与变更影响
  4. 04检查架构层和规则
  5. 05关联 API 模式与实现
  6. 06报告未解析关系和分析盲区

适用场景

  1. 01了解陌生的 PHP 项目
  2. 02追踪请求从路由到处理器的路径
  3. 03变更前识别受影响代码和测试
  4. 04在 CI 中检查架构层规则

MCP 相关

通过 MCP 提供静态分析结果,包含项目概览、图查询、代码结构、节点关系、影响分析和路径查询等工具。图谱在首次使用时构建,并在代码变化时增量更新。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"hbenabdallah-phpgraph": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/hbenabdallah/phpgraph:1.6.1"
]
}
}
}
启动命令docker run -i --rm ghcr.io/hbenabdallah/phpgraph:1.6.1

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
ocighcr.io/hbenabdallah/phpgraph:1.6.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

phpgraph 将 PHP 代码库转换为领域、处理器、仓储、控制器和适配器的知识图谱,并通过 CLI 和 MCP 服务提供给 AI Agent 使用

phpgraph

为 AI 编码 Agent 打造的 PHP 代码库知识图谱。
通过 MCP 向 Sherpa、Claude Code、Codex、Cursor 和任何 MCP 客户端提供静态分析结果。
无需大模型、网络或嵌入:提供确定性事实,并为每项事实标注置信度。

CI Packagist 版本 下载量 PHP 8.2+ 许可证:MIT 已收录于 mcpservers.org

English · Français


为什么选择 phpgraph?

AI Agent 遇到不熟悉的 PHP 项目时,会逐个读取文件并搜索名称。但它会错过真实代码库中最重要的信息:谁调用了什么、哪位处理器接收消息、哪个控制器处理路由、方法更改会造成什么影响,以及各层之间的依赖关系。

phpgraph 使用 nikic/php-parser 解析整个项目(使用 phpstan/phpdoc-parser 解析文档块),然后通过图谱回答这些问题:

  • 提供确定性事实,而非猜测。 每种关系都会标记为 EXTRACTED(从代码中读取)、INFERRED(根据声明的类型解析)或 AMBIGUOUS(猜测,并明确标注)。
  • 明确说明未知之处。 未解析的调用、缺失的 vendor/、没有处理器的消息:这些缺漏都会报告出来,让 Agent 知道何时需要自行阅读代码。
  • 专为 DDD 和六边形架构打造,随后扩展到微服务,再到一般 PHP 项目:Symfony、Laravel、Ecotone、PrestaShop、WordPress 或纯 PHP。
  • 从设计上保护隐私。 你的代码绝不会离开本机,phpgraph 也绝不会执行代码,只会解析代码。

功能

  • 带类型推断的调用图:返回类型、链式调用($a->b()->c())、局部变量、文档块(@return、@var),以及 vendor/ 依赖的签名,让调用链可以贯穿 Doctrine、Symfony 或 Laravel API。
  • 架构:DDD 和六边形分层、限界上下文、在 CI 中检查分层规则(phpgraph check),以及影响分析(impact_of):哪些内容依赖某个类或方法,以及要运行哪些测试。
  • 消息和事件:Symfony Messenger、Laravel 作业和事件、Ecotone、PrestaShop CQRS、WordPress 钩子、领域事件和自定义总线的发送方 → 消息 → 处理器关系,来源可以是属性、服务配置(YAML、XML、PHP)或代码结构。
  • HTTP:将路由(Symfony 属性、YAML 和 PHP 路由文件、API Platform 资源、Laravel 路由文件、以容器服务 ID 命名的控制器)关联到控制器,并将 HTTP 调用关联到其访问的路由。如果某个路由的控制器类既不在项目中,也不在 vendor/ 中,报告中会指出其路由文件。
  • 依赖注入:Symfony 容器配置除构造函数类型之外注入的内容、某个标签的所有服务(tagged_iterator、#[AutowireIterator])或按 ID 命名的服务,支持从 PHP、YAML 或 XML 配置中解析;也支持带字面量参数调用的辅助方法(Wiring::wire($services, 'sales', ...),只求值,不执行):验证器会关联到它收到的规则。声明自身可处理哪些对象的规则(supports(object $input): bool { return $input instanceof CreateEstimateQuery; },也包括 canHandle、accepts 等)不会被传入其他输入的用例关联到共享流水线中,输入属性所持有的类也包括在内。接口类型的输入可能是任何实现该接口的项目类,以及输入被传递给的参数类型(将 QueryInterface $query 传给 setUp(CreateEstimateQueryInterface $query):一个 CreateEstimateQuery);无法读取的守卫,或仅针对其中某些类运行的规则,会保留并标记为 supports() not resolved。
  • 微服务:每个服务使用独立的 ID 空间,并通过各自的契约连接服务:共享消息类、路由键、HTTP 路由。
  • API 架构:将 OpenAPI(Swagger)文档中的操作关联到实现它们的路由;如果文档描述的是其他 API,则关联到其客户端的 HTTP 调用;将 protobuf 服务的 RPC 关联到实现其生成接口的类。报告会指出已记录但未实现的内容,以及没有文档描述的路由。impact 会列出更改所涉及的操作(客户端依赖的契约),outline 会列出某个功能所服务的操作。
  • 零配置 MCP 服务:首次使用时构建,代码更改时增量重建(在含 8,400 个文件的项目中,每次编辑约需 3 秒)。

快速开始

1. 安装。 单个可执行文件,无需 PHP(支持 Linux 和 macOS 的 x86_64 与 ARM,以及 Windows 的 x86_64):

curl -fsSL https://raw.githubusercontent.com/hbenabdallah/phpgraph/main/install.sh | sh                  # latest release
curl -fsSL https://raw.githubusercontent.com/hbenabdallah/phpgraph/main/install.sh | sh -s -- 0.2.0     # a given release
curl -fsSL https://raw.githubusercontent.com/hbenabdallah/phpgraph/main/install.sh | sh -s -- dev-main  # the main branch
irm https://raw.githubusercontent.com/hbenabdallah/phpgraph/main/install.ps1 | iex                       # Windows
$env:PHPGRAPH_VERSION = "dev-main"; irm https://raw.githubusercontent.com/hbenabdallah/phpgraph/main/install.ps1 | iex

该脚本会下载二进制文件、校验其 SHA-256,并将其放入 ~/.local/bin(Windows:%LOCALAPPDATA%\Programs\phpgraph,并添加到 PATH);设置 PHPGRAPH_INSTALL_DIR 可更改目录。再次运行即可更新。每次推送到 main 时,都会重新构建 dev-main(edge 预发布版本)。该二进制文件是内置 phpgraph 的静态 PHP 运行时,使用 static-php-cli 构建。

已安装 PHP 8.2+,或使用 Docker 时:

composer require --dev hbenabdallah/phpgraph       # or: composer global require hbenabdallah/phpgraph
curl -LO https://github.com/hbenabdallah/phpgraph/releases/latest/download/phpgraph.phar
docker pull ghcr.io/hbenabdallah/phpgraph          # nothing else to install

2. 连接你的 Agent。 mcp-config 会打印一份可直接粘贴的配置,并填入路径(通过 Composer 安装时为 vendor/bin/phpgraph):

phpgraph mcp-config sherpa    # Sherpa: a `sherpa mcp add` command, one declaration for every project
phpgraph mcp-config claude    # Claude Code: a `claude mcp add` command, or .mcp.json
phpgraph mcp-config codex     # Codex: ~/.codex/config.toml
phpgraph mcp-config cursor    # Cursor: .cursor/mcp.json, shareable with the team
phpgraph mcp-config json      # any other MCP client over stdio

即使已连接 phpgraph,Agent 还是会先用 grep。mcp-config 还会打印一行内容,供你添加到项目的说明文件(CLAUDE.md、AGENTS.md)中,告知 Agent 在使用 grep 或读取文件之前先调用 phpgraph;服务的 MCP 说明中也有同样的要求。

使用终端编码 Agent Sherpa 时,只需声明一次即可用于所有项目:Sherpa 会从其运行所在的项目启动 MCP 服务。

sherpa mcp add phpgraph -- ~/.local/bin/phpgraph serve

在 Sherpa 会话中,/mcp add 也能完成同样的操作,工具随即即可使用。两种方式都会写入 ~/.config/sherpa/mcp.json。

使用 Claude Code 时(使用绝对路径,与 mcp-config 打印的路径一致):

claude mcp add phpgraph -- /home/you/.local/bin/phpgraph serve /path/to/project

3. 向 Agent 提问,例如 “概述一下这个项目”、“订单是如何下单的?” 或 “如果我更改 OrderRepository::save,会有什么影响?”。首次调用时会构建图谱,之后会持续保持最新。

MCP 工具

工具用途
overview从这里开始:Composer 技术栈、包含各层的命名空间树、有界上下文、层规则违规、消息、HTTP 路由(每条包含方法、路径、控制器和文件,最多 30 条;超出则按路径前缀分组)、服务,以及图谱无法看到的内容。
query_graph当你不知道类名时,通过类名中的词查找某个主题的相关代码(例如,验证器会匹配到 Validator),并显示它所命名类周围的功能类。关于路由的问题("mooc courses routes"、"GET /courses")会返回匹配的路由及其控制器。答案周围的类群按文件夹计数(ContextRuleInterface: 34 in …),注入的列表只计一次(ContextValidator receives 34 ContextRuleInterface)。
outline解释某项功能前,先查看其结构大纲,以便只阅读少数包含不明显逻辑的文件。内容包括:按命名空间和层列出的组成类,以及其文档块的首句、公开签名、常量和枚举项;周围的接口和基类及其按模块列出的实现(0 表示已说明);逐条调用链中到达该功能的路由,包括它运行的闭包;从方法体读取的行为(受保护的抛出、提前返回、基于常量的分支、循环上限、前后状态比较);包含服务 ID 的容器装配;该功能的使用者、按模块统计的测试、没有测试涉及的内容,以及没有任何代码使用的内容。约 15 KB;如果列表被截短,会指出包含全部内容的章节(section behaviour,1 至 9 KB)。如果传入一个方法(OrderService::place),则改为展示它运行的内容:它调用的方法,最多深入 3 次调用,每个类只列出这些方法;统计枢纽(被 20 个或更多类调用的类,以及单例的 getInstance()),但不继续追踪;按从近到远的顺序展示其行为。
get_node查看一个类、方法、路由或频道及其所有关系;按模块统计使用它的测试。
get_neighbors查看使用某节点的内容(in),或该节点依赖的内容(out)。
impact_of修改前查看:每个依赖某个类或方法的类,按从近到远的顺序排列,并附上调用行;通过其实现接口的调用者;依赖其写入状态的代码;到达它的路由及每条调用链;以及通过测试辅助工具找到的待运行测试。它会标记没有测试涉及的类,以及应用中没有任何代码使用的类。
shortest_path查看两段代码如何关联。
god_nodes查找大部分代码所依赖的枢纽。

服务会告知 Agent 首先调用 overview。在 php-ddd-example 上,Agent 看到的路径如下:

Shortest path (4 hops):
  PUT /courses/{id} --handled_by--> CoursesPutController::__invoke()
    --dispatches--> CreateCourseCommand --handled_by--> CreateCourseCommandHandler::__invoke()
    --calls--> CourseCreator::__invoke()

命令行

phpgraph build [path]                        # phpgraph-out/graph.json and GRAPH_REPORT.md
phpgraph overview                            # stack, structure and gaps
phpgraph impact "OrderRepository::save"      # what depends on it, and the tests to run (--all, --section tests, --format json)
phpgraph check                               # layer rules, for CI: exit code 1 on a new violation
phpgraph explain "PlaceOrderHandler"         # a node and its relations (-d in|out)
phpgraph path "StockChecker" "DbalOrderRepository"
phpgraph query "how is stock checked"
phpgraph outline "notification validation"   # the outline of a feature (--section flow, --format json)
phpgraph serve [path]                        # the MCP server, over stdio
phpgraph mcp-config claude|codex|cursor|json [--docker image]

build 选项:-e 用于排除路径(可重复指定),--no-vendor 表示不读取依赖项,--no-cache 表示重新解析每个文件。vendor、node_modules、var、.git 以及项目自身 .gitignore 文件中忽略的文件都会被跳过;项目上层的 Git 仓库不会应用其规则,因此复制到忽略目录中的项目仍会被读取。当目录中有 PHP 文件,但全部被排除时,build 会失败并说明原因;每个 MCP 答案都会以该警告开头,而不是提供空图谱。默认情况下内存不设上限;PHPGRAPH_MEMORY_LIMIT=2G 可设置上限。安装 PHP 后,opcache 及其 JIT 可使构建速度提高约 20%:php -d opcache.enable_cli=1 -d opcache.jit=tracing -d opcache.jit_buffer_size=128M vendor/bin/phpgraph build(Docker 镜像和 mcp-config 打印的命令已启用这些选项)。

图谱包含的内容

关系来源 → 目标
defines、imports文件 → 类、use 语句
extends、implements、uses_trait类 → 父类、接口、trait
has_method、overrides类 → 方法;方法 → 它重写的方法
instantiates、referencesnew Foo();参数、返回值和属性类型,catch、instanceof、常量、属性
calls方法 → 解析出的被调用方法,并标注调用位置的行号;运行所接收闭包的方法($apply(...))会调用该闭包所调用的方法,INFERRED,通过 via 标明 closure of <the method writing it>
dispatches、handled_by发送方 → 消息或通道 → 处理程序;路由 → 控制器;由 supports() 方法测试的类 → 对应策略
contract一个服务发送的消息类 → 另一个服务处理的同一类
requestsHTTP 调用 → 它访问的路由(可以在同一服务或另一服务中),或另一 API 的 OpenAPI 文档中的操作
implemented_byAPI schema 操作(operation:)→ 对应方法和路径的路由(OpenAPI),或实现生成服务的类的方法(protobuf)
receives服务 → 容器配置注入其中的每个服务(标签、ID 或被装饰的服务);via 用于说明具体是哪一个:当一个类有多个服务时,使用 service sales.pipeline
reads_state_of方法 → 同一类中修改其读取属性的方法,构造函数之外(hasErrors() → add()),INFERRED

每种关系都带有一个置信级别:

级别含义
EXTRACTED在单个文件的代码或配置(属性、服务标签、路由文件)中直接读取。
INFERRED根据声明的类型跨文件解析:参数、属性、$this、继承、返回类型、instanceof 测试、代码结构。如果分支结束后某个变量可能持有多个类的实例,则将调用关联到每个类的方法。
AMBIGUOUS一种猜测,例如项目中唯一一个同名方法,但接收对象类型未知;或者变量可能持有某个类的实例,但另一个分支结束后其类型未知。
识别的框架和约定
处理程序和监听器发送的消息HTTP
Symfony#[AsMessageHandler]、#[AsEventListener]、标签 messenger.message_handler 和 kernel.event_listener(YAML、XML、PHP、_instanceof)、getSubscribedEvents()、addListener()MessageBusInterface、EventDispatcherInterface、命名事件#[Route](路径也可定义在类常量中)、YAML 和 PHP 路由文件、由服务 ID 命名的控制器;API Platform #[ApiResource] 和操作属性,由其 provider、processor 或控制器处理
Laravel$listen、Event::listen()、订阅者(subscribe() 中的 $events->listen(),或其返回的映射)、联合事件的监听器(handle(Accepted|Rejected $event))、作业(ShouldQueue、Dispatchable)event()、dispatch()、facade、Job::dispatch()Route::get/post/…、match、resource、view、redirect、带 prefix 和 domain 的分组、routes/api.php;Http:: 客户端,相对于其 baseUrl()
Ecotone#[CommandHandler]、#[EventHandler]、#[QueryHandler]、路由键CommandBus、EventBus、DistributedBus、sendWithRouting()
PrestaShop#[AsCommandHandler]、#[AsQueryHandler]CommandBusInterface::handle()YAML 路由
WordPressadd_action()、add_filter()do_action()、apply_filters()
自定义代码可按名称识别的处理程序属性和标签、__invoke/handle(Message $m)、*Handler 类*Bus、*Dispatcher、*Publisher 类型、聚合的 recordThat()、自定义总线封装器Guzzle、Symfony HttpClient、PSR-18
架构:分层、有界上下文、CI 中的规则 类的层级从其命名空间中读取:第一个显式层级段(`Domain`、`Application`、`Infrastructure`、`UI`、`Presentation`、`Port`、`Adapter`、`UseCase`);若没有,则读取最后一个通用层级段(`Model`、`Entity`、`Controller`、`Http`、`Persistence` 等)。限界上下文从层级之前读取(`CodelyTv\Mooc\Courses\Domain`),或者当项目将层级放在最前面时从其之后读取(`Core\Domain\Product`)。

phpgraph check 会将分层架构的默认规则应用于应用代码:domain 不得依赖 application、infrastructure 或 interface;application 和 port 不得依赖 infrastructure 或 interface。对于已有违规项的项目:

phpgraph check --generate-baseline   # accept today's violations in phpgraph-baseline.json
phpgraph check                       # fail on new ones only

项目自身的规则写在 phpgraph.yaml 中,并由同一命令使用同一基线检查。模式是类名上的 glob(* 匹配一个命名空间段内的内容,** 跨段匹配),以 \ 结尾的命名空间包含其中的所有内容:

rules:
  dependencies:                       # who may depend on what
    - name: doctrine-in-infrastructure
      dependency: 'Doctrine\'
      allow-from: ['App\*\Infrastructure\']  # or deny-from, which wins
      reason: Only infrastructure talks to the database.
  structure:                          # what the classes a pattern selects must be
    - on: 'App\**\Controller\*'
      not-on: 'App\Legacy\**'
      target: class                   # class, interface, trait or enum
      must-be-final: true             # must-be-abstract, must-be-readonly: true or false
      must-be-named: '*Controller'
      must-implement: ['App\Shared\Http\Controller']   # must-extend, must-use-trait
      only-public-methods: [__construct, __invoke]
  handlers:                           # a message handler dispatches the messages of its own bounded context
    own-context: true
    shared: [Shared]                  # contexts any handler may use

依赖仅包括应用代码中确定的依赖(EXTRACTED 和 INFERRED:类型、属性、new、调用、继承);继承关系从图中读取,因此无法获知 vendor/ 中类的父类。处理器和消息的限界上下文与层级一样,都是从其命名空间中读取。

微服务

包含两个或更多独立 Composer 应用的代码库会被拆分为多个服务。单体仓库中的包仍属于其项目:根目录自动加载、声明为路径仓库或通过 replace 替换的包是项目的一部分,而不是服务;服务所需的库(路径仓库或其包名)则是它们的共享代码。此时,ID 会带上所属服务,例如 billing@App\Domain\Order,名称绝不会解析到其他服务:服务通过契约交互。

  • 一个服务发送、另一个服务处理的同一消息类:contract;
  • 路由键(sendWithRouting('order.place', …)、#[CommandHandler('ticket.create')]、具名事件):所有服务共享的 channel: 节点;
  • 对另一服务路由的 HTTP 调用:requests。如果调用路径只匹配为其他 HTTP 方法声明的路由,则会报告该调用:按当前写法,它会失败;
  • API 架构:OpenAPI 文档(GET /api/hotels/{hotel})或 protobuf 服务(CinemaService.Reserve)中的 operation: 节点,由提供这些操作的路由或类通过 implemented_by 关联。代码库根目录中的文档描述所有服务(即 API 网关),服务目录中的文档则只描述该服务。

当自动检测不适用时,可选的 phpgraph.yaml 可用于指定服务:

services:
  - services/billing
  - services/shipping
自定义路由文件:XML 中的操作代码

旧版应用通常通过屏幕发送的操作代码(value="saveReservation")进行路由,并由自定义 XML 文件将其映射到控制器方法。phpgraph.yaml 用于描述格式,不会硬编码任何内容:

actions:
  - file: Framework/jacActions.xml
    element: //action                                 # XPath of the action elements
    code: code                                        # their attributes (these are the defaults)
    controller: controller
    method: method
    controller_file: '{dir}/control/{name}CTRL.php'   # _Stock/Reservation → _Stock/control/ReservationCTRL.php
    target: target                                    # the action or template it goes on to
    forward: { element: forward, result: result, target: target }
    cited_in: [html, js]                              # the screens quoting the codes

每个操作都是一个路由节点,例如 ACTION saveReservation,通过 handled_by 关联到其方法(EXTRACTED);如果方法继承而来,则通过 via controller 引用其控制器。其目标和转发通过 references 指向下一个操作或模板(_Stock/SO_080/Resa.html 会查找 _Stock/views/SO_080/Resa.html),并通过 via target 或 forward KO 标记。引用该代码(value="saveReservation"、'saveReservation&id=')的 cited_in 文件会指向该操作,且状态为 AMBIGUOUS:impact_of 会在路由下列出这些屏幕。对于普通单词形式的代码(default、login),不会进行查找;压缩文件也不会查找。overview 会统计操作数量,并列出缺少控制器或方法的操作;方法的 outline 会从其操作开始展示流程;query_graph("saveReservation routes") 可以找到该操作。controller_class: '{name}CTRL' 会按类名而不是文件查找类。

工作原理 1. 每个 PHP 文件都会被解析为事实:声明、调用(包括描述接收者的表达式类型)、消息发送和处理器、路由、HTTP 调用。 2. 构建器会跨文件解析这些事实:通过继承和返回类型确定类型,按需读取 Composer 元数据中的 `vendor/` 签名(只解析,绝不执行),并从容器配置中读取服务定义。 3. 图会保存为 `phpgraph-out/graph.json`,并生成供人阅读的 `GRAPH_REPORT.md`。

重建采用增量方式:按内容缓存文件的提取结果;如果没有任何声明发生变化,MCP 服务只会重新解析已更改文件中的调用。结果与完整构建完全一致。

在真实项目上的测量结果

phpgraph 基于一组固定到特定提交的开源项目进行开发,并通过这些项目的指标评估每项改动(composer corpus:measure)。仅统计应用代码;接收者类型已确定表示 phpgraph 能确定接收者类型的方法调用所占的比例。构建数据由 corpus:measure 测得,每次在 Docker 中测量一个项目(PHP 8.5,不启用 opcache),版本为 1.5.8。

项目类型PHP 文件数构建时间接收者类型已确定
php-ddd-exampleDDD,CQRS3040.8 s99.1 %
SyliusSymfony 电商4,94612.7 s98.6 %
Akeneo PIM六边形架构、有界上下文8,41618.7 s92.9 %
PrestaShopCQRS 和遗留代码7,85020.4 s96.5 %
Ecotone quickstart47 个服务、消息传递5831.1 s92.7 %
BookStackLaravel1,5136.6 s97.4 %
WordPress无框架、钩子1,8999.7 s97.7 %

语料库还包含四个微服务项目:两个通过 RabbitMQ 通信的 Laravel 服务,由 4 个消息契约关联;四个通过 HTTP 通信的 Laravel 服务,phpgraph 在其中发现了一个发送到仅支持 GET 的路由的 POST 请求;Spiral 的票务预订项目,服务通过 .proto 文件中声明的 gRPC 服务相互调用;以及 StayHub,七个 Laravel 服务位于由 OpenAPI 文件描述的 API 网关之后。后两个项目将 API 架构作为契约进行测量:StayHub 文档中的全部 45 个操作,以及 ticket-booking 中的全部 14 个 rpc,都关联到了其实现代码。约 26,500 个文件中没有解析失败。各项目的详细信息(包括消息、路由和服务)见 corpus/RESULTS.md。

与 Agent 配合使用

在一个包含 4,800 个文件的私有 Symfony/DDD 项目上测量,每种设置运行 3 次;使用相同的 Agent(Sherpa),分别测试不使用和使用 phpgraph 1.0.2 的情况:

问题类型使用 phpgraph质量
影响分析或重构(impact_of)令牌数减少 42 %,耗时减少 50 %,工具调用次数减少 59 %相同或更好
解释功能(outline)令牌数减少 25 %,耗时减少 34 %,工具调用次数减少 49 %;使用默认 outline 时令牌数减少 31 %相同

使用和不使用 phpgraph 时,各次运行的结果范围互不重叠。

与其他代码图的对比

Kimai 2.70.2(Symfony,1,072 个源文件和 761 个测试文件,不在 phpgraph 的测量语料库中),2026 年 10 月,不使用任何 LLM:phpgraph 1.5.2 和 graphify 0.9.93(extract --code-only),在同一台机器上,通过 Docker 运行。graphify 为每个调用方和被调用方保留一条边,因此按方法对比较项目方法之间的调用。差异样本已在代码中核查。tools/graphify/benchmark.sh 可再次运行基准测试。

phpgraphgraphify
构建(6 次运行的中位数;graphify 每次都慢 2.2 到 2.7 倍)11 s27 s
调用方和被调用方方法对,项目方法20,1957,615
仅此工具发现12,936356
方法名只声明一次的方法的调用方文件(grep 得到 2,408 个)2,407 个,无误报902 个,无误报
关联到订阅者的事件、分发26,1350,0
关联到控制器的路由2660
  • phpgraph 独有的调用对是根据已声明类型解析的,也包括继承方法和 instanceof:抽查的 14 个全部正确。即使方法名唯一,graphify 也会漏掉对局部变量和调用链的调用($sut->hasUser()、$this->getDateTimeFactory()->getStartOfMonth())。
  • graphify 的 356 个调用对中,有 243 个将 phpgraph 关联到同名另一个方法的调用关联了过去:抽查的 12 个中,phpgraph 有 11 个正确,graphify 在第 12 个上正确(phpgraph 根据声明的返回类型 AbstractWidgetType 跟踪到继承方法;该对象是一个覆盖了此方法的子类)。其余 113 个中,抽查的 12 个有 11 个错误:对另一个对象的调用被关联到当前类中同名的方法(Doctrine 的 $em->rollback() 被关联到 TimesheetRepository::rollback());第 12 个正确,这是对一个没有返回类型的辅助对象的调用,phpgraph 将其计为未知接收方。
  • phpgraph 在 2,408 个文件中唯一的漏项:$user->isFirstDayOfWeekSunday()。loadUserByIdentifier() 声明其返回 Symfony 的 UserInterface,但该接口没有此方法。
  • graphify 有而 phpgraph 没有的功能:支持其他语言(Kimai 的 JavaScript)、社区、交互式可视化,以及将文档纳入图中。

Sylius(语料库提交版本),2026 年 10 月,不使用任何 LLM:phpgraph 1.0.2、codebase-memory-mcp 0.11.0 和 graphify 0.9.76(--code-only),在同一台机器上,通过 Docker 运行。调用按位置逐一比较(文件、行、方法),差异样本已在代码中核查。

phpgraphcodebase-memory-mcpgraphify
构建10 s17 s32 s
关联到项目方法的调用位置22,87032,2744,005
其中根据类型解析22,7474,314—
找到的 OrderItemQuantityModifierInterface::modify() 调用方(代码中有 17 个)17 个,无误报11 个,2 个误报0 个,1 个误报
关联到消息的 Messenger 处理器20600
容器注入、路由1,360,1880,1 个(误报)0,0
  • codebase-memory-mcp 在其根据类型解析的调用中,有 99.7% 与 phpgraph 一致。其余边是根据方法名猜测的(置信度为 0.04 到 0.44):对于两者都关联的同一个调用,它们在 9,400 个位置上意见不一;额外的 11,300 条边大多是将对 vendor/ 的调用附加到同名的项目方法上(抽查的 20 个中有 19 个如此)。
  • graphify 会将 $this->other->foo() 关联到当前类的 foo():对 $this->foo() 来说正确;对抽查的 10 个差异全部错误。
  • codebase-memory-mcp 有而 phpgraph 没有的功能:支持其他语言、全文和向量搜索、Cypher 查询。

常见问题

phpgraph 会把我的代码发送到其他地方吗? 不会。它在本地运行,不会发起网络请求,也不使用大模型。

它会执行我的代码吗? 不会。它会解析 PHP 文件并读取 JSON、YAML 和 XML 配置。即使是 autoload_classmap.php 和 PHP 服务配置器,也只会被解析,不会被引入。

它能分析哪些 PHP 版本? 任何 nikic/php-parser 5 能解析的版本:PHP 7 和 8 的语法,以及 PHP 4 和 5 中已移除的语法($s{0}、=& new、08 这类八进制数)。对于最新语法无法解析的文件,它会尝试使用旧语法。phpgraph 本身需要 PHP 8.2 或更高版本。

它能与哪些 Agent 配合使用? 任何通过 stdio 通信的 MCP 客户端:Sherpa、Claude Code、Codex、Cursor、Claude Desktop 等。mcp-config 会输出适用于常见客户端的配置。

它能处理多大的项目? 语料库中最大的项目 Akeneo 有 8,400 个文件:完整构建约需 22 秒和 730 MB(在 Docker 中且未启用 opcache),编辑后重建约需 3 秒。

限制

  • 只读取 PHP:overview 会列出混合仓库中的其他语言(它们对 PHP 部分的调用不会建立关联),MCP 指令也会在项目完全没有 PHP 时立即说明。
  • 对全局函数的调用不会成为图中的边,不过函数的返回类型会决定对其返回值的调用类型(now()->addDay()、response()->json());动态调用($this->$name()、__call)只会通过类文档中说明的方法解析(@method、@mixin)。
  • 文档块由 phpstan/phpdoc-parser 读取:类型可以跨多行书写;单行文档块中的标签可以与另一个标签写在同一行;旧代码中虽不符合 PHPDoc 规范、但实际使用的写法会按其含义读取(不带返回类型的 callable(Foo $x)、?A|B、@method static NAME(): Type)。文档块中的泛型也会读取(@template、@extends Repository<Order>、@return Collection<int, Item>、ScalarNodeDefinition<$this>),包括项目和 vendor/ 中的文档块:模板会根据接收者的类型以及将其传递下去的父类绑定,foreach 会遍历任何带类型的集合中的元素。对于联合类型(Response|ResponseFactory)或交叉类型(Countable&Named),无论是文档声明还是代码声明,都会对每个声明了该方法的成员进行调用;传给方法的闭包会获得该方法文档所述的参数(@param callable($this): mixed、Laravel 的 when(fn ($query) => ...));在 trait 的使用处,@use HasFactory<HotelFactory> 会绑定其模板。方法的模板会根据每次调用的实参绑定:实参指定的类(带有 @param class-string<T> 的 getRepository(Order::class))、值的类型(@param T $value)、闭包的返回值(@param callable(): T、其 return 语句或原生类型)。@phpstan-return 和 @psalm-return 的读取优先级高于 @return;如果实参无法决定条件类型(TAsync is false ? Response : PromiseInterface),则取其各分支的联合类型。由 @property 和 @property-read 文档声明的魔术属性会延续调用链;对于 Eloquent 模型,转换规则('paid_at' => 'datetime' 表示 Carbon、枚举类型或转换类的 TGet)、时间戳以及作为属性读取的关联($page->book、book()->getResults() 的返回值)也会延续调用链。未声明返回类型的方法会返回其所重写方法声明的类型(@inheritDoc)。global $wpdb 的类型取自函数的 @global wpdb $wpdb;没有该标签时,类型仍为未知。不会计算类型条件($id is array ? Collection : Model),文档中返回类型为 mixed 的 facade 方法(DB::transaction())仍为未知。
  • 局部变量会根据函数各分支的执行情况追踪:if/elseif/else、switch、match、try/catch、三元表达式、?? 或循环汇合处的变量,会持有所有能到达该处的分支赋予的类型(以 return、throw、exit、break 或 continue 结束的分支不会到达该处,null 和标量不会增加类型)。instanceof、is_a()、assert()、&&/|| 和 match (true) 会在条件成立处收窄变量类型;否定测试后紧接退出(if (!$x instanceof Foo) { return; })会收窄其后代码中的类型。对这样的变量进行调用时,会关联到它可能持有的每个类的方法:如果每个分支都能确定类型,则为 INFERRED;如果有一个分支的类型仍未知,则为 AMBIGUOUS。调用会根据其可调用对象在项目或 vendor/ 中的文档收窄实参类型:包括 @phpstan-assert、@psalm-assert 及其 -if-true/-if-false 形式,也包括通过 class-string 实参绑定的模板(WordPress 的 is_wp_error()、Webmozart\Assert\Assert::isInstanceOf($x, Order::class)、PHPUnit 的 assertInstanceOf());只会查找名称类似断言的方法(is…、assert…、ensure…、check…、expect…、validate…、verify…,以及任何 …Assert 或 …Assertion 类的静态方法)。只会收窄变量,不会收窄属性($this->x instanceof Foo);循环体只读取一次(在循环末尾赋予的类型不会在循环开头被读取)。
  • 库对调用的处理方式若无法从签名中得知,则由其 provider 提供(src/Builder/Libraries):PSR-11 容器按类获取时会返回该类的实例($container->get(Mailer::class));Eloquent 模型的属性类型取自其转换规则、时间戳和关联。
  • 调用链会在魔术方法处,以及返回类型不可用的依赖处停止。PHP 自带的类(DateTime、ArrayObject、SplObjectStorage、PDO 等)依据一次性生成的签名(tools/internal-classes.php)识别;intl 和 mysqli 扩展中的类则不会识别。
  • 未安装 vendor/ 时,调用链会在遇到第一个依赖处停止。
  • 不会读取运行时构建的服务(编译器传递、bundle 扩展、根据仅在运行时才知道的值计算出的 ID)、XML 路由(但 phpgraph.yaml 中映射的 action 文件除外)、在 XML 或 YAML 中声明的 API Platform 资源,以及 OpenAPI 和 protobuf 以外的 API schema(GraphQL、AsyncAPI);不会跟踪经 gateway 重写的 OpenAPI 路径;无法建立关联的注入会由 overview 列出。
  • 假定两个服务声明的同名消息类是同一个类。

贡献

欢迎提交问题和拉取请求。请参阅 CONTRIBUTING.md:composer check 必须通过(php-cs-fixer、PHPStan 最高级别、PHPUnit),分析方面的更改则依据语料库进行评估。如果未安装 PHP,可通过 bin/dev composer check 在 Docker 中运行所有检查。

各版本间保持稳定的内容(命令、MCP 工具、graph.json 格式)列于 docs/STABILITY.md;更改记录见更新日志。

发布版本时,只需推送一个版本标签:发布工作流会构建 PHAR、适用于 Linux、macOS 和 Windows 的可执行文件(tools/build-binary.sh),以及 Docker 镜像 ghcr.io/hbenabdallah/phpgraph。

许可证

phpgraph 基于 MIT License 开源:你可以出于任何目的使用、复制、修改、合并、发布、分发、再许可和销售,但必须随附版权声明和许可证文本。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。