离线打包必须在目标环境一致的PHP版本下执行composer install --no-dev --optimize-autoloader,确保vendor/完整、autoload_classmap生成、composer.lock与composer.json齐备,并解压后补composer dump-autoload -o验证类加载。

离线打包不是配个参数就能跑通的事,它本质是「构建时确定全部依赖 + 运行时不发任何 HTTP 请求」。只要 vendor/ 里缺一个类、composer.lock 里多一个远程 dist.url、或者目标机 PHP 小版本不一致,部署就卡在 Class not found 或 Could not fetch packages.json。
用 composer install --no-dev --optimize-autoloader 做构建前置
这是所有离线打包动作的起点。不运行这步,vendor/ 里可能混着 dev-only 包(如 phpunit)、autoload 映射未优化、甚至某些类文件没生成(比如 Laravel 的 compiled.php)。
- 必须在与目标环境一致的 PHP 版本下执行——比如目标机是 PHP 8.1.22,源机就不能用 8.2.x,否则
vendor/里可能出现match表达式或属性构造器语法错误 -
--optimize-autoloader会生成vendor/composer/autoload_classmap.php,绕过文件扫描,避免离线时因路径权限或 symlink 失效导致类加载失败 - 别信
--no-scripts能跳过构建:某些框架(如 ThinkPHP)的 post-autoload-dump 脚本会生成路由缓存,漏掉它会导致上线后 404
打包命令必须包含整个项目目录,不能只打 vendor/
只拷 vendor/ 是最常见也最危险的做法。composer.lock 和 composer.json 缺一不可——前者锁定每个包的 dist.shasum,后者声明 autoload 规则和仓库配置。少了它们,composer install 在离线机上根本不会启动,直接报错退出。
- 推荐用
tar -czf myapp-release.tar.gz .(注意末尾的.),确保当前目录下composer.json、composer.lock、vendor/、public/全部进包 - 别用
zip -r在 Windows 上打包再解压到 Linux,容易丢掉vendor/bin/下脚本的可执行位,导致phpunit或phinx执行失败 - 如果项目用了自定义
vendor-dir(比如"vendor-dir": "lib/vendor"),打包前确认该路径已存在且内容完整,否则composer install会静默创建空目录
离线机上禁止运行 composer create-project
这个命令在离线环境下必然失败,不是配置问题,是设计如此。它第一步就要访问 https://repo.packagist.org/packages.json 解析模板项目元数据,COMPOSER_DISABLE_NETWORK=1 对它完全无效,--offline 参数根本不存在。
- 正确做法是:把打包好的
.tar.gz解压到离线机,然后直接运行composer install --no-plugins --no-scripts --no-autoloader - 执行完必须补一记
composer dump-autoload -o——因为--no-autoloader跳过了 autoload 重建,而线上环境通常禁用 opcache file cache,不 dump 就找不到类 - 如果项目有 post-install-cmd 脚本(比如生成 key、清理缓存),得手动补上,
--no-scripts不是省事,是强制你明确知道哪些步骤不能少
生产发布前必须验证 autoload 和基础入口
打包完成不等于能跑。很多团队卡在「解压后 500 错误却查不出原因」,往往是因为没做最小化验证。
- 在源机打包前,用一行脚本验证:
php -d display_errors=1 -r "require 'vendor/autoload.php'; echo class_exists('Monolog\Logger') ? 'OK' : 'FAIL';" - 离线机解压后,同样跑这行——如果 FAIL,说明
dump-autoload -o没生效,或 PHP 版本/扩展不匹配 - 别跳过 public/index.php 的最小请求测试:
php -S 127.0.0.1:8000 -t public然后curl http://127.0.0.1:8000,能返回内容才算真正就绪











