<p>Composer 的 scripts 仅在手动运行(如 composer run xxx)或绑定生命周期事件(如 post-install-cmd)时生效;未调用或未绑定的脚本不执行,且需注意命令冲突、参数传递(用 -- 分隔)、类自动加载及事件语义。</p>

Composer 的 scripts 不是用来“随时写、随时生效”的快捷配置,它只在两种明确场景下起作用:一是你手动运行 composer run xxx(或语法糖 composer xxx),二是它被绑定到某个生命周期事件(如 post-install-cmd)并在对应 Composer 命令执行时自动触发。没被调用、也没被事件绑定的脚本,就是一段 JSON,完全不运行。
scripts 字段只响应显式调用或事件绑定
很多人以为加了 "build": "php build.php" 就等于“有了个 build 命令”,其实只是注册了一个可选任务。它不会自动执行,也不会出现在 composer list 里(那是插件命令的事)。是否执行,全看你怎么用:
- 手动执行:
composer run build或composer build(如果名字不冲突) - 事件触发:
"post-install-cmd": ["@build"]→ 安装依赖后自动跑 - 不绑定、不调用 → 脚本存在但等于不存在
哪些名字能直接 composer xxx,哪些必须 composer run-script "xxx"
不是所有脚本名都能省略 run-script。Composer 会优先把命令字匹配到自己的原生命令列表,冲突就跳过你的脚本:
-
"test"→composer test✅(没冲突,且自动触发pre-test/post-test) -
"install"→composer install❌(执行的是原生命令),必须写composer run-script "install" -
"dev:start"→composer dev:start❌(冒号导致解析失败),必须加引号:composer run-script "dev:start" -
"ci-lint"→ 同样需引号,Windows 下双引号不能省
传参必须用 -- 分隔,否则参数被 Composer 自己吃掉
这是最常静默失败的点。你想把 --filter=FooTest 透传给 PHPUnit,但写成:
composer test --filter=FooTest
→ Composer 会尝试把它当自己的选项处理,报错或忽略。正确写法是:
composer test -- --filter=FooTest
中间两个短横是分隔符,表示“这之后的都交给脚本进程”。PHP 回调里拿不到 $_SERVER['argv'],但可以读环境变量 COMPOSER_ARGS(Composer 2.5+ 支持)或靠 shell 层拼接传入。
PHP 回调类必须 public static,且 autoload 配置不能漏
写 "scripts": { "clear": "MyTool::clearCache" } 后报 Class 'MyTool' not found?不是 Composer 故意找茬,而是它根本没加载自动加载器:
- 类文件路径必须声明在
autoload里,例如:"psr-4": { "App\Scripts\": "scripts/" } - 改完
composer.json后必须运行composer dump-autoload,否则新路径不生效 - 方法必须是
public static,且不能 new 未加载的类(比如直接new IlluminateSupportStr会失败) - 不推荐在回调开头手动
require __DIR__.'/vendor/autoload.php'—— 正确做法是让 Composer 自动加载整个类
真正容易被忽略的,是脚本执行上下文和生命周期的耦合关系:一个写在 post-update-cmd 里的脚本,在 composer require xxx 时才触发,而 composer install 时反而不跑——因为 require 是 update 流程。别凭直觉猜什么时候执行,查清楚事件语义再配。


















