OPS-001
A settings table with typed access and one query per request
Everything an operator might want to change belongs in the database, not in code. This is the small layer that makes that painless.
Hardcoded values are the reason a site cannot be handed to anyone else. Payout rates, minimums, toggles, API keys: an operator should be able to change all of it without opening a file.
The layer is three functions and one static cache, so the whole settings table costs one query per request no matter how many values you read. The grouping column is what lets an admin page render itself into sections without a hardcoded list.
function settings_all(PDO $pdo): array
{
static $cache = null;
if ($cache === null) {
$cache = [];
foreach ($pdo->query('SELECT k, v FROM settings')->fetchAll(PDO::FETCH_ASSOC) as $r) {
$cache[$r['k']] = $r['v'];
}
}
return $cache;
}
function setting(PDO $pdo, string $key, string $default = ''): string
{
$all = settings_all($pdo);
return array_key_exists($key, $all) ? (string) $all[$key] : $default;
}
function setting_int(PDO $pdo, string $key, int $default = 0): int
{
$v = setting($pdo, $key, (string) $default);
return is_numeric($v) ? (int) $v : $default;
}
function setting_bool(PDO $pdo, string $key, bool $default = false): bool
{
return setting($pdo, $key, $default ? '1' : '0') === '1';
}
function setting_set(PDO $pdo, string $key, string $value, string $group = 'general'): void
{
$pdo->prepare(
'INSERT INTO settings (k, v, grp) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE v = VALUES(v)'
)->execute([$key, $value, $group]);
}
Using it
Seed defaults through a migration with INSERT IGNORE so an upgrade adds new keys without touching values the operator has already changed.
Read settings through the typed helpers rather than casting at each call site. One place that decides what "on" means saves a lot of confusion later.
What bites people
The static cache lives for one request, which is what you want — but a script that writes a setting and then reads it back in the same request will see the old value. Update the cache in setting_set if that pattern matters to you.
Do not put database credentials in this table. They have to exist before the table can be read.