phpClaw 拆解:PHP 项目挂一个 AI Agent 要几步,以及为什么 8 个安全守卫比 Agent 本身更值得看

阅读时长:20分钟

PHP 程序员想给项目加个 AI Agent,通常的路径是这样的:装一个 LLM SDK,拿到一个 HTTP 客户端,然后自己写 tool-calling 循环。

循环写完了,问题才开始——工具结果要不要过滤?提示词注入怎么防?记忆存哪?流式输出和工具调用能不能同时跑?

2026 年 10 月底,Hacker News 上出现一个项目,自称把这些全做完了:phpClaw。

我读了它的 README、Packagist 元数据和 HN 上的讨论。结论是——它解决的不是"PHP 能不能调 LLM",而是"PHP 项目里那个 Agent 循环,谁来维护"。

这篇把它的机制拆开,重点讲三个东西:工具怎么注册、安全守卫怎么工作、以及它和通用 LLM SDK 的真实边界。

TL;DR

  • phpClaw 是 MIT 开源的 PHP Agent 运行时,核心零框架依赖,PHP 8.1+ 即可
  • 一个 composer require + 一行 builder + 一次 send(),就是完整 Agent
  • 8 个默认安全守卫才是重点:prompt injection / PII / 代码注入 / Unicode / homoglyph / 角色切换 / 破坏性 SQL / 消息长度
  • ShellTool 白名单硬编码不可覆盖,HttpTool 校验解析后的 IP(堵 DNS rebinding)
  • 明确不做 RAG:没有 embeddings、没有向量库

PHP 项目里的 Agent 循环,为什么会变成祖传代码?

先看不用任何框架要写什么。一个最小的 tool-calling agent,PHP 里大致长这样:

<?php
// 手写 tool-calling 循环的骨架(示意)
function runAgent(array $messages, array $tools, callable $dispatch): string
{
    for ($i = 0; $i < 20; $i++) {
        $resp = $http->post('https://api.anthropic.com/v1/messages', [
            'messages' => $messages,
            'tools'    => $tools,
        ])->json();

        if (!empty($resp['content'])) {
            foreach ($resp['content'] as $block) {
                if ($block['type'] === 'tool_use') {
                    // ← 这里:工具结果要不要过滤?
                    $result = $dispatch($block['name'], $block['input']);
                    $messages[] = ['role' => 'user', 'content' => json_encode($result)];
                }
            }
        } else {
            return $resp['content'][0]['text']; // ← 循环出口
        }
    }
    throw new RuntimeException('iteration cap');
}

这段代码的问题不在写,在维护。

  • 改一个系统提示词 → 动这个文件
  • 加一个工具 → 动这个文件
  • 换模型 → 动这个文件
  • 加一层防注入 → 还是动这个文件

Agent 一复杂,这个文件就没人敢碰了。phpClaw 在 README 里把这句话说得很直白:

Every “AI in PHP” tutorial ends the same way: an HTTP client bolted onto a provider API, a hand-rolled tool-call loop, and a prayer that the prompt holds up in production.

翻译过来就是:所有"PHP 接入 AI"的教程,结局都一样——HTTP 客户端焊在 provider API 上,手写一个工具循环,然后祈祷提示词在生产环境能撑住。

phpClaw 的定位是把"runtime"这一层抽出来:循环、守卫、记忆、框架接线,全部装好。你只写业务工具。

1 分钟跑起来:安装、builder、send

按官方 README,最小可用路径是三步:

composer require phpclaw/phpclaw
export ANTHROPIC_API_KEY="sk-ant-..."
<?php
require 'vendor/autoload.php';

$agent = PhpClaw\Claw::builder()->build();
echo $agent->send('What can you help me with?')->text;

一个环境变量,一个 builder,一次 send()。

builder 的 provider 探测顺序,README 写得很明确——按这个顺序找环境变量,用第一个找到的:

顺序 环境变量 Provider
1 ANTHROPIC_API_KEY Anthropic
2 OPENAI_API_KEY OpenAI
3 GROQ_API_KEY Groq
4 GEMINI_API_KEY Gemini
5 MISTRAL_API_KEY Mistral
6 DEEPSEEK_API_KEY DeepSeek
7 OLLAMA_HOST Ollama

想换 provider 不改代码,用 ->provider() + ->model()。想加 fallback,->withFallback('ollama', 'qwen2.5:7b')——主 provider 挂了自动切。

还有个细节:README 里写明自定义 OpenAI 兼容端点是 opt-in,要设 PHPCLAW_PROVIDER=custom。也就是说默认不假设你在用什么野鸡网关。

核心依赖有多轻

按 Packagist 的元数据,phpclaw/phpclaw 最新版 v0.1.5 的 require 只有三项:

{"php": "^8.1", "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*"}

三个扩展,没了。README 里那张对比表的最后一行写的是:

Zero framework deps in core: ext-curl, ext-json, ext-mbstring, done.

这个"done"不是客套。它意味着你可以在一个没有任何框架的裸 PHP 项目里跑 Agent——不需要 Laravel,不需要 Symfony,不需要 DI 容器。这对那种有 2005 年历史、composer.json 都不存在的 PHP 项目,是唯一现实的接入路径。

工具怎么注册:五个内置工具 + 白名单

phpClaw 内置的工具集,README 点名的是这几个:

use PhpClaw\Tools\ShellTool;
use PhpClaw\Tools\HttpTool;
use PhpClaw\Tools\FileReadTool;
use PhpClaw\Tools\DatabaseQueryTool;
use PhpClaw\Tools\CodeSearchTool;

$agent = Claw::builder()
    ->tools([
        new ShellTool(allowlist: ['ls', 'df', 'tail']),
        new HttpTool(),
        new FileReadTool(),
        new DatabaseQueryTool($pdo),
        new CodeSearchTool(),
    ])
    ->build();

配这几个工具,Agent 就能干这些活(README 原文给的示例问句):

工具 能干什么
ShellTool “Check disk usage and tail the last 20 lines of the error log”
HttpTool “Is api.example.com responding right now?”
DatabaseQueryTool “Run a read-only SELECT to count today’s orders”
CodeSearchTool “Find every place UserService is used in this codebase”
FileReadTool 读工作区内的文件

关键机制:Agent 自己决定调哪个工具、按什么顺序调,跑一个 ReAct 循环(reason → act → observe → repeat),直到有答案或者撞上迭代上限。README 写明 max_iterations 默认 20。

这个循环对 shell、数据库、HTTP、代码库是同一套逻辑——工具是注册进去的,循环不关心工具内部怎么实现。

ShellTool 的白名单是硬编码的

这是我最关注的部分,因为它是 Agent 安全的第一道闸。README 的 Security 段写得很具体:

ShellTool runs only commands on its allowlist (default ls, pwd, df, wc, date, uptime, hostname, whoami), matched exactly and case-sensitively; a fixed, non-overridable blocklist of dangerous commands is checked first regardless of what’s allowlisted.

拆开三个要点:

  1. 默认白名单只有 8 个命令:ls pwd df wc date uptime hostname whoami
  2. 精确匹配 + 大小写敏感——不是前缀匹配,ls 不会放行 lsalias
  3. blocklist 先检查,且不可覆盖——就算你把 rm 加进白名单,也先被 blocklist 拦掉

第 3 点是设计上的硬约束。大多数"可配置白名单"的框架,管理员误配就能把危险命令放进来。phpClaw 把 blocklist 放在白名单判断之前,且做成不可覆盖——这是防御性设计,不是灵活性取舍。

HttpTool 防的是 DNS rebinding

README 原文:

HttpTool blocks requests by hostname prefix, then re-validates every resolved IP address, catching CGNAT and DNS-rebinding attempts that hostname checks alone would miss.

先按 hostname 前缀拦,再校验解析后的每一个 IP。

为什么要这一步?因为 hostname 检查有个经典漏洞:第一次解析返回合法 IP(通过检查),第二次解析返回内网 IP(DNS rebinding)。只校验 hostname 的框架会被这招绕过。phpClaw 的做法是解析后逐个校验 IP,堵住 CGNAT 和 rebinding。

这个细节在 README 里只有一句话,但它是判断一个 Agent 框架"有没有真正想过安全"的试金石。

文件工具是沙箱隔离的

FileReadTool / FileWriteTool 按 README:

sandboxed to a configured workspace root and refuse credentials, keys and security directories (.git, .ssh, wp-admin, …) unconditionally, and vendor/ and node_modules/ by default.

无条件拒绝 .git、.ssh、wp-admin,默认拒绝 vendor/ 和 node_modules/。

注意"unconditionally"和"by default"的区别——敏感目录是硬拒,依赖目录是默认拒但可配。这个分层是合理的:.ssh 里的东西永远不该给 LLM 看,但 vendor/ 在某些代码搜索场景下是有用的。

8 个默认安全守卫:这才是重点

phpClaw README 里最该被圈起来的数字是 8。

8 security guards on by default (prompt injection, PII, code injection, Unicode, homoglyph, role-switch, destructive SQL, message length), plus rate limiting available as an opt-in guard.

守卫 防什么
prompt injection 用户输入里藏指令
PII 个人身份信息外泄
code injection 代码块注入
Unicode 异常 Unicode 字符(零宽、控制符)
homoglyph 同形异义字攻击(用西里尔字母冒充拉丁字母)
role-switch 试图切换模型角色
destructive SQL 破坏性 SQL(DROP / TRUNCATE 等)
message length 消息长度超限

默认全开,不用你一个个打开。rate limiting 是唯一 opt-in 的。

还有一个容易忽略的设计:

Tool results are sanitised unconditionally by ToolOutputGuard, which sits outside the guard registry.

ToolOutputGuard 在守卫注册表之外,无条件执行。

这句话的含义很重要:其他 8 个守卫是注册式的(可以注册自定义的),但工具输出净化是独立于注册表、强制生效的。也就是说你无法通过"不注册某个 guard"来绕过它。

LLM 输出净化同样默认开启,可以用 ->sanitiseOutput(false) 关掉——但工具输出净化没有这个开关。

44 个 hook

README 的架构图里写着:

Message
  → Guards       (blocks injection/PII before the LLM sees it)
  → Hooks         (lifecycle events fire, 44 across the loop)
  → Agent          (ReAct loop: provider + tools + memory + skills)
  → AgentResponse  (text, tokens, tool calls, timing)

44 个生命周期 hook 贯穿整个循环。这个数字说明它的扩展点不是"加个工具"这么简单,而是可以在循环的几乎每一步插逻辑。

五个静态注册表

README 写明扩展机制:

Five static registries (GuardRegistry, HookRegistry, MemoryRegistry, ProviderRegistry, SkillRegistry) plus the per-agent ToolRegistry instance. Drop a class in packages/core/src/<Subsystem>/ with the matching attribute (#[Tool], #[Provider], #[Memory], #[Skill], #[Hook], #[Guard]), run composer dump-autoload, it’s live.

PHP 8 attribute 驱动——写个类,打上 #[Tool] 或 #[Guard],composer dump-autoload,就生效了。这是 PHP 8.1+ 的原生 attribute 语法,不需要写配置文件。

8 个框架适配器:CMS 为什么走 ZIP

phpClaw 支持 8 个平台,但安装方式分两类:

平台 包 安装方式 版本
Laravel phpclaw/phpclaw-laravel Composer 10, 11, 12, 13
Symfony phpclaw/phpclaw-symfony Composer 6.4, 7.x, 8.x
Drupal phpclaw/phpclaw-drupal Composer 10, 11
Magento phpclaw/phpclaw-magento Composer 2.4.x
WordPress + WooCommerce — ZIP 上传 6.2+
Joomla — ZIP 上传 4, 5, 6
OpenCart — ZIP 上传 3.x, 4.x
PrestaShop — ZIP 上传 8.x, 9.x

README 解释了为什么这么分:

CMS adapters ship as ZIP downloads from Releases, not Composer, because that’s how their host platforms install extensions.

CMS 平台就是这么装插件的——WordPress 后台点上传,Joomla 后台点安装。让它们走 Composer 等于让用户去改生产站的构建流程。

每个适配器接进宿主的惯用机制:Laravel 用 Eloquent,Symfony 用 Doctrine,WordPress 用 WP-CLI,Drupal 用 Drush。底层是同一套 3 表记忆 schema。

命令行也统一了:

php artisan phpclaw "deploy staging and notify #ops"          # Laravel
bin/console phpclaw "check all database connections"          # Symfony
drush phpclaw:run "list recently-active users"                # Drupal
bin/magento phpclaw:run "check order-queue status"             # Magento
wp phpclaw send "summarise today's posts"                     # WordPress

MCP server:把 PHP 工具暴露给 Claude Desktop

composer require phpclaw/phpclaw-mcp

README 原文:

phpclaw/phpclaw-mcp exposes your existing ToolRegistry over the Model Context Protocol, so Claude Desktop, Claude Code, Cursor, or any MCP-compatible client can call your tools directly, with the default guards scanning every tool call’s arguments.

关键点:默认守卫会扫描每一次工具调用的参数。

这里有个诚实的边界说明——README 主动指出:

the prompt-only code-injection, PII and length guards apply to chat messages, not tool arguments

也就是说,code-injection / PII / 长度这三个守卫是只针对聊天消息的,不覆盖工具参数。工具参数走的是另一套检查。这个区分写得透明,比"全面防护"这种话实在。

它明确不是什么

phpClaw README 里有一句自我限定,我觉得比功能列表更有信息量:

Not trying to be everything. phpClaw isn’t a RAG framework: there are no embeddings or vector stores. It’s built to give an LLM tools, memory and security controls against your real application, and to chain those agents into pipelines and graphs when one call isn’t enough.

不做 RAG。没有 embeddings,没有向量库。

为什么这条重要?因为"Agent"这个词现在被用烂了,很多框架把 RAG、Agent、workflow 混在一起卖。phpClaw 明确划开:我管工具、记忆、安全,RAG 你自己搭。

需要 RAG 的场景(比如"从 10 万篇文档里找答案"),phpClaw 不是答案。需要"让 LLM 操作你的数据库、跑你的命令、调你的 API",它才是。

和通用 LLM SDK 的真实边界

README 里那张对比表是官方立场,我按它拆:

维度 phpClaw 通用 LLM SDK
tool-calling 循环 ReAct,内置 你自己写
框架原生适配器 8 个,按框架惯用法 无
prompt injection 防御 8 默认守卫 + 可选限流 无
记忆抽象 可换驱动 无
流式 + 工具调用同时 hybrid 模式 看 SDK
内置 MCP server 暴露任意 app 给 Claude Desktop 无
chains / 多 agent 图 Pipeline + Graph 你自己搭
核心框架依赖 ext-curl / ext-json / ext-mbstring 看 SDK
核心协议 MIT,无功能锁定 看 SDK

通用 LLM SDK 给你什么:一个 HTTP 客户端。它能调 API,返回文本。

phpClaw 给你什么:客户端外面那一圈——循环、守卫、记忆、框架接线。

所以这不是"二选一"。你完全可以在已有 SDK 的项目里,只把循环和守卫换成 phpClaw 的 core(它零框架依赖,理论上能塞进任何 PHP 项目)。

Pipeline 和 Graph

README 提到两个进阶能力:

  • Pipeline:链式步骤(prompt 模板、agent、结构化输出、你自己的 callable),带 retry 和 fallback
  • Graph:节点可以循环、分支、暂停等人工、在另一个进程里恢复、fan-out 给 cron / 队列 / CLI 的 worker

“暂停等人工、另一个进程恢复"这条值得注意——它解决的是 Agent 长任务里最烦的问题:跑到一半要人确认,但进程不能一直挂着。phpClaw 把这个做成框架能力,不是让你自己写状态机。

我的判断:值得看的是守卫,不是 Agent

读完材料,我的结论是——phpClaw 的价值排序里,8 个守卫排在 ReAct 循环前面。

理由:

  1. ReAct 循环已经不稀缺。网上教程一大把,Copilot 都能给你写。
  2. 守卫是真门槛。prompt injection、homoglyph、DNS rebinding——这三个词能同时出现在一个 PHP 项目的 README 里,说明作者真的想过"Agent 接进生产系统会发生什么”。
  3. ToolOutputGuard 独立于注册表,这个设计决策比"支持 8 个 provider"重要得多。

适合的场景

  • 老 PHP 项目(WordPress 站、Laravel 后台、自研 CMS)想加 Agent 能力
  • PHP 团队不想引入 Python 服务,Agent 和主业务同栈
  • 需要 MCP:想把 PHP 工具暴露给 Claude Desktop / Claude Code
  • 需要安全基线:默认 8 守卫 + 不可覆盖的 blocklist

不适合的场景

  • 需要 RAG:没有 embeddings 和向量库,得自己搭
  • PHP 版本 < 8.1:不支持
  • 复杂分支编排:Graph 有循环和分支,但如果你的流程需要精细到"第 7 步失败时回退到第 3 步并重试两次",还是得看 Graph 能不能表达
  • 想要 100% 控制循环:它的循环是框架管的,改不了内部行为

一个现实顾虑

HN 讨论里有一条评论值得摆出来(HN item 50293361,12 points):

I will say I’m a bit sceptical to the general quality and probably sloppiness of this based on the 300k loc initial commit which sets off some warning signs for me.

“30 万行代码的首次提交"让人警觉。

这条评论点出了一个真问题:一个 2026 年 9 月 23 日才创建的仓库,首次提交就是 30 万行——这个体量不像手写,更像生成或搬运。仓库目前 24 stars,Packagist 最新 v0.1.5。

我的看法:这种项目值得看设计,不值得直接上生产。先读它的守卫实现和 blocklist,学到东西;真要在生产用,先做一轮代码审计。README 里也写了漏洞报告走 SECURITY.md、不要开公开 issue——说明作者至少知道这个流程。

常见问题

Q1: phpClaw 收费吗?

A: 核心 MIT 协议,README 写明"nothing gated”(无功能锁定)。Packagist 上 phpclaw/phpclaw 可直接 composer require。有个可选的 cloud transport(托管的、持续更新的 prompt-scan 守卫),那是增值服务,核心不依赖它。

Q2: 我的项目是 WordPress,能用吗?

A: 能。WordPress 适配器走 ZIP 上传(后台安装),不需要 Composer。支持 WordPress 6.2+ 和 WooCommerce。命令是 wp phpclaw send "summarise today's posts"。

Q3: 支持哪些 PHP 版本?

A: 8.1 / 8.2 / 8.3 / 8.4。README 写明"Tested across every supported version"。低于 8.1 不支持。

Q4: 能换模型吗?

A: 能。->provider() + ->model() 切换,其他代码不用改。7 个 provider 按环境变量自动探测,顺序是 Anthropic → OpenAI → Groq → Gemini → Mistral → DeepSeek → Ollama。自定义 OpenAI 兼容端点要设 PHPCLAW_PROVIDER=custom。

Q5: ShellTool 会不会被误配成危险命令?

A: 不能。默认白名单只有 8 个只读命令,精确匹配 + 大小写敏感。危险命令 blocklist 在白名单判断之前检查,且不可覆盖——就算你把 rm 加进白名单也会被拦。

Q6: 我想做 RAG,phpClaw 够用吗?

A: 不够。README 明确写"it isn’t a RAG framework: there are no embeddings or vector stores"。它管工具、记忆、安全;向量检索和 embeddings 要你自己搭。

Q7: 它和 LangChain PHP 之类的库是什么关系?

A: 竞争关系,但定位不同。phpClaw 强调"零框架依赖的 core + 8 个适配器",走的是 runtime 路线(循环、守卫、记忆装好)。选它的主要理由不是功能多,是PHP 原生 + 安全基线 + 不锁定框架。

相关阅读

© 2026 softon.top

本站已稳定运行 307 天 9 小时 · 147 篇文章

使用 Hugo 构建 主题 Stack 由 Jimmy 设计 由 softon 魔改

最近构建时间:2026-11-04 17:21:05 CST