86 lines
3.0 KiB
PHP
86 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Runtime settings an operator can change from the console.
|
|
*
|
|
* Cached because the public-site gate reads one of these on every single
|
|
* request; the cache is dropped on write, so a flip takes effect immediately
|
|
* rather than "within a minute".
|
|
*/
|
|
final class Settings
|
|
{
|
|
private const CACHE_PREFIX = 'app_setting:';
|
|
|
|
public static function get(string $key, mixed $default = null): mixed
|
|
{
|
|
$cacheKey = self::CACHE_PREFIX.$key;
|
|
|
|
if (Cache::has($cacheKey)) {
|
|
$raw = Cache::get($cacheKey);
|
|
} else {
|
|
try {
|
|
$row = DB::table('app_settings')->where('key', $key)->value('value');
|
|
} catch (QueryException $e) {
|
|
// The gate reads a setting on every request, so an unreachable
|
|
// or not-yet-migrated table would take the whole site down —
|
|
// including during a deploy where new code runs before migrate.
|
|
// Fall back to the caller's default, but do NOT cache that:
|
|
// caching a failure would keep a hidden site publicly visible
|
|
// long after the database came back.
|
|
Log::warning('app_settings unavailable, using default', [
|
|
'key' => $key, 'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return $default;
|
|
}
|
|
|
|
// Sentinel: distinguishes "stored null" from "never stored", so a
|
|
// missing row does not get re-queried on every request.
|
|
$raw = $row ?? '__missing__';
|
|
Cache::forever($cacheKey, $raw);
|
|
}
|
|
|
|
return $raw === '__missing__' ? $default : json_decode($raw, true);
|
|
}
|
|
|
|
public static function set(string $key, mixed $value): void
|
|
{
|
|
DB::table('app_settings')->updateOrInsert(
|
|
['key' => $key],
|
|
['value' => json_encode($value), 'updated_at' => now(), 'created_at' => now()],
|
|
);
|
|
|
|
Cache::forget(self::CACHE_PREFIX.$key);
|
|
}
|
|
|
|
public static function bool(string $key, bool $default = false): bool
|
|
{
|
|
return (bool) self::get($key, $default);
|
|
}
|
|
|
|
/**
|
|
* Remove a setting entirely, falling back to the caller's own default
|
|
* from then on.
|
|
*
|
|
* Not `set($key, null)`: that would still leave a row (and a cache
|
|
* entry) behind — indistinguishable to `get()`, but a migration's
|
|
* `down()` undoing its own `up()` should leave no row, not a null one.
|
|
* Deletes the row before dropping the cache, the same order `set()`
|
|
* writes-then-invalidates in — a reader between the two statements sees
|
|
* the OLD value from a cache that is about to be dropped, never a stale
|
|
* one that outlives the row it described.
|
|
*/
|
|
public static function forget(string $key): void
|
|
{
|
|
DB::table('app_settings')->where('key', $key)->delete();
|
|
Cache::forget(self::CACHE_PREFIX.$key);
|
|
}
|
|
}
|