composer不支持if-else,需通过require-dev分层、config.platform锁定扩展、composer-staging.json适配staging、replace/provide实现伪条件依赖、按环境生成专属composer.lock及ci脚本管控来实现多环境依赖管理。

composer.json 里不能写 if-else,但可以靠 config.platform 和 require-dev 分层控制
Composer 本身不支持运行时条件加载依赖,所谓“多环境动态切换”,本质是提前按角色分组 + 手动触发对应安装逻辑。核心思路是:把 dev 相关包全塞进 require-dev,生产环境用 --no-dev 过滤;再配合 config.platform 锁定扩展版本,避免本地有 ext-memcached 但线上没有导致装错包。
常见错误现象:composer install 在 staging 环境报 class 'MockeryMockery' not found,其实是误用了 --no-dev,但 staging 实际需要部分 dev 工具(比如健康检查脚本依赖的 symfony/console)。
- 生产环境部署必须加
--no-dev --optimize-autoloader,否则require-dev里的包仍会进 autoloader,浪费内存且可能引发冲突 - staging 环境不是非黑即白的“dev 或 prod”,建议单独建
composer-staging.json,用COMPOSER=composer-staging.json composer install切换 -
config.platform只影响依赖解析,不改变实际扩展是否加载。例如设"ext-memcached": "3.1.5"后,即使服务器没装 memcached 扩展,Composer 也不会报错——但运行时会崩
用 replace 和 provide 实现“伪条件依赖”
当某个包只在特定环境需要,又不想放进 require-dev(比如它同时被 prod 代码引用,只是实现方式不同),就得用 replace 隐藏真实依赖,再靠不同环境装不同“替代品”。
使用场景:日志驱动在 dev 用 monolog/monolog,prod 用 SaaS 厂商 SDK。你不能让 SDK 出现在 require-dev,否则本地跑不起来;也不能硬塞进 require,否则测试环境要配一堆 mock。
- 主项目
composer.json中声明:"replace": {"acme/logger-adapter": "*"} - 新建
acme/logger-adapter-dev包,composer.json写"provide": {"acme/logger-adapter": "1.0"},并 requiremonolog/monolog - 新建
acme/logger-adapter-prod包,同样"provide": {"acme/logger-adapter": "1.0"},但 require 厂商 SDK - 各环境只装对应 adapter 包,Composer 自动识别“已提供 acme/logger-adapter”,跳过冲突检查
composer.lock 不该提交到所有分支,但也不能每个环境都重生成
很多人以为 composer.lock 是“一次生成,到处 deploy”,其实它绑定了 PHP 版本、平台扩展、甚至 Composer 自身版本。dev 机器 PHP 8.2 装的 guzzlehttp/guzzle,和 prod 的 PHP 8.1 可能解出完全不同的子版本。
错误做法:CI 流水线里执行 composer update 再 commit lock —— 这等于把构建环境的偶然性固化成发布依据。
- dev 分支保留完整
composer.lock,含require-dev和本地平台信息 - prod 构建时,用干净容器执行:
composer install --no-dev --platform=php:8.1 --ignore-platform-req=ext-apcu,生成专属 lock - staging 构建命令里加
--with-all-dependencies,确保它既包含 prod 的稳定依赖,又补上 staging 需要的调试工具(如psy/psysh)
环境变量无法直接进 composer.json,但可以用 scripts + composer config 曲线救国
想根据 ENV=staging 自动切包?不行。composer.json 是静态 JSON,不支持变量插值。但你可以把“环境感知”逻辑从依赖管理下沉到部署脚本。
性能影响很小,但容易忽略的是:每次 composer config 修改都会写入 composer.json 或 auth.json,如果没清理,下次 git status 就会看到脏文件。
- 在 CI 脚本开头加:
composer config --global github-oauth.github.com ${GITHUB_TOKEN},避免交互式登录中断流水线 - 用
composer config --unset repos.packagist关闭 packagist.org,强制走私有源(适用于内网 staging) - 不要在
scripts里写"post-install-cmd": "if [ "$ENV" = "prod" ]; then composer remove --dev phpunit/phpunit; fi"——post-install-cmd运行时$ENV不一定存在,且 remove 会改写composer.json
最麻烦的点其实是人:团队里总有人在 staging 机器上手敲 composer require debugbar/debugbar,然后忘记删,结果下一次 composer update 就把它带进 prod lock。这类操作没法靠配置堵死,只能靠部署流程卡住——比如 prod 构建机禁止执行 composer require。