Composer不支持在require字段动态控制依赖,仅config.http-basic、repositories[].url等少数字段支持${VAR}插值,且需提前注入环境变量;动态依赖应通过platform配置或path repo物理拆分实现。

composer.json 里不能直接用 APP_ENV 控制装哪些包——它不是模板引擎,require 字段必须是静态 JSON。所谓“动态配置依赖”,本质是绕过 Composer 原生限制,在运行时干预或模拟。
composer.json 的 ${VAR} 只在少数字段生效,且不支持逻辑判断
Composer 仅对明确文档标注的字段做一次 getenv() 替换,发生在 install/update 解析阶段。常见可用字段包括:
-
config.http-basic:如{"repo.example.com": {"username": "${REPO_USER}", "password": "${REPO_TOKEN}"}} -
repositories[].url:如"${PRIVATE_REPO_URL}" -
config.process-timeout、config.notify-batch等数值型配置(值必须为纯数字字符串)
以下写法全部无效,不会报错但会被当字面量存入 composer.lock:
-
require或require-dev中的包名或版本号写成"${PACKAGE_VERSION}" -
config.repos、autoload、scripts字段使用插值 - 试图用
${CI:-false}做默认值 fallback——Composer 不解析 Bash 语法,该行为需由 shell 层提前展开
想按环境跳过某些 require 包?别碰 require-dev,改用 platform + COMPOSER_PLATFORM_CHECK
require-dev 是全局开关,不是环境开关。它控制“是否安装 dev 包”,无法区分 staging 和 production。
真正可行的是让 Composer “假装”有/没有某个扩展,从而影响依赖解算结果:
- 在
composer.json的config.platform中声明:"ext-xdebug": "3.1.5",这样即使本地没装 xdebug,Composer 也会认为它存在,避免因 dev 包依赖它而报错 - 线上部署时加
COMPOSER_PLATFORM_CHECK=0 composer install --no-dev,彻底关闭平台校验,比改platform更干净 - 注意:
platform不会隐藏整个包,只影响兼容性判断;它也不改变require列表本身
需要彻底隔离不同环境的依赖?用 path repo 拆项目,别硬塞进一个 composer.json
当必须做到「prod 装 A、dev 装 B,且互斥」时,唯一可维护方案是物理拆分:
- 新建子目录
myapp/prod-deps,内含独立composer.json,只写生产所需依赖 - 同理建
myapp/dev-deps,含psy/psysh、phpunit/phpunit等 - 主项目
composer.json中配置:"repositories": [{"type": "path", "url": "../myapp/${ENV_DEPS_DIR}"}],CI 中用ENV_DEPS_DIR=prod-deps控制路径 - 主项目
require改为:"myapp/prod-deps": "*"或"myapp/dev-deps": "*"
这种结构保证 composer.lock 可复现,且不污染主项目的 autoload 映射。
脚本中读不到 .env?因为 Composer 进程不自动加载 dotenv
你在 scripts 里写 getenv('APP_ENV') 返回空,不是 PHP 配置问题,而是 vlucas/phpdotenv 根本没被触发——Composer 启动的是全新 PHP 进程,$_ENV 默认为空,variables_order 通常不含 E。
- 必须在脚本入口手动加载:
use Dotenv\Dotenv; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->safeLoad(); - 之后统一用
getenv('APP_ENV'),不要用$_ENV或$_SERVER['APP_ENV'](后者只在putenv()后才存在) - 路径务必用
__DIR__,不用getcwd(),否则从子目录调用脚本时会找不到.env
最易忽略的一点:所有环境变量注入(shell export、CI env 块、Docker -e)都必须发生在 Composer 进程启动前;一旦 PHP 进程起来,getenv() 就只能读到那一刻已存在的变量。











