scripts 必须作为 composer.json 顶层字段,键为 composer 内置事件名(如 post-install-cmd),值支持字符串、数组或 classname::methodname 格式;事件名拼写错误或位置错误将导致静默失效。

composer.json 里怎么写 scripts 字段
Composer 的脚本钩子全靠 scripts 字段驱动,它不是插件也不是扩展,就是个 JSON 键值对。写错位置或格式,composer install 不报错但脚本压根不触发。
-
scripts必须是顶层字段,和require、autoload平级,不能塞进config或extra里 - 键名是事件名(比如
post-install-cmd),值可以是字符串命令、数组命令、或指向 PHP 方法的ClassName::methodName - 如果值是数组,Composer 会按顺序执行每一项;字符串则直接交给 shell 解析,注意引号和空格转义
示例:在项目根目录 composer.json 中添加
"scripts": {
"post-install-cmd": [
"echo '安装完成'",
"php -r \"file_put_contents('runtime/installed', date('c'));\""
],
"pre-autoload-dump": "MyScript::beforeAutoload"
}
哪些事件名真正可用、什么时候触发
事件名不是随便写的,必须是 Composer 内置的生命周期钩子,拼错一个字母就静默失效。常见误区是把 post-update 当成合法事件——实际只有 post-update-cmd。
- 命令类事件带
-cmd后缀:pre-install-cmd、post-update-cmd、post-autoload-dump(无-cmd) -
post-autoload-dump在生成自动加载文件后触发,适合清理旧缓存或生成代理类;post-install-cmd和post-update-cmd区别在于前者只在首次install触发,后者在update或install(有 lock 文件时)都触发 - 自定义脚本名(如
build:assets)不会自动绑定事件,需手动运行composer run build:assets,也不能被其他事件监听
PHP 类方法当脚本时要注意什么
用 ClassName::methodName 形式调用 PHP 方法看似高级,但容易因自动加载失败而中断——Composer 运行脚本时,vendor/autoload.php 尚未完全就绪,尤其是 pre-autoload-dump 阶段。
- 方法必须是
public static,且参数签名要匹配:通常接收一个Composer\Script\Event实例 - 不要在方法里依赖尚未生成的 autoload 文件,比如别在
pre-autoload-dump里 new 一个刚 require 的类 - 调试时加
var_dump(get_class($event)); exit;看是否真进了方法,比猜日志更直接
示例类写法:
class MyScript
{
public static function beforeAutoload($event)
{
$vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
file_put_contents("$vendorDir/../.build-timestamp", time());
}
}
脚本执行失败却不报错?检查这几点
默认情况下,脚本返回非零退出码,Composer 只会警告(Script xxx handling the xxx event returned with error code 1),但不会中断后续流程——除非你加了 --no-interaction 或设了 COMPOSER_NO_INTERACTION=1。
- 用
composer run-script post-install-cmd -v加-v查看完整输出,隐藏的 permission denied 或 command not found 就会露出来 - Shell 命令中含管道、重定向或 && 时,整个表达式被视为单个命令,出错不会被单独捕获;建议拆成多个数组项,或用
sh -c "xxx && yyy"包一层 - Windows 下
php -r的双引号容易和 CMD 解析冲突,改用单引号包裹 PHP 代码,或直接写 .php 文件再调用
最常被忽略的是:脚本在 CI 环境里以 nobody 用户运行,对 storage/ 或 runtime/ 目录没写权限,但错误被吞掉,只看到时间戳没更新。










