composer 不应直接执行数据库迁移,而应在 scripts 中通过环境校验(如 app_env)和独立脚本控制迁移时机,避免在 ci/生产环境误触发或重复执行,确保配置从环境变量加载而非硬编码。

composer install 后自动运行 migrate 命令
Composer 本身不执行数据库迁移,但可以通过 scripts 配置触发自定义命令。关键不是“让 Composer 干这事”,而是让它在安装依赖后顺手跑一句 php artisan migrate(Laravel)或 php bin/console doctrine:migrations:migrate(Symfony)这类操作。
常见错误是直接写 "post-install-cmd": "php artisan migrate",结果 CI 环境没配好 DB 连接,迁移失败导致整个 composer install 中断——这反而破坏了依赖安装的稳定性。
- 只在开发/部署环境启用,用
scripts的条件执行机制:例如 Laravel 推荐用"post-autoload-dump"而非post-install-cmd,避免在生产镜像构建时误触发 - 加
--no-interaction和--force(如支持),防止交互式确认卡住 - 迁移命令前务必检查环境:比如先跑
php -r "if (!getenv('APP_ENV')) die(1);",环境变量缺失就跳过
如何避免 migrate 在 composer update 时重复执行
composer update 也会触发 post-update-cmd,但你通常只希望它更新代码,不希望每次改个 vendor 就重跑一遍迁移——尤其线上环境可能已有新表结构,重复执行会报错或丢数据。
典型错误现象:SQLSTATE[42S01]: Base table or view already exists,说明迁移脚本被不加区分地反复执行。
- 把迁移逻辑抽成独立脚本(如
bin/run-migrations.php),在scripts里调用它,并由脚本自行判断是否该执行(例如检查APP_ENV === 'local'或CI !== 'true') - 不要在
post-update-cmd里直接写迁移命令;如果必须集成,加前置校验:if [ "$APP_ENV" = "production" ]; then exit 0; fi && php artisan migrate - Laravel 用户注意:
php artisan migrate默认只跑未记录在migrations表里的文件,但php artisan migrate:refresh会清库重来——后者绝不能放进scripts
跨框架迁移命令的兼容性陷阱
不同框架对迁移命令的参数、退出码、输出格式处理差异很大,硬编码进 composer.json 容易在升级框架后突然失效。
比如 Doctrine Migrations 3.x 把 doctrine:migrations:migrate 的 --no-interaction 改成了 --no-input;Laravel 11 默认禁用 migrate:fresh 在生产环境运行,但旧版没这限制。
- 始终用完整路径调用命令二进制:比如
./vendor/bin/phpunit而不是phpunit,避免系统 PATH 混淆 - 迁移命令失败时,Composer 默认会中断流程并返回非零退出码——如果你只是想“尽量跑”,得用
|| true包裹(但要清楚后果) - 别依赖命令输出内容做判断(如 grep “Migrated”),不同版本输出文案可能变;优先查
migrations表记录或命令退出码
为什么不能把数据库连接配置写死在 composer.json 里
有人试图在 scripts 里拼接 DB_HOST=xxx php artisan migrate,这是危险操作:敏感信息会留在 composer.json 历史中,且无法按环境区分。
更隐蔽的问题是,Composer 执行脚本时的环境变量继承自 shell,而 Docker 或 CI 环境往往不加载 .env 文件,导致迁移连不上库,但错误提示只显示 “Connection refused”,根本看不出是配置没生效。
- 所有数据库配置必须从环境变量或
.env加载,迁移命令自身负责读取——不要在scripts层面覆盖 - 本地开发可配合
dotenv工具预加载:php -r "putenv('APP_ENV=local'); (require '.env') && include 'vendor/autoload.php';",但这属于脚本内部逻辑,不在composer.json里硬写 - 最稳妥的做法:迁移只作为部署脚本(如
deploy.sh)的一环,由运维流程控制时机和环境,而非塞进 Composer 生命周期
真正难的不是让命令跑起来,而是确保它只在该跑的时候、用该用的配置、以该有的权限跑——这些边界稍一模糊,自动化就变成定时删库。










