能,但仅限开发或CI环境;生产环境禁用,因缺乏回滚、并发控制与状态校验,须改用独立部署流程执行迁移。

post-install-cmd 能不能直接跑 migrate?
能,但只在开发或 CI 环境下安全;生产环境直接用它跑迁移,等于把数据库变更和依赖安装混为一谈——失败时既没法回退代码,也没法回退结构,composer install 一挂,整个部署就卡死。
常见错误现象:Command "migrate" is not defined,本质是 artisan 还没加载好(autoload 未 dump),或脚本在 vendor/ 解压完成前就执行了(尤其 Composer 2.2 以前版本)。
- 确保命令以
php开头,比如php artisan migrate --no-interaction,别写成artisan migrate - 优先用
post-autoload-dump替代post-install-cmd,它在自动加载文件生成后触发,Artisan类已可用 - 加
--force(Laravel)或--no-interaction(Symfony/Doctrine),否则 CI 会卡在交互提示上
如何让迁移脚本只在特定环境运行?
硬编码 "php artisan migrate --force" 到 composer.json 里,等于默认所有环境都执行——本地开发每次 composer install 都清库重来,CI 构建镜像时连不上 DB 就报错退出。
正确做法是把环境判断逻辑外移,不靠 shell 条件语句(Composer 不解析 if),而是用 PHP 脚本控制:
- 新建
scripts/migrate.php,显式读取APP_ENV:getenv('APP_ENV') === 'production'才执行 - 在
composer.json中调用:"post-install-cmd": ["php scripts/migrate.php"] - 脚本内加连接校验,比如
DB::connection()->getPdo(),失败直接exit 1,避免迁移命令盲目启动
为什么不能把 migrate 放进 post-update-cmd?
composer update 是改依赖版本的,不是改数据库结构的。把它塞进 post-update-cmd,会导致每次更新一个 dev-only 包(比如 phpunit),都触发一遍 php artisan migrate ——线上已有表,再跑一次就报 Base table or view already exists。
真正该做的事,是把迁移从“依赖变更钩子”里剥离出来,变成独立可触发的动作:
- 定义一个自定义 script:
"migrate": "php artisan migrate --no-interaction" - CI 流水线里明确调用:
composer run migrate,而不是依赖post-update-cmd - 若必须集成,前置加判断:
php -r "if (getenv('CI') !== 'true') exit(0);",跳过本地 update
原子化迁移的关键不在 Composer,而在执行时机
Composer 本身没有事务或原子回滚能力。composer install 失败,vendor/ 可能半残,composer.lock 却已写入新哈希——这时再跑迁移,连 autoloader 都加载不了。
所谓“原子化”,是指迁移动作和代码部署解耦,并由外部流程保障一致性:
- 构建阶段:在临时目录(如
/tmp/app-$(date +%s))执行composer install --no-scripts --no-plugins - 验证阶段:检查
vendor/autoload.php是否存在、关键类能否class_exists() - 切换阶段:用
mv vendor vendor.old && mv /tmp/app-xxx/vendor .原子替换(Linux/macOS 同文件系统下可靠) - 迁移阶段:切换成功后,单独执行
php artisan migrate --force,失败不影响当前运行态
最容易被忽略的一点:迁移脚本里写的 php,在 Docker 多阶段构建中可能指向 Alpine 的 php-cli,而它默认不带 pdo_mysql 扩展——得提前装好,或者改用绝对路径调用宿主机 PHP。


















