故障排查
先找到与现象匹配的条目。示例假设你在项目根目录执行命令,并已安装 Composer。
安装与命名空间
Class "Pure\..." not found 或 Call to undefined function Pure\...()
使用库之前先加载 Composer 自动加载器,并确认运行时满足项目声明的 PHP 下限:
<?php
require __DIR__ . '/vendor/autoload.php';
use function Pure\HTML\{div, h1};
echo div(h1('Hello'))->render();项目要求 PHP 8.1 或更新版本。div() 等辅助函数需要 use function; Pure\Core\Slot、Pure\Component\Call 等类需要 use。复制的示例若省略 自动加载器,就不是独立可运行的脚本。
找不到 vendor/bin/pure
在通过 Composer 安装了本包的应用里,composer install 会创建 vendor/bin/pure 代理,在项目根目录执行它即可:
composer install
vendor/bin/pure --help在本仓库的克隆里没有 vendor/bin/pure:Composer 只为已安装的依赖包创建这个 代理,请在仓库根目录改用 php bin/pure。
示例同样依赖根目录的 vendor/autoload.php;从其它工作目录启动文件不会改变 这个要求。
no *.shape.php or *.cmp.php files found in '...'
两个命令都只按文件后缀发现单元,此外没有任何别的规则:组件单元是 *.cmp.php, 低层模板是 *.shape.php。把文件命名为 Card.php 在运行时完全正常——后缀只是 编译器和检查器找到它的方式——所以项目在第一次 pure compile 之前看上去都是健康的。
重命名文件,或者直接传真实路径,这样错误信息会指名文件:
mv components/Card.php components/Card.cmp.php
vendor/bin/pure compile components文件确实存在但后缀不对,直接传路径时会报下面这条;目录里只有错误的后缀时报上面 那条 no ... files found。两者是同一个错误的两面。
'...' is not a *.shape.php or *.cmp.php file.Slot 与绑定错误
MissingSlotException: slot '...' is required but was not provided
模板读取了必填键,但渲染数据里没有它。请在调用处补上键;如果缺失确实合法, 把 Slot 标为可选:
Slot::value('title')
Slot::value('title')->required(false)
Slot::value('title')->default('Untitled')必填的 value/raw Slot 也会拒绝显式 null。请传入真正的文本,或改用可选 Slot; 不要用 null 表示“缺失”。Slot 参考见 编译 API 与 Props 与 Slot。
child 或 each Slot 的值被拒绝
Slot::child() 有结构约束:child 作用域必须是数组。each 的每一项同样是作用域, 唯一例外是条目 Shape 只渲染一个 Slot —— 此时该项可以直接是该 Slot 的值, 字符串列表无需再包一层。条目 Shape 读取多个 Slot,或唯一的 Slot 是嵌套的 Slot::child() / Slot::each() 时,每一项仍然必须是数组:
$rows = array_map(
static fn (array $record): array => ['title' => $record['title']],
$records
);
echo $page(['rows' => $rows]);报错信息会列出条目 Shape 读取的键,照着它构造即可。已经渲染好的字符串列表应放入 Slot::raw(),而不是 Slot::each()。
出现 unknown prop、missing prop 或 did you mean
存在 prepare() 钩子时,钩子参数就是完整的链式 prop 契约。按精确名称传入所有 必填参数,并删除未知 setter:
register(Card(...),
factory: static fn () => div(...),
prepare: static function (string $title, string $class = 'card'): array {
return ['title' => $title, 'class' => $class];
}
);
echo Card()->title('Welcome')->class('card');没有 prepare() 时,setter 名称本身就是 bindings。
看到哪一条消息取决于错在哪。未知 setter 会先被报出,并附带一条针对"只差一次编辑" 的那个 prop 的 did you mean——所以必填 prop 拼错时读到的是 unknown prop 'titel' (did you mean 'title'?) 而不是 missing prop;同一次调用里的 每个未知 prop 都有各自的建议。等到报 missing prop 时,setter 名称是对的,只是值没传。
修改单元后运行 vendor/bin/pure check components,尽早发现不匹配:它报的是同一份 契约,并带上出问题的那个 ->prop(...) 所在行号。
子内容消失,或提示模板没有 children Slot
children 要传给调用函数,而不是调用 children() setter。模板必须显式读取保留 Slot:
function Button(mixed ...$children): Call
{
return component(__FUNCTION__, ...$children);
}
register(Button(...), static fn () =>
button(Slot::raw('children'))->class('btn')
);
echo Button('Save');没有 Slot::raw('children') 的模板按设计会拒绝非空 children。反过来也会悄悄丢失 内容:调用函数没有声明 mixed ...$children 时,调用传入的参数会被忽略,因为 PHP 允许 向用户函数多传参数。模板确实读取了该 Slot 而 children 仍然缺失时,声明它并转发给 component()。pure check 检查的是 prepare() 与 ->prop() 链,不读调用函数,因此 不会报告这种情况。
转义与标记
标记被当成转义文本显示
普通字符串子节点就是文本。如果内容确实要作为标记输出,请把 Pure\Core\Markup 值(例如 Raw::of($trustedHtml) 或另一个组件 Call)送入 raw Slot:
use Pure\Core\Raw;
return ['body' => Raw::of($trustedHtml)];用户文本使用 Slot::value()。不要因为参数标了 #[Trusted] 就把请求数据当成可信 标记;该注解只表达信任边界,不会替你清理输入。
产物、缓存与性能
输出是旧的,或产物提示运行 pure compile
修改源单元或升级库后重新生成产物:
vendor/bin/pure compile components
vendor/bin/pure compile --plain components
vendor/bin/pure compile --check --plain components产物必须不早于源文件,并返回 Pure\Compile\Renderer。缓存条目过期时,用 Compile::clearCache() 或只删除库自己管理的缓存目录;不要手改生成文件。
每个请求都在重新构建 Shape
static 变量的生命周期只有一个 PHP 进程。PHP-FPM 下应在 bootstrap 中启用磁盘 渲染器缓存,或随应用发布新产物:
use Pure\Compile\Compile;
Compile::cachePath(__DIR__ . '/var/cache/purephp');缓存目录应私有且位于 Web 根目录之外。Compile::flush() 只清理内存中的渲染器, 不会删除缓存文件。详见编译渲染与产物与部署。
pure compile --list 出现不熟悉的单元类型
列表描述的是发现的文件:
(component)是已注册的*.cmp.php单元;(shape)是独立的*.shape.php模板;(template)是标记了#[Template]的函数。
没有 (page) 类型。页面可以是根节点为文档根的组件单元,也可以由调用方渲染独立 模板。
plain 视图缺失或仍然调用 PurePHP
显式生成它:
php bin/pure compile --plain examples/bootstrap*.plain.php 只包含标记与原生 PHP。加载器必须先把 bindings 提取为局部变量再 require;参见示例与产物与部署。
组件与 PHPStan 边界
PHPStan 报 unknown component 'X'
Pure\StaticAnalysis\UnknownComponentRule 会在完整项目分析中把字面量组件调用 和注册项进行比较。请在被分析项目中用 register(Icon(...)) 注册单元,或使用与 调用函数一致的名称。路径和 component(__FUNCTION__) 会按文件/函数上下文解析。
如果规则没有生效,请在自己的 phpstan.neon 中注册它,配置见 组件 API。该规则是项目级检查,请分析整个项目而不是 单个文件:只有所有文件都分析完成后,注册项才完整。
pure check 还会加载发现的单元文件并调用 factory 来校验契约,因此只应对可信项目代码运行, 并保持 factory 没有副作用。
Registry 标为 @internal
Pure\Component\Registry 是公开 register() 与 component() 函数使用的实现注册表。 应用代码应使用这些辅助函数,不要依赖 Registry 的方法签名。公开组件入口汇总见 组件 API。
HTTP 与示例
HTMX 响应在片段前包含完整页面
HTMX endpoint 只应返回客户端请求的片段。按请求方法分支并只输出一次替换标记; 不要先打印完整文档再追加片段。对比两种渲染路径时,保持 /pure 与 /plain 路由 彼此独立。
本地示例返回 404
从示例文档指定的目录启动,并使用对应的 front controller:
php -S localhost:8000 -t examples/bootstrap/public examples/bootstrap/public/index.php
php -S localhost:8000 -t examples/event-counter/public examples/event-counter/public/index.php
php -S localhost:8000 -t examples/xml/public examples/xml/public/index.php路由表见示例。404 响应也会列出当前示例支持的路由。
快速诊断清单
- 确认已加载
vendor/autoload.php,且 PHP 为 8.1 或更新版本。 - 运行
vendor/bin/pure compile --list <path>,确认实际发现的单元类型。 - 调试生成结果前先运行
vendor/bin/pure check <path>。 - 修改源文件或依赖后重建
*.pure.php与*.plain.php。 - 对照模板必填 Slot 检查数据键;显式
null不能代替必填值。 - 先用示例中的最小复现,再逐项加入功能。