Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 28s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
69 lines
1.9 KiB
PHP
69 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Closure;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Read-through cache for slow third-party API calls.
|
|
*
|
|
* Fresh entries are served instantly, expired entries fall back to the last good value if the
|
|
* upstream call fails, and a short failure cooldown stops every page load waiting on a dead API.
|
|
* The scheduled `integrations:refresh` command runs with $forceRefresh enabled to keep it warm.
|
|
*/
|
|
class IntegrationCache
|
|
{
|
|
private const RETAIN_SECONDS = 86400;
|
|
|
|
private const FAILURE_COOLDOWN_SECONDS = 30;
|
|
|
|
public bool $forceRefresh = false;
|
|
|
|
/**
|
|
* @template TValue
|
|
*
|
|
* @param Closure(): TValue $fetch
|
|
* @return TValue
|
|
*/
|
|
public function remember(string $key, int $freshSeconds, Closure $fetch, bool $fresh = false): mixed
|
|
{
|
|
$cacheKey = 'integration:'.$key;
|
|
$entry = Cache::get($cacheKey);
|
|
$bypass = $fresh || $this->forceRefresh;
|
|
|
|
if (! $bypass && is_array($entry) && time() < $entry['fetched_at'] + $freshSeconds) {
|
|
return $entry['value'];
|
|
}
|
|
if (! $bypass && ! is_array($entry) && ($failure = Cache::get($cacheKey.':failed'))) {
|
|
throw new RuntimeException((string) $failure);
|
|
}
|
|
|
|
try {
|
|
$value = $fetch();
|
|
} catch (Throwable $exception) {
|
|
if (is_array($entry)) {
|
|
report($exception);
|
|
|
|
return $entry['value'];
|
|
}
|
|
Cache::put($cacheKey.':failed', $exception->getMessage(), self::FAILURE_COOLDOWN_SECONDS);
|
|
|
|
throw $exception;
|
|
}
|
|
|
|
Cache::put($cacheKey, ['value' => $value, 'fetched_at' => time()], self::RETAIN_SECONDS);
|
|
Cache::forget($cacheKey.':failed');
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function forget(string $key): void
|
|
{
|
|
Cache::forget('integration:'.$key);
|
|
Cache::forget('integration:'.$key.':failed');
|
|
}
|
|
}
|