rasuvaeff / yii3-settings-db
Database-backed writable settings provider for Yii3 applications
Requires
- php: ^8.3
- ext-sodium: *
- rasuvaeff/yii3-settings: ^2.0
- yiisoft/db: ^2.0
- yiisoft/db-migration: ^2.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.29
- maglnet/composer-require-checker: ^4.17
- phpunit/phpunit: ^11.5
- psr/simple-cache: ^3.0
- rector/rector: ^2.4
- roave/backward-compatibility-check: ^8.0
- vimeo/psalm: ^6.16
- yiisoft/cache: ^3.2
- yiisoft/db-sqlite: ^2.0
Suggests
- psr/simple-cache: For wrapping reads in the core CachedSettingsProvider (PSR-16)
This package is auto-updated.
Last update: 2026-06-09 17:59:23 UTC
README
Database-backed writable settings provider for Yii3 applications. Implements WritableSettingsProvider and SettingsInspector from rasuvaeff/yii3-settings, persists runtime overrides in a DB table, and supports at-rest encryption of secret settings via libsodium (XChaCha20-Poly1305).
Using an AI coding assistant? llms.txt has a compact API reference you can feed into the model.
Requirements
- PHP 8.3+
ext-sodium(bundled with PHP 7.2+)rasuvaeff/yii3-settings^1.1yiisoft/db^2.0yiisoft/db-migration^2.0- a PSR-16 cache implementation — required transitively by
yiisoft/db2.0 (e.g.yiisoft/cache)
Installation
composer require rasuvaeff/yii3-settings-db
Requires rasuvaeff/yii3-settings ^2.0. With Yii3 config-plugin this package binds
SettingsProvider (and WritableSettingsProvider) automatically; the core binds
the Settings facade. Do not also bind SettingsProvider in your application
or another backend, or yiisoft/config reports a Duplicate key error.
Usage
Basic provider
use Rasuvaeff\Yii3Settings\SettingDefinition; use Rasuvaeff\Yii3Settings\SettingType; use Rasuvaeff\Yii3Settings\Settings; use Rasuvaeff\Yii3SettingsDb\DbSettingsProvider; $definitions = [ 'mail.from' => new SettingDefinition(key: 'mail.from', type: SettingType::String, default: 'noreply@example.com'), 'orders.max_items' => new SettingDefinition(key: 'orders.max_items', type: SettingType::Int, default: 100), ]; $provider = new DbSettingsProvider( db: $connection, definitions: $definitions, table: 'settings', ); $provider->set('mail.from', 'admin@example.com'); $provider->set('orders.max_items', '250'); $settings = new Settings(provider: $provider, definitions: $definitions); $settings->string('mail.from'); $settings->int('orders.max_items');
The constructor accepts an optional fallback (SettingsProvider). When a key
has no stored DB row, get() delegates to the fallback (which must recognize the
same keys) instead of returning the definition default — this is how config
values are preserved in the DI wiring below. Without a fallback, a missing row
resolves to SettingDefinition::default.
Yii3 config-plugin wiring
The package ships config/params.php and config/di.php via config-plugin.
It binds WritableSettingsProvider and SettingsProvider; the Settings facade
itself is bound by the core (rasuvaeff/yii3-settings ^2.0) from the injected
SettingsProvider. The default wiring keeps explicit config values working:
DbSettingsProvider is built with a ConfigSettingsProvider fallback, so a key
without a stored DB row resolves to its config value (and only then to the
definition default).
return [ 'rasuvaeff/yii3-settings' => [ 'definitions' => [ 'mail.from' => ['type' => 'string', 'default' => 'noreply@example.com'], 'orders.max_items' => ['type' => 'int', 'default' => 100], ], 'values' => [ 'mail.from' => 'config@example.com', ], ], 'rasuvaeff/yii3-settings-db' => [ 'table' => 'settings', ], ];
Runtime precedence after wiring:
| Source | Priority |
|---|---|
| DB row | 1 |
Explicit config values |
2 |
SettingDefinition::default |
3 |
Migration
Register the shipped migration path in your app config:
return [ 'yiisoft/db-migration' => [ 'sourcePaths' => [ dirname(__DIR__) . '/vendor/rasuvaeff/yii3-settings-db/migrations', ], ], ];
Then run:
./yii migrate:up ./yii migrate:down --limit=1
Core cache decorator
use Rasuvaeff\Yii3Settings\CachedSettingsProvider; $cached = new CachedSettingsProvider( inner: $provider, cache: $psr16Cache, definitions: $definitions, ttl: 60, ); $cached->get('mail.from'); $cached->clear('mail.from');
Secret settings
use Rasuvaeff\Yii3Settings\SettingDefinition; use Rasuvaeff\Yii3Settings\SettingType; use Rasuvaeff\Yii3SettingsDb\Crypto\KeyRing; use Rasuvaeff\Yii3SettingsDb\Crypto\SodiumCipher; $keyRing = new KeyRing( keys: ['key-2025' => sodium_crypto_aead_xchacha20poly1305_ietf_keygen()], activeKeyId: 'key-2025', ); $cipher = new SodiumCipher(keyRing: $keyRing); $definitions = [ 'billing.stripe_key' => new SettingDefinition(key: 'billing.stripe_key', type: SettingType::String, secret: true), 'mail.from' => new SettingDefinition(key: 'mail.from', type: SettingType::String, default: 'noreply@example.com'), ]; $provider = new DbSettingsProvider(db: $db, definitions: $definitions, cipher: $cipher); $provider->set('billing.stripe_key', 'sk_live_xxx'); // Stored as enc:vkey-2025:... in DB — plaintext never at rest. $provider->get('billing.stripe_key'); // 'sk_live_xxx' (transparent decrypt)
Settings inspector
$state = $provider->describe(key: 'billing.stripe_key'); $state->key; // 'billing.stripe_key' $state->hasStoredOverride; // true $state->source; // 'db', 'config', or 'default' $state->isSecret; // true — value is masked (null) $state->isWritable; // true
Key rotation
$count = $provider->reencryptSecrets(); // Decrypts all stored secret values with their current key, // re-encrypts with the active key. Plaintext values (without enc: prefix) // are encrypted in-place. Returns count of re-encrypted keys.
Public API
| Class | Description |
|---|---|
DbSettingsProvider |
DB-backed WritableSettingsProvider + SettingsInspector |
KeyRing |
Key management with versioning and active key |
SodiumCipher |
XChaCha20-Poly1305 encryption via libsodium |
InvalidSettingRowException |
Stored row type mismatch |
Security
- Unknown keys are rejected:
get(),set(), andremove()throwUnknownSettingException. - DB values are normalized through
SettingDefinition; malformed ints/floats/arrays throwInvalidSettingRowException. - User values go through bound parameters in write/delete commands.
- Table names must be trusted application configuration.
- At-rest encryption: XChaCha20-Poly1305 AEAD via libsodium. Each value uses a random 24-byte nonce.
- AAD binding: ciphertext is bound to the setting key — a value cannot be moved to a different row.
- Fail loud: tampered data throws
DecryptionException, never silent fallback. - Key rotation: keyId in envelope allows reading with old key, writing with active key.
- Secret values do not appear in
SettingState::effectiveValue(masked as null). - Cipher is required when
secret: truedefinitions exist — fail-fast at construction.
Examples
See examples/ for runnable scripts.
Development
make install && make build
License
BSD-3-Clause. See LICENSE.md.