Composer不支持运行时Feature Flag,必须拆解为构建期决策(如--no-dev、CI脚本控制)和运行期适配(如class_exists兜底判断),依赖始终静态声明且安装即锁定。

Composer 本身不支持运行时 Feature Flag
Composer 是静态依赖管理工具,所有 require 和 require-dev 在 composer.json 中声明后,install/update 时就已锁定版本并下载完毕。它没有“根据环境变量或配置开关动态加载某包”的能力——这不是 bug,是设计使然。所谓“基于 Feature Flag 的依赖动态选择”,必须拆解为两层:构建期决策(用 Composer 脚本或 CI 环境控制) + 运行期适配(靠代码逻辑跳过未启用功能的依赖调用)。
用 composer install --no-dev + 环境变量控制 dev-only 包
这是最常用、最稳妥的“伪动态”方式。把仅用于特定功能(如 A/B 测试后台、灰度监控 SDK)的包放到 require-dev,再通过环境变量决定是否安装它们:
-
COMPOSER_DEV_MODE=0 composer install --no-dev:跳过所有require-dev包,生产环境默认行为 -
COMPOSER_DEV_MODE=1 composer install:保留 dev 包,配合 CI/CD 流水线在灰度环境触发 - PHP 代码中必须用
class_exists('Some\DevOnlyClass')或function_exists('some_dev_helper')做兜底判断,不能直接 new 或调用 - 注意:
--no-dev不影响 autoloader 注册,只是不安装;但若某 dev 包被主代码硬依赖(如use了它的类),会直接 fatal error
用 composer require 动态追加包(仅限部署脚本)
适用于明确知道某 Feature 开启后才需要某 SDK 的场景,比如开启“邮件模板引擎”才装 league/commonmark:
- 在部署脚本中判断配置:
if [ "$FEATURE_EMAIL_TEMPLATES" = "on" ]; then composer require league/commonmark; fi - 必须确保该包未出现在
composer.json的require或require-dev中,否则composer update会覆盖掉 - PHP 层需用
class_exists('League\CommonMark\Environment')检查可用性,且避免在全局 scope 引入其类 - 风险点:不同环境的
composer.lock不一致,可能引发 diff 难以追踪、回滚困难
别碰 autoload.files 和条件 autoload
有人试图在 composer.json 里写条件式 autoload.files,比如按 $_ENV['FEATURE_X'] 切换加载文件——这完全无效。Composer 的 autoloader 是生成时静态编译的,vendor/autoload.php 一旦生成,内容就固定了,运行时无法改变加载规则。
真正可行的是:把 feature 相关的初始化逻辑封装进独立函数或服务提供者,在入口处统一判断 flag 后再决定是否调用它。依赖本身仍得提前装好,或走上面两种构建期控制方式。
最易被忽略的一点:Feature Flag 的状态必须和依赖存在性严格对齐。Flag 为 true 却没装对应包,或 Flag 为 false 却仍调用了已装包里的敏感逻辑,都会导致运行时崩溃或数据污染。


















