post-install-cmd 并非每次 composer install 都执行,仅当 composer.lock 不存在或过期时触发;需每次运行应改用 post-update-cmd、post-autoload-dump 或显式调用 composer run-script。

Composer 脚本不会“自动运行”,除非你把它绑到正确的生命周期事件上,且满足触发条件——post-install-cmd 并不是“每次 composer install 都跑”,它只在 composer.lock 不存在或过期时才执行。
post-install-cmd 为什么经常不触发?
这是最常踩的坑:你以为装完依赖就该跑脚本,但 Composer 实际判断逻辑很严格。
-
post-install-cmd仅在composer install且当前没有有效的composer.lock,或锁文件里记录的依赖与composer.json不一致时触发 - CI/CD 中执行
composer install --no-scripts(默认行为)会直接跳过所有脚本,包括post-install-cmd - 生产部署加了
--no-dev,而脚本定义在"scripts-dev"下(Composer 2.2+ 支持),也会静默失效 - 写成
PostInstall、post_install_cmd或post-install—— 全错,Composer 只认全小写+短横线的post-install-cmd
怎么让脚本“每次 install 都执行”?
靠单一事件做不到,得组合策略。关键是区分“首次安装”和“重复安装”场景。
- 同时注册
post-install-cmd和post-update-cmd,后者会在 lock 文件有变更时触发,覆盖更多 install 场景 - 更稳的做法是改用
post-autoload-dump:只要 autoloader 重建(install/update 都会触发),它就一定跑,时机稍晚但更可靠 - 如果必须“每次运行”,放弃事件绑定,改用显式调用:
composer install && composer run-script deploy:post-install - 自定义脚本名(如
deploy:post-install)不会自动触发,必须手动composer run-script,但它不受--no-dev或 lock 状态影响
脚本里调用 artisan 为什么本地行、线上挂?
不是命令写错了,而是执行上下文不一致:PHP 版本、autoload 加载、用户权限、工作目录都可能不同。
- 别写
php artisan cache:clear,改用@php artisan cache:clear——@php强制走 Composer 自带的 PHP 可执行文件,避免系统 PHP 版本错乱 - PHP 脚本中必须第一行写
require __DIR__ . '/../vendor/autoload.php';,否则类全找不到 - 权限问题常见于
storage/目录:部署用户 ≠ Web 用户,chmod -R 755 storage在脚本里执行后,Web 进程仍可能因 umask 或 SELinux 失败;建议用 PHP 脚本逐个chmod()+chown(),并检查is_writable() - 路径别硬写
./artisan,用php ./artisan或完整路径php ${PWD}/artisan,防止工作目录意外偏移
CI/CD 里脚本不执行,先查这三件事
90% 的线上失效都卡在这三个地方,顺序检查比重写脚本更快。
- 构建命令是否含
--no-scripts或-n?GitLab CI 默认加了,GitHub Actions 的官方 action 也常默认禁用,必须显式删掉或补--scripts - 是否用了
--no-dev?它不仅跳过 dev 依赖,还会跳过所有未声明为 production-only 的脚本钩子——把关键操作(如config:cache)抽成独立脚本名,再用composer run-script xxx --no-dev显式调用 - 脚本定义是否在根
composer.json的顶层"scripts"字段下?嵌套在extra或子包里,Composer 根本看不见
真正难的不是写脚本,而是理解 Composer 脚本本质:它只是命令别名 + 事件触发器的组合,没有魔法,也没有隐式保障。任何依赖“自动”的设计,在跨环境部署时都会暴露脆弱性——所以关键动作永远要显式调用,而不是赌 hook 一定触发。











