composer 的 scripts 是命令别名与生命周期钩子的组合,键名成为可执行命令,路径须基于项目根目录,推荐用 vendor/bin/xxx 和 php 脚本透传参数,失败默认不中断流程。

怎么在 composer.json 里定义自定义 script
Composer 的 scripts 是个配置块,不是插件也不是命令行工具——它本质是「命令别名 + 生命周期钩子」的组合。你写进去的每个键,都会变成一个可执行的 composer run-script xxx 或直接 composer xxx(如果名字不冲突)。
常见错误是把路径写成相对当前目录,比如 "php ./bin/mytool.php";实际运行时工作目录是项目根(即 composer.json 所在目录),但用户可能在子目录下执行 composer xxx,所以路径必须写死或用 $COMPOSER_HOME 等环境变量兜底。
- 脚本名不能含空格、斜杠,建议全小写+下划线,比如
post-install-dev合法,my:command会报错 - 支持数组形式写多条命令,按顺序执行,遇到非零退出码就中断:
["php -v", "php ./scripts/check.php"] - 如果想让脚本在
composer install后自动跑,得用post-install-cmd这类预定义钩子名,而不是随便起名
为什么有些 script 执行后没反应或报 command not found
最常踩的坑是:脚本里调用了未安装或未加入 $PATH 的二进制文件。比如写了 "phpunit",但本地没全局装 PHPUnit,也没通过 vendor/bin/phpunit 显式调用。
另一个隐形问题是 Windows 和 Unix 的 shell 差异:Windows 默认用 cmd.exe,不认单引号、反引号、管道符;而 Composer 在 Windows 上默认不启用 bash 兼容模式。
- 优先用
vendor/bin/xxx而不是裸命令,比如"vendor/bin/phpcs --standard=PSR12 src/" - 避免
&&、|、$()这类 shell 特性,改用 PHP 脚本封装逻辑 - 检查
composer config --list | grep bin-dir,确认bin-dir没被设成空或非法路径
如何让 script 支持传参(比如 composer build -- --env=prod)
Composer 原生不解析 script 后面的参数,-- 后的内容会被丢弃,除非你手动处理。真正能透传参数的方式只有一种:用 php 调用自定义 PHP 文件,并在脚本里读取 $argv。
比如定义:"build": "php scripts/build.php",然后在 scripts/build.php 里用 array_slice($argv, 1) 拿到所有参数。
-
composer run-script build -- --env=prod中的--env=prod不会自动变成$argv的一部分,必须靠 PHP 脚本自己解析 - 不要试图用
exec()或shell_exec()去拼接命令,容易被注入;用proc_open()更安全 - 如果只是简单开关,可用环境变量替代:运行前设
ENV=prod composer build,脚本里读getenv('ENV')
script 执行失败时为什么有时不中断 install/update
因为 Composer 默认把 script 当作“尽力而为”操作。除非你显式设置了 "process-timeout": 0 或在 script 命令末尾加 || exit 1,否则一个 post-update-cmd 里某条命令失败,整个流程仍会继续。
更麻烦的是,某些脚本(如生成 autoload 文件)失败后,后续依赖可能加载失败,但 Composer 不会回滚已安装的包。
- 关键脚本建议加兜底判断:比如
"php -m | grep -q xdebug || (echo 'xdebug missing' >&2; exit 1)" - CI 环境中务必加
--no-interaction --no-ansi,避免因交互式提示卡住 - 调试时用
composer run-script xxx -v查看完整命令和 stderr 输出,别只看返回码
真正难搞的不是写 script,而是搞清它在哪种上下文里跑、PATH 是什么、当前 uid 有没有权限访问目标路径——这些不会报错,只会静默失败。










