应在 composer.json 中配置 post-create-project-cmd 钩子执行迁移命令,如 "post-create-project-cmd": ["@php ./artisan migrate --force"],并前置环境检查脚本确保 .env 配置就绪,避免重复执行或静默失败。

create-project 后如何自动执行数据库迁移?
Composer create-project 本身不支持运行自定义脚本,它只做代码拉取 + composer install。想在项目初始化后立刻同步表结构,必须把逻辑塞进 Composer 的生命周期钩子中——最可靠的是 post-create-project-cmd。
这个钩子在 create-project 完成后触发,且只运行一次,适合做初始化操作。但注意:它默认在目标目录里执行,而 Laravel、Symfony 等框架的迁移命令(如 php artisan migrate 或 bin/console doctrine:migrations:migrate)依赖正确的当前工作目录和环境配置。
- 确保
composer.json顶层定义"scripts": {"post-create-project-cmd": ["@php artisan migrate --force"]}(Laravel 示例) - 如果项目未预装
vendor/bin,@php可能找不到artisan,改用完整路径:"@php ./artisan migrate --force" -
--force必须加,否则在非交互式环境下(CI/CD 或create-project)会卡住 - 数据库连接必须已通过
.env或环境变量就绪——create-project不会自动写入配置,得提前在模板项目里配好占位值或靠外部注入
为什么不能在 post-install-cmd 里做这件事?
post-install-cmd 在每次 composer install 都会执行,包括开发过程中反复安装依赖时。如果把它当“初始化迁移”用,会导致表被重复执行,轻则报错(如 “table already exists”),重则数据丢失(比如 migrate:fresh)。
而 post-create-project-cmd 是一次性动作,语义上更准确。但它有个硬限制:只在 create-project 场景下触发,对 git clone + composer install 无效——如果你需要统一入口,得另起一个初始化脚本(如 bin/init),并在文档里明确要求用户手动运行。
-
post-create-project-cmd不会在composer update中触发 - 它不继承父项目的脚本,只执行目标项目的
composer.json中定义的部分 - 若模板项目用了插件(如
hirak/prestissimo),某些旧版 Composer 会跳过该钩子,建议锁定 Composer 2.2+ 使用
如何安全处理 .env 文件缺失导致迁移失败?
直接跑 php artisan migrate 而没配好 .env,会抛出 InvalidArgumentException: Database connection [default] not configured 或更底层的 PDO 连接异常,整个 create-project 流程就中断了,用户看到的是红色错误堆栈,而不是友好的提示。
稳妥做法是在迁移前加一层检查:用 PHP 脚本读取 .env,验证关键键是否存在(如 DB_DATABASE、DB_USERNAME),缺失时输出清晰提示并退出,不继续执行迁移。
- 可写一个
scripts/check-env.php,在post-create-project-cmd中先调它:"php scripts/check-env.php" - 不要用
file_exists('.env')简单判断——有些项目用.env.example作为模板,需提醒用户复制并编辑 - 若允许空数据库名(如 SQLite 内存模式),也要在检查逻辑里排除,避免误判
迁移脚本被跳过或静默失败怎么办?
常见静默失败原因:钩子命令返回非零退出码但被忽略、PHP 错误被 suppress、或迁移命令本身因权限/超时中断。Composer 默认不会把钩子输出打到终端,除非你显式加 -v 参数。
调试时务必加 -vvv 运行 create-project,观察是否真进了钩子、命令是否执行、有没有 stderr 输出。另外,别依赖 echo ——很多环境会过滤 stdout,改用 file_put_contents('php://stderr', "msg\n") 确保可见。
- 在钩子命令末尾加
&& echo "✅ DB migrated",能快速确认执行流走到哪一步 - 如果用
exec()或shell_exec()调外部命令,记得检查返回值:if (0 !== $ret) { exit(1); } - 某些共享主机禁用
proc_open,导致artisan无法启动子进程——这时得改用纯 PHP 实现建表逻辑,或提前检测并报错
真正麻烦的不是写脚本,是让脚本在各种 CI 环境、不同 PHP 版本、不同数据库驱动下都稳定执行。哪怕只是多一行 sleep(1) 等数据库服务就绪,都可能决定初始化成败。











