Composer 不提供打 PHAR 包功能,需用 PHP 的 Phar 扩展手动构建;关键步骤包括:准备入口脚本、生成确定性自动加载映射、排除无关文件、设置 stub 与压缩、签名及权限配置。

先说一个核心事实:Composer 本身并不负责打包。它只是一个顶级的依赖管理器,帮你把项目所需的库规整到 `vendor` 目录里。真正能把整个项目“装进”一个可执行 PHAR 文件的,是 PHP 自带的 Phar 扩展。而像 composer install 或 composer dump-autoload 这些命令,充其量只是为打包做前期准备——比如生成自动加载器、固化依赖路径,它们离生成最终的 `.phar` 文件还差着关键一步。
为什么不能直接用 composer build-phar?
很简单,因为这个命令根本不存在。Composer 的设计初衷里就没有内置 PHAR 构建逻辑,社区也从未将其纳入核心功能。不少人会误解,以为执行了 composer install --no-dev --optimize-autoloader 就等于在“打包”,其实这只是在生成一个更紧凑的 vendor/autoload.php 和一系列自动加载映射文件,距离一个独立、可移植的 PHAR 包还差很远。
真正的 PHAR 打包,需要你显式地完成下面几件事:
- 创建
Phar对象、往里添加文件、设置启动存根(stub)、可选地进行签名和压缩。 - 确保入口脚本(比如
bin/myapp)能被 PHAR 正确识别为命令行入口,并且不再依赖外部的vendor/目录。 - 让自动加载器适配 PHAR 环境:要知道,
__DIR__在 PHAR 内部会指向 `phar://` 协议路径,原来那些硬编码的相对路径很可能会失效。
如何让 Composer 项目支持 PHAR 打包?
所以,关键不是“用 Composer 去打包”,而是“如何让一个由 Composer 管理的项目,能被安全、完整地打包进 PHAR 里”。这通常需要三步准备:
- 改造入口脚本:确保你的入口脚本(如
bin/console)以#!/usr/bin/env php开头,并在脚本开头加入Phar::mapPhar()和类似include 'phar://myapp.phar/vendor/autoload.php';的语句(或者使用Phar::loadPhar())。 - 生成确定性依赖:运行
composer install --no-dev --optimize-autoloader --classmap-authoritative。这个命令会生成一份确定性的、不依赖反射的自动加载映射,这对 PHAR 的稳定运行至关重要。 - 固化项目自身加载规则:在
composer.json中明确定义"autoload": {"psr-4": {"App\": "src/"}},然后执行composer dump-autoload --classmap-authoritative,避免 PHAR 在运行时再去动态查找类文件。
如果跳过这些步骤,PHAR 内部的类加载大概率会失败,报出类似 Class "AppConsole" not found 或 require(): phar://xxx.phar/vendor/autoload.php: failed to open stream 的错误。
立即学习“PHP免费学习笔记(深入)”;
Phar::buildFromDirectory() 常见陷阱
这是最常用,但也最容易出错的打包方式。它会递归扫描整个目录并加入所有文件,但默认行为里藏着不少“坑”:不处理符号链接、忽略隐藏文件,而且最关键的是,它不会自动帮你重写文件内的路径。
- 必须手动排除无关文件:像
.git、tests/、docs/这些非运行时必需的目录,必须手动排除,否则 PHAR 体积会毫无意义地暴增,甚至可能引入敏感信息。 - 必须设置存根(Stub):一定要调用
$phar->setStub($phar->createDefaultStub('bin/console')),否则执行php myapp.phar时会直接报Phar: invalid stub。 - 必须启用缓冲才能压缩:压缩和签名功能需要在缓冲区内进行。必须成对调用
$phar->startBuffering()和$phar->stopBuffering()。如果直接在buildFromDirectory()后调用压缩,是无效的。 - 注意运行时依赖:如果使用了
gz压缩,那么运行这个 PHAR 的 PHP 环境必须启用zlib扩展,否则解包会失败。
一个关键的操作示例如下:
$phar = new Phar('myapp.phar');
$phar->startBuffering();
$phar->buildFromDirectory('build/', '/.php$/'); // 只添加 PHP 文件
$phar->setStub($phar->createDefaultStub('bin/console'));
$phar->compressFiles(Phar::GZ);
$phar->stopBuffering();
PHAR 签名与执行权限问题
没有签名的 PHAR 文件,在某些严格的 PHP 配置下是无法执行的(因为 phar.readonly=On 是默认值,且不能在运行时修改)。而签名本身又依赖 OpenSSL 私钥——这常常导致本地测试和 CI 流水线中的打包行为不一致。
- 开发环境配置:本地测试时,可以临时设置
phar.readonly=Off(在 php.ini 中修改,或通过php -d phar.readonly=Off script.php执行)。但生产环境分发时,PHAR 必须经过签名。 - 签名失败的常见原因:签名后执行仍报
Phar: invalid signature,通常是因为:私钥格式不对(需要 PEM 格式)、忘记调用$phar->setSignatureAlgorithm(Phar::SHA256, $privateKey)、或者使用的签名算法不在Phar::getSupportedSignatures()返回的支持列表中。 - 系统权限差异:在 Linux/macOS 下生成的 PHAR 文件,需要执行
chmod +x myapp.phar才能通过./myapp.phar直接运行。而 Windows 用户则必须使用php myapp.phar的方式。
话说回来,打包动作本身并不复杂。真正麻烦的,是让一个原本依赖 Composer、依赖特定目录结构的项目,在脱离 vendor 目录、脱离当前工作路径、甚至脱离原始文件系统结构之后,还能正确地加载类、读取配置文件、定位资源文件——这些路径逻辑往往深藏在框架或自定义代码中,不打到最后一包,根本发现不了问题所在。
