必须将开发依赖(如phpunit)移至require-dev,部署时执行composer install --no-dev --optimize-autoloader,确保生产环境不安装、不加载任何dev包,杜绝因类存在性检测导致的行为不一致。

composer.json 里用 require-dev 隔离开发依赖
开发时需要的工具类包(比如 phpunit、laravel/pint、doctrine/dbal)必须和生产环境依赖分开,否则上线后可能意外加载测试用代码,甚至引发安全风险。核心做法是把它们全部移到 require-dev 字段下,而不是混在 require 里。
实际效果:运行 composer install --no-dev(CI/部署常用)时,require-dev 下的所有包完全不安装,autoload-dev 的自动加载规则也一并跳过。
-
require放运行时必需的包,如monolog/monolog、guzzlehttp/guzzle -
require-dev放仅本地或 CI 阶段需要的包,如phpunit/phpunit、sebastian/environment - 如果某个包既被运行时逻辑引用、又被测试代码引用,它就必须留在
require,不能因为“测试也在用”就挪进require-dev
用 COMPOSER_DEV_MODE 控制自动加载开关
默认情况下,autoload-dev 规则只在开发环境生效(即 composer install 未加 --no-dev 时),但有些场景需要更细粒度控制——比如想让日志组件在开发环境启用调试模式,但在生产环境彻底禁用其调试类。这时可以结合环境变量 + 自定义 autoloader。
做法是在 autoload-dev 中声明开发专用命名空间,再在入口文件(如 public/index.php)中根据 $_ENV['APP_ENV'] 或 getenv('COMPOSER_DEV_MODE') 决定是否调用 require __DIR__.'/../vendor/autoload.php';或者更稳妥地,在 composer.json 的 autoload-dev 里只配开发专用路径,确保生产环境根本不会注册这些类的加载器。
- 不要在
autoload里写开发专用路径,否则即使加了--no-dev,类仍可能被加载 -
autoload-dev的优先级低于autoload,但它只在 dev 模式下注册,是真正安全的隔离层 - 检查是否生效:部署后执行
composer show --installed --no-dev,确认列表里没有phpunit类包
部署时坚持用 composer install --no-dev --optimize-autoloader
很多线上问题源于部署命令写错。只写 composer install 或漏掉 --optimize-autoloader,会导致生产环境多装一堆无用包,且自动加载慢、内存占用高。
--no-dev 是硬性开关,它不仅跳过 require-dev 安装,还会忽略 autoload-dev 注册;--optimize-autoloader 生成 vendor/composer/autoload_classmap.php,绕过 PSR-4 的文件系统查找,性能提升明显。
- CI 脚本里务必固定这条命令,不要依赖
composer.lock是否含 dev 包——lock文件本身会记录require-dev信息,但安装行为由命令决定 - 如果项目用了
config.platform(比如指定 PHP 版本),要确保它与目标服务器一致,否则--no-dev可能因平台约束失败 - 执行后检查
vendor/autoload.php头部注释,里面会写明是否启用了 classmap 加载器
避免在代码里用 class_exists('SomeTestHelper') 做环境判断
有人会写条件逻辑来“探测”某个测试类是否存在,从而切换行为。这很危险:一旦部署漏了 --no-dev,该类存在,逻辑走 A 分支;某次部署正确执行了 --no-dev,类不存在,逻辑走 B 分支——行为不一致,排查困难。
正确方式是统一通过环境变量或配置驱动,比如读取 $_ENV['APP_ENV'] === 'production',再配合配置文件(如 config/logging.php)明确声明不同环境的行为差异。
- 所有环境相关分支都应基于显式配置,而非隐式类存在性检测
- 测试类名不应出现在生产代码的任何地方(包括字符串形式的
class_exists()参数) - 静态分析工具(如 PHPStan)可配置扫描
require-dev包是否被require代码直接引用,提前拦截这类隐患
composer.json 结构只是表象,真正关键的是「安装命令是否严格执行」和「代码是否假设了 dev 包的存在」——这两点出问题,再多配置都白搭。










