composer exec 不自动补全路径、不加载项目 autoloader、不切换至项目根目录,导致“Command not found”等错误;应显式指定路径或改用 composer run-script。

直接用 composer exec 就能跑 vendor/bin 下的工具,但必须注意它不自动补全路径、不加载项目 autoloader、且默认工作目录不是项目根目录——很多报错都源于这三点。
为什么 composer exec phpunit 会报 “Command not found”
Composer 默认不会把 vendor/bin 加入 PATH,也不会帮你 cd 到项目根目录。它只是简单地调用 proc_open() 执行命令,而该命令在当前 shell 环境下找不到可执行文件。
- 确保你在项目根目录(即有
composer.json的地方)运行命令 - 不要依赖系统 PATH 查找
phpunit,应显式写成composer exec vendor/bin/phpunit或用--分隔符传参 - 如果脚本是 PHP 写的(如
phpstan、psalm),它可能依赖项目 autoloader,此时需加--no-dev或--with-dependencies不起作用——exec本身不加载 autoload
composer exec 的参数传递规则
它用 -- 区分 composer 自己的选项和要传给目标命令的参数。漏掉 -- 会导致参数被 composer 吞掉或解析错误。
- 正确:
composer exec -- phpunit --testdox tests/Unit - 错误:
composer exec phpunit --testdox tests/Unit(--testdox被当成 composer 参数,报错 Unknown option) - 想指定 PHP 版本?得用
COMPOSER_PHP_BINARY=/usr/bin/php8.2 composer exec -- phpunit,不能用-d或--php
替代方案:用 composer run-script 更可靠
如果你的工具已定义在 composer.json 的 scripts 里(比如 "test": "phpunit"),优先用 composer test 或 composer run-script test -- --testdox。它会自动切换到项目根目录,并支持 autoload-dev 加载。
-
composer run-script会触发post-autoload-dump等钩子,exec完全不会 - 脚本中可用
$COMPOSER_BIN_DIR环境变量(值为vendor/bin),比硬编码路径更安全 - 需要动态参数?用
composer run-script lint -- --level=5 src/,--后的内容透传给脚本定义里的命令
{
"scripts": {
"cs-fix": "php-cs-fixer fix --dry-run --diff",
"analyze": [
"@psalm",
"@phpstan"
],
"psalm": "psalm --no-cache",
"phpstan": "phpstan analyse --no-progress"
}
}
真正容易被忽略的是:无论用 exec 还是 run-script,只要脚本本身调用了 require_once 'vendor/autoload.php',就必须保证它运行时的当前工作目录是项目根目录——否则相对路径失效,autoload.php 找不到。










