paulohps / laravel-db-config
Override Laravel config values at runtime with values stored in the database.
Requires
- php: ^8.4
- illuminate/contracts: ^12.0
- illuminate/database: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- larastan/larastan: ^3.1
- laravel/pint: ^1.21
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.7
README
Override Laravel config values at runtime with values stored in the database.
You decide, per config file, which keys may be overridden — either by allowing
only a few keys (only) or by allowing everything except a few keys (except).
The service provider reads the stored values on boot and applies the allowed
ones on top of the file-based configuration.
Requires PHP 8.4+ and Laravel 12+.
Installation
composer require paulohps/laravel-db-config php artisan migrate
The migration for the db_config_settings table is loaded automatically.
Publishing the config file (and optionally the migration) is available via:
php artisan vendor:publish --tag=db-config-config php artisan vendor:publish --tag=db-config-migrations
Configuration
// config/db-config.php return [ // Turn the runtime overriding on/off. When off, nothing is overridden // and the app falls back to the plain file-based configuration. 'enabled' => env('DB_CONFIG_ENABLED', true), 'table' => 'db_config_settings', 'connection' => null, // Cache the stored settings so the provider does not query the database // on every request. 'store' = cache store (null = default), 'ttl' in // seconds (null = forever). 'cache' => [ 'enabled' => env('DB_CONFIG_CACHE_ENABLED', true), 'store' => env('DB_CONFIG_CACHE_STORE'), 'key' => 'db-config', 'ttl' => null, ], // Which config files (and keys) may be overridden from the database. 'configs' => [ '*', // every file 'app', // every key of app.php 'mail' => ['only' => ['from.address', 'from.name']], 'services' => ['except' => ['ses.secret']], ], ];
Rules:
'*'allows every file. Files with an explicit entry keep their own rules even when'*'is present.only: only the listed keys of that file may be overridden.except: every key of that file may be overridden, except the listed ones — useful when excluding a few keys is shorter than listing all the others.- Keys match by dot-notation prefix:
fromalso coversfrom.address. - With
except, ancestors of an excluded key are blocked too (overridingfromcould smuggle a value into the excludedfrom.address), and whole-file overrides are never allowed when key rules exist. - When both
onlyandexceptare given for the same file,onlywins.
Usage
Stored keys are full config keys in dot notation (config file name first). Values that do not pass the rules above are ignored at apply time.
use Paulohps\DbConfig\Facades\DbConfig; // Persist a value and apply it immediately (throws KeyNotConfigurableException // when the key is not allowed by the rules): DbConfig::set('mail.from.address', 'hello@myapp.com'); // Read the stored (database) value, ignoring the file config: DbConfig::get('mail.from.address'); // 'hello@myapp.com' DbConfig::get('mail.from.name', 'fallback'); // 'fallback' (never stored) // Remove the override — the file value is restored on the next request: DbConfig::forget('mail.from.address'); // Check whether a key may be overridden: DbConfig::allows('mail.from.address'); // bool
Values are JSON-encoded, so arrays, booleans, numbers and null round-trip.
Caching
With cache.enabled on (the default), the stored settings are cached — forever
when ttl is null, otherwise for ttl seconds — and the database is only
queried again after the cache is invalidated.
Every write that goes through the Eloquent model — set(), forget(), a
seeder or admin panel calling create()/update()/delete() on
DbConfigSetting — rebuilds the cache automatically. The observer handles
events after the transaction commits, so a rolled-back write never poisons the
cache. Writes that bypass model events (query-builder update(), DB::table(),
raw SQL) do not trigger the rebuild; after those, flush by hand:
DbConfig::flushCache(); // next read repopulates // or DbConfig::refreshCache(); // flush and repopulate right away
On multi-server setups use a shared store (Redis, Memcached, database) so the
invalidation reaches every server; with a per-server store (file, array) the
other servers keep the stale payload until their ttl expires.
Caveats
config:cache: the overrides are applied at runtime on every request, on top of the (possibly cached) file config. With the settings cache disabled this costs one database query per request.- Boot order: providers that read config in
register(), or that boot before this package's provider, see the file value instead of the database value. - Resilience: when the database is unavailable or not migrated yet (fresh install, CI), the provider silently skips the overrides instead of taking the application down.
Testing
composer test # Pest composer analyse # PHPStan (level 8, via Larastan) composer format # Pint
License
MIT. See LICENSE.md.