Files
HickLab3D_Dashboard/app/Services/IntegrationCache.php
T
hermes c8c5822166
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 38s
[verified] Harden Etsy MCP validation and scheduled token refresh
2026-11-07 21:54:33 +00:00

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) && ! $fresh) {
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');
}
}