### Composer的require-dev:你真的会用吗?
先说几个核心判断:`composer require --dev` 这个命令,不少人理解得有点偏差。它到底管什么、不管什么,生产环境该怎么正确部署,autoload-dev又该用在哪儿——这些都是绕不开的关键问题。咱们今天就把它彻底说清楚。
#### composer require --dev 只改 composer.json 字段,不控制安装行为
`--dev` 这个参数,本质上只决定包被写进 `composer.json` 的哪个字段——是 `require` 还是 `require-dev`。它跟“这个包在生产环境会不会生效”没有任何直接关系。很多人想当然地觉得,加了 `--dev`,这条命令一敲下去,线上环境就能自动屏蔽它。可实际上呢?只要你的构建脚本里没加 `--no-dev`,这玩意儿照样会钻进 `vendor` 目录,照样会挂到 autoloader 上,该报的错一个都不会少给你看。
说白了,绕不开那几个坑:
* 用 `composer require --dev phpunit/phpunit` 装了包,结果 Docker 构建时只跑了个裸奔的 `composer install`,生产镜像里莫名其妙多出几十兆的 PHPUnit 和它的那一堆依赖库。
* 本地测试跑得风生水起,CI 流水线却说“Class not found: PHPUnitFrameworkTestCase”。原因很简单——CI 脚本里漏了 `--dev` 参数,包根本没装进去。
* 更常见的低级错误:手一滑写成了 `composer require-dev phpunit/phpunit`(少空格、多连字符),Composer 把它当成普通包名处理,直接塞进 `require`。上线?必炸无疑。
#### 生产部署必须显式用 --no-dev,否则等于没分
`composer install` 的默认行为,就是把 `require` 和 `require-dev` 里的所有包一股脑全装上。它不关心你的环境变量是不是设了 `APP_ENV=prod`,不看
服务器主机名,更不会去猜当前目录是不是生产目录——它只认你给它的命令参数。
正确的做法其实很明确:
* 在 Dockerfile 里,固定写成:`RUN composer install --no-dev --optimize-autoloader --classmap-authoritative`
* 在 CI/CD 流水线(比如 GitHub Actions 或 GitLab CI)中构建生产镜像时,`composer install` 必须带上 `--no-dev`。而且,缓存 key 里也要包含这个标志,避免缓存的产物复用后,把 dev 包漏删或误留。
* 至于 CI 的测试阶段,就用完整安装:`composer install && ./vendor/bin/phpunit`。但到了构建产物的那一步,必须切回 `--no-dev`。
#### autoload-dev 不是给 PHPUnit 用的,是给你自己的 tests/ 目录用的
`autoload-dev` 的配置,它的作用是在你本地执行了完整安装(即装了 `require-dev` 里的包)的前提下,把你自己的 `tests/`、`stubs/` 这些目录也加进自动加载规则。它不是为了让 PHPUnit 自己能正常加载,而是让你在测试代码里写 `use AppTestsFooTest` 的时候,Composer 能找到它、不会报错。
这里有几个关键点:
* 如果生产环境只跑了 `--no-dev`,那 `autoload-dev` 里定义的 PSR-4 映射根本就不会被写进 `vendor/autoload.php`。
* 假如你的测试类里用到了 `mockery/mockery`,除了把它放在 `require-dev` 里,还得确保 `autoload-dev` 包含了 `tests/` 目录。否则,`new MockeryLegacyMockInterface()` 这种代码在测试时仍然会找不到类。
* 当你在 `--no-dev` 模式下执行 `composer dump-autoload --classmap-authoritative` 时,这个操作特别重要:它会让 autoloader 完全跳过文件扫描。这样一来,如果某段生产代码里不小心 new 了一个只在 dev autoloader 中注册的类,它就会直接暴露出来,绝不会给你含糊过去。
#### 验证 --no-dev 是否真生效,别只信配置
上线之后,最直接的验证方式,就是进到容器或者部署目录里,动手检查实际结果:
* 检查 `vendor/` 目录:执行 `ls vendor/ | grep phpunit`。如果输出不为空,说明 `--no-dev` 没起作用。
* 检查 classmap:执行 `grep -n "PHPUnit" vendor/composer/autoload_classmap.php`。如果找到匹配项,说明 dev 包参与了 autoloader 的构建。
* 运行时验证:执行 `php -r "var_dump(class_exists('PHPUnit\\Framework\\TestCase'));"`。生产环境必须返回 `bool(false)`。
* 额外注意一点:`vendor/autoload.php` 有没有被手动修改过?如果有人为了省事, hack 式地追加了 dev 路径,那么在 `--no-dev` 模式下,这种操作会直接失效。
真正的教训在于:`require-dev` 的边界,不在于“我用不用这个包”,而在于“业务代码会不会在运行时去 `new` 或者 `use` 它”。哪怕你只是在某个控制器里临时写了一个 `dump()`,而 `symfony/var-dumper` 恰好被放在了 `require-dev` 里,上线后只要没有 `--no-dev`,它就会立刻给你颜色看。这才是 Composer 依赖管理中最容易被忽略,也最容易坑死人的边界问题。
本文转载于:https://www.php.cn/faq/2739023.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。