* * @param Closure(): TValue $read Reads the real value from the database. * @param TValue $whenUnavailable Returned as-is when the database cannot answer. * @return TValue */ public static function rememberForever(string $key, Closure $read, array $whenUnavailable): array { try { return Cache::rememberForever($key, $read); } catch (PDOException $e) { // QueryException extends PDOException, so this covers both a // missing table and a connection that could not be opened. self::warnOnce($key, $e); return $whenUnavailable; } } /** * The same protection for a value that is deliberately *not* cached. * * A credential must not sit in the cache store, so it is read on each * boot that actually needs it — but that read lands on the same path * as the cached ones and must survive the same missing database. The * caller has already been handed a cached array saying the feature is * configured; that array can be warm while the database is, right now, * unreachable. * * @template TValue * * @param Closure(): TValue $read Reads the real value from the database. * @param TValue $whenUnavailable Returned as-is when the database cannot answer. * @return TValue */ public static function read(Closure $read, mixed $whenUnavailable = null): mixed { try { return $read(); } catch (PDOException $e) { self::warnOnce('(uncached credential read)', $e); return $whenUnavailable; } } /** * Once per process: an install that has not been migrated yet would * otherwise log this on every artisan command, and a genuine database * outage would log it on every request. */ private static function warnOnce(string $key, PDOException $e): void { if (self::$warned) { return; } self::$warned = true; Log::warning('Falling back to default settings: the database could not be read during boot.', [ 'key' => $key, 'reason' => $e->getMessage(), ]); } }