rasuvaeff / yii3-feature-flags-db
Database-backed feature flag provider for Yii3 applications
Package info
github.com/rasuvaeff/yii3-feature-flags-db
pkg:composer/rasuvaeff/yii3-feature-flags-db
Requires
- php: 8.3 - 8.5
- psr/simple-cache: ^3.0
- rasuvaeff/yii3-feature-flags: ^1.0
- yiisoft/db: ^2.0
- yiisoft/db-migration: ^2.1
- yiisoft/definitions: ^3.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- infection/infection: ^0.33
- maglnet/composer-require-checker: ^4.17
- rasuvaeff/property-testing: ^2.6
- rector/rector: ^2.4
- roave/backward-compatibility-check: ^8.0
- testo/bridge-infection: ^0.1.6
- testo/testo: ^0.10.25
- vimeo/psalm: ^6.16
- yiisoft/cache: ^3.2
- yiisoft/db-sqlite: ^2.0
- yiisoft/injector: ^1.2
- yiisoft/test-support: ^3.0
This package is auto-updated.
Last update: 2026-08-04 06:12:29 UTC
README
Database-backed feature flag provider for Yii3 applications. Implements the FlagProvider interface from rasuvaeff/yii3-feature-flags and reads flag configuration from a database table in a single query.
Using an AI coding assistant? llms.txt contains a compact API reference you can ingest in your prompt context.
Requirements
- PHP 8.3+
rasuvaeff/yii3-feature-flags^1.0yiisoft/db^2.0yiisoft/db-migration^2.1 (ships the table migration)yiisoft/definitions^3.0 (DIReferenceforWritableFlagProvider)- a PSR-16 cache implementation — required transitively by
yiisoft/db2.0 (e.g.yiisoft/cache)
Installation
composer require rasuvaeff/yii3-feature-flags-db
With Yii3 config-plugin this package binds both FlagProvider and
WritableFlagProvider to the same instance — do not also bind either key in
your application or another backend, or yiisoft/config reports a
Duplicate key error.
Database schema
Create the feature_flags table (adjust types for your RDBMS):
CREATE TABLE feature_flags ( name VARCHAR(190) PRIMARY KEY, enabled BOOLEAN NOT NULL DEFAULT TRUE, salt VARCHAR(190) NOT NULL DEFAULT '', rollout SMALLINT NOT NULL DEFAULT 100, kill_switch BOOLEAN NOT NULL DEFAULT FALSE, environments TEXT NOT NULL DEFAULT '[]' );
| Column | Type | Default | Description |
|---|---|---|---|
name |
VARCHAR(190) PK |
— | Flag name (core regex: /^[a-z][a-z0-9._-]*$/) |
enabled |
BOOLEAN |
true |
Whether the flag is active |
salt |
VARCHAR(190) |
'' |
Empty string falls back to flag name |
rollout |
SMALLINT |
100 |
Percentage 0..100 |
kill_switch |
BOOLEAN |
false |
Emergency off switch |
environments |
JSON/TEXT |
'[]' |
JSON array of strings |
Migration
Register the bundled migration by namespace — no vendor paths:
// config/common/di/migration.php use Yiisoft\Db\Migration\Service\MigrationService; return [ MigrationService::class => [ 'setSourceNamespaces()' => [[ 'App\\Migration', 'Rasuvaeff\\Yii3FeatureFlagsDb\\Migration', ]], ], ];
yiisoft/db-migration resolves the migration through Injector::make(), so
it picks up FeatureFlagsTableName from the container the same way the
provider does — no manual wiring needed beyond setSourceNamespaces() above.
Custom table name
Set it in params — the same value reaches the migration and DbFlagProvider:
// config/common/params.php 'rasuvaeff/yii3-feature-flags-db' => [ 'table' => 'my_feature_flags', 'table_prefix' => '', // prepended to `table`; e.g. 'rsv_' → rsv_my_feature_flags ],
Do not configure the migration through the DI container.
M...::class => ['__construct()' => ['table' => ...]]does not work: the migration is built byInjector::make(), which resolves arguments by type and never reads a container definition keyed by the migration's own class. Worse, adding that definition makes the container fatal at build time in every request, because the class is not autoloadable until the migration runner requires it. That recipe was documented in 1.x; it never worked.
Usage
Basic DB provider
use Rasuvaeff\Yii3FeatureFlags\FeatureFlags; use Rasuvaeff\Yii3FeatureFlagsDb\DbFlagProvider; $provider = new DbFlagProvider( db: $connection, // yiisoft/db ConnectionInterface table: 'feature_flags', // optional, default is 'feature_flags' ); $featureFlags = new FeatureFlags(provider: $provider); if ($featureFlags->isEnabled('new-checkout')) { // new checkout flow }
With PSR-16 caching
use Rasuvaeff\Yii3FeatureFlagsDb\CachedFlagProvider; $cached = new CachedFlagProvider( inner: $provider, cache: $psr16Cache, // PSR-16 CacheInterface ttl: 60, // seconds ); $featureFlags = new FeatureFlags(provider: $cached);
Clear cache
$cached->clear(); // removes cached flags, next call reloads from DB
Writing flags
DbFlagProvider and CachedFlagProvider both implement
WritableFlagProvider. Use them for programmatic CRUD or an admin UI.
use Rasuvaeff\Yii3FeatureFlags\Flag; use Rasuvaeff\Yii3FeatureFlags\WritableFlagProvider; /** @var WritableFlagProvider $provider */ $provider->save(flag: new Flag( name: 'new-checkout', enabled: true, rollout: 25, environments: ['production'], )); $provider->remove(name: 'old-checkout');
save()is an upsert keyed byname(insert or replace).remove()is idempotent: deleting a missing name is a no-op.CachedFlagProvideris write-through: after a successfulsave()/remove()it clears its cache before returning, so the next read reflects the change. When the inner provider is read-only (e.g.ConfigFlagProvider), write calls are silent no-ops — wrap a config provider safely without exceptions.- Salt is normalized:
Flag::__construct()replaces an empty salt with the flag name. On write the row stores''wheneversalt === nameso the round-trip read keeps the same invariant (emptySaltFallsBackToName). - Environments are encoded through
FlagRowMapper::encodeEnvironments()and decoded throughextractEnvironments(). Round-trip is guaranteed.
Writable DI binding
config/di.php binds WritableFlagProvider to the same instance as
FlagProvider via Yiisoft\Definitions\Reference:
use Rasuvaeff\Yii3FeatureFlags\WritableFlagProvider; use Yiisoft\Definitions\Reference; return [ // ...FlagProvider::class closure omitted for brevity... WritableFlagProvider::class => Reference::to(FlagProvider::class), ];
Inject WritableFlagProvider in write paths and FlagProvider in read paths;
both resolve to the same object.
API reference
| Class | Description |
|---|---|
DbFlagProvider |
Reads all flags from DB in one SELECT *; implements WritableFlagProvider |
CachedFlagProvider |
PSR-16 decorator with write-through cache; implements WritableFlagProvider |
FlagRowMapper |
@internal row ↔ Flag mapper; also exposes encodeEnvironments() |
InvalidFlagRowException |
Thrown when a DB row has invalid structure |
Security
- Kill switch, rollout hash logic, and environment targeting remain in the core package — the DB adapter is only a configuration source.
- Invalid row data (missing columns, malformed JSON, wrong types, out-of-range rollout, invalid flag name) throws
InvalidFlagRowExceptioninstead of silently enabling features. Core validation errors are wrapped, so callers only need to catchInvalidFlagRowException. - No SQL injection risk: table name is quoted via yiisoft/db quoter.
Examples
See examples/ for runnable scripts.
Development
composer build # full gate: validate + normalize + cs + psalm + test composer cs:fix # auto-fix code style composer psalm # static analysis composer test # run tests
License
BSD-3-Clause. See LICENSE.md.