Laravel中AppServiceProvider的Cache调用在package:discover时失败
php artisan package:discover时Redis连接失败,但浏览器运行正常的问题解决 问题描述
我在Laravel项目中使用business-day包处理工作日逻辑,在AppServiceProvider的boot方法中注册节假日,同时将数据库中的额外日期加入假期列表,代码如下:
AppServiceProvider 代码
public function boot() { $additionnalExclusions = Cache::remember('excludedWebPaymentDates', 60 * 60, function () { return ExcludedWebPaymentDate::all()->mapWithKeys(function ($model) { return [$model->Motif => Carbon::parse($model->Date)->format('Y-m-d')]; })->toArray(); }); BusinessDay::enable(['Carbon\Carbon', 'Carbon\CarbonImmutable'], 'ca-qc', $additionnalExclusions); }
浏览器访问应用一切正常,但执行php artisan package:discover --ansi(或触发该命令的操作,比如composer dump-autoload、Docker镜像构建)时,出现Redis连接错误:
Predis\Connection\ConnectionException
php_network_getaddresses: getaddrinfo for redis failed: Temporary failure in name resolution [tcp://redis:6379]
at vendor/predis/predis/src/Connection/AbstractConnection.php:155
我的.env中Redis配置:
REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379
目前只有移除缓存调用才能避免错误,但这样会导致每次请求都查询数据库,并非最优解。
原因分析
- 运行环境差异:浏览器访问时,应用处于完整的运行环境中(比如Docker集群里Redis容器已启动、本地开发Redis服务已运行),Laravel可以正常解析
redis主机名并建立连接。 - 命令行执行场景问题:
AppServiceProvider的boot方法会在所有Artisan命令执行时触发,包括package:discover这类不需要依赖Redis或数据库的命令:- Docker构建阶段:此时Redis容器还未启动,Laravel容器无法解析
redis主机名,触发连接失败。 - 本地开发环境:如果执行命令时Redis服务未启动,同样会出现主机解析失败。
- Docker构建阶段:此时Redis容器还未启动,Laravel容器无法解析
解决办法
方案1:判断环境跳过非必要操作
在boot方法中判断当前是否为命令行环境,仅在需要加载配置的场景下执行缓存/数据库操作:
public function boot() { // 仅在非命令行环境,或执行特定需要该配置的命令时加载 if (!app()->runningInConsole() || in_array(request()->server('argv')[1] ?? '', [ 'serve', 'queue:work', // 添加其他需要加载该配置的命令 ])) { $additionnalExclusions = Cache::remember('excludedWebPaymentDates', 60 * 60, function () { return ExcludedWebPaymentDate::all()->mapWithKeys(function ($model) { return [$model->Motif => Carbon::parse($model->Date)->format('Y-m-d')]; })->toArray(); }); BusinessDay::enable(['Carbon\Carbon', 'Carbon\CarbonImmutable'], 'ca-qc', $additionnalExclusions); } else { // 命令行环境下加载基础配置,不加载额外排除日期 BusinessDay::enable(['Carbon\Carbon', 'Carbon\CarbonImmutable'], 'ca-qc', []); } }
方案2:捕获异常降级处理
捕获Redis连接异常,当缓存不可用时直接查询数据库:
public function boot() { try { $additionnalExclusions = Cache::remember('excludedWebPaymentDates', 60 * 60, function () { return ExcludedWebPaymentDate::all()->mapWithKeys(function ($model) { return [$model->Motif => Carbon::parse($model->Date)->format('Y-m-d')]; })->toArray(); }); } catch (\Predis\Connection\ConnectionException $e) { // Redis不可用时直接查询数据库 $additionnalExclusions = ExcludedWebPaymentDate::all()->mapWithKeys(function ($model) { return [$model->Motif => Carbon::parse($model->Date)->format('Y-m-d')]; })->toArray(); } BusinessDay::enable(['Carbon\Carbon', 'Carbon\CarbonImmutable'], 'ca-qc', $additionnalExclusions); }
方案3:调整Docker构建流程
如果是Docker构建时出现问题,将package:discover的执行时机从构建阶段移到容器启动阶段,确保Redis已运行:
- 创建
entrypoint.sh脚本:
#!/bin/sh set -e # 等待Redis服务可用 until nc -z redis 6379; do echo "Waiting for Redis..." sleep 1 done # 执行包发现命令 php artisan package:discover --ansi # 启动应用 exec "$@"
- 修改Dockerfile,用该脚本作为入口点,移除构建阶段的
php artisan package:discover命令。
方案4:本地开发启动Redis服务
如果是本地开发环境执行命令报错,直接启动Redis服务即可(比如执行redis-server,或通过Docker启动Redis容器)。
内容的提问来源于stack exchange,提问作者adarm9

