composer如何为多环境(dev/staging/prod)动态切换依赖?(条件加载配置技巧)

2次阅读

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

composer如何为多环境(dev/staging/prod)动态切换依赖?(条件加载配置技巧)

composer.json 里不能写 if-else,但可以靠 config.platformrequire-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 也不会报错——但运行时会崩

replaceprovide 实现“伪条件依赖”

当某个包只在特定环境需要,又不想放进 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"},并 require monolog/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.jsonauth.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

text=ZqhQzanResources