Cache integration API reads and refresh them on a schedule
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 28s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Hickmeister
2026-10-11 19:01:32 +01:00
co-authored by Claude Sonnet 5.5
parent 8bee8d42c7
commit 2fe4fbd91e
10 changed files with 253 additions and 24 deletions
+68
View File
@@ -0,0 +1,68 @@
<?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');
}
}