Composer没有pre-test-cmd钩子,测试预热必须通过phpunit.xml指定的bootstrap文件(如tests/bootstrap.php)在PHPUnit启动前执行,确保环境就绪、配置加载、依赖可用,避免数据库连接失败或缓存未生成等问题。

测试前预热数据不能靠 post-install-cmd 或手动跑脚本凑数——它必须在 PHPUnit 启动前、且环境完全就绪时执行,否则数据库连接失败、缓存未生成、配置未加载,预热就等于没做。
为什么 pre-test 钩子根本不存在
Composer 没有 pre-test-cmd 这类内置钩子。有人试图用 scripts 里定义 test 命令并前置逻辑,比如:
"test": "php scripts/warmup.php && vendor/bin/phpunit"
这看似可行,但问题极多:
- 无法区分是本地开发运行
composer test,还是 CI 中执行vendor/bin/phpunit --configuration phpunit.xml—— 后者完全绕过 Composer 脚本 -
warmup.php若依赖 Laravel 的app()或 Symfony 的Kernel,而此时 autoloader 尚未完整加载或环境变量未注入,直接 fatal error - 并发跑测试(如
--process-isolation)时,多个进程可能同时写同一张表或缓存文件,导致数据污染或权限冲突
真正可靠的预热入口:PHPUnit 的 bootstrap 文件
PHPUnit 在执行任何测试前,会先加载 phpunit.xml 中指定的 bootstrap 文件。这才是唯一可控、稳定、与测试生命周期强绑定的时机。
- 在
tests/bootstrap.php开头加入预热逻辑,确保每次测试启动都走一遍 - 用
file_exists(__DIR__ . '/../vendor/autoload.php')判断是否已安装依赖,避免在composer install失败后还硬执行 - 数据库预热优先用事务回滚 + fixtures,而非 truncate/insert:避免锁表、适配内存 SQLite 测试库
- 缓存预热要跳过
cache:clear类命令,改用Cache::put('key', 'value', 3600)直接写入,避开未就绪的 Redis 连接
如何让预热逻辑不拖慢本地开发
预热不是每次都重来。加一层轻量守卫机制:
- 检查
storage/framework/cache/data/下是否存在test-warmed-up缓存项,有则跳过整个预热流程 - 用
file_put_contents(storage_path('framework/test-warmed-up'), time())标记完成时间,便于调试时强制刷新 - CI 环境下通过
$_SERVER['CI'] ?? false或getenv('CI')区分,禁用守卫、强制全量预热 - 不要在预热中调用
Artisan::call('migrate:fresh')—— 它会删库,应改用DatabaseMigrationstrait 或自建轻量初始化器
最容易被忽略的点:.env 和 config 缓存的加载顺序
预热代码若读取了 config/database.php,而该文件又依赖 env('DB_CONNECTION'),但 .env 还没被加载,就会拿到默认值 sqlite,导致后续连不上 MySQL。必须确保:
-
Dotenv::createImmutable(__DIR__ . '/..')->load();在预热逻辑之前执行 - 若项目已运行过
php artisan config:cache,预热时要先Artisan::call('config:clear'),否则config()返回的是缓存旧值 - 所有预热操作完成后,再调用一次
Artisan::call('config:cache')(仅限测试环境),保证后续测试读到最新配置


















