phil / feature-flags-bundle
Feature flags and A/B tests for Symfony: flags evaluated once per request from ordered providers (configuration, database), targeting by tenant, project or subject, stable rollouts and experiments, expiry dates, audited changes with a reason, admin screens, a controller attribute, a route condition,
Package info
github.com/parler-haut-interagir-librement/feature-flags-bundle
Type:symfony-bundle
pkg:composer/phil/feature-flags-bundle
Requires
- php: >=8.5
- ext-mbstring: *
- doctrine/dbal: ^4.5
- doctrine/orm: ^3.7
- phil/circuit-breaker: ^0.1@dev
- phil/circuit-breaker-bundle: ^0.1@dev
- phil/ddd-bundle: ^0.1@dev
- phil/enums: ^0.3@dev
- psr/clock: ^1.0
- psr/log: ^3.0
- symfony/config: ^8.2
- symfony/console: ^8.2
- symfony/dependency-injection: ^8.2
- symfony/http-foundation: ^8.2
- symfony/http-kernel: ^8.2
- symfony/routing: ^8.2
- twig/twig: ^3.21
Requires (Dev)
- doctrine/doctrine-bundle: ^3.3
- phpunit/phpunit: ^13.4
- symfony/cache: ^8.2
- symfony/clock: ^8.2
- symfony/expression-language: ^8.2
- symfony/framework-bundle: ^8.2
- symfony/twig-bundle: ^8.2
Suggests
- symfony/web-profiler-bundle: For the profiler panel of the flags
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-11 13:30:19 UTC
README
Turn a feature off without a deploy, run an A/B test, and force a failing third party's circuit open.
phil/feature-flags-bundle gives a Symfony application two tools. Feature flags withdraw a new feature at once.
A/B tests split contexts between weighted variants. Each circuit breaker of phil/circuit-breaker-bundle also gets a
flag that forces it open, so that an operator stops calling a failing third party. Flags are declared in the
configuration or created from admin screens into the database. Each one carries an owner, a kind and an expiry
date, and targets a tenant, a project or a subject, with stable percentage rollouts.
It is for the developers who ship features behind a flag and for the administrators who switch them. Every toggle and edit is authorized, audited before it is written, and carries a mandatory reason, so the history of a flag says who changed it and why. The bundle is no authentication layer: the application puts the admin screens behind its firewall and decides who may change what. It is no client of a hosted flag service, and no analytics tool either. An A/B exposure is counted by flag and variant, never recorded with its context.
Reach for it to withdraw a new feature. It is no operational kill switch for an established subsystem. That switch is a short, enumerated list that must keep working when the database or the cache is the problem, so it is read without this bundle's providers.
A flag is read in a controller attribute, a route condition, Twig or a service. It is evaluated once per request and context from ordered providers (configuration, then the Doctrine DBAL store), each answer giving its reason. A profiler panel shows the flags a request evaluated.
Status
| Area | State |
|---|---|
| Flags, providers, state store, once-per-request evaluation, reasons | Implemented and tested |
| Targeting (membership, stable percentage rollouts) | Implemented and tested |
| A/B experiments and exposure counting | Implemented and tested |
| Metadata and expiry | Implemented and tested |
| Audited toggle and editor, revisions, conflicts | Implemented and tested |
| Database store (Doctrine DBAL, schema listener) | Implemented, tested on SQLite, MySQL 8 and PostgreSQL |
| Admin screens, same-origin guard | Implemented, tested in a kernel |
| Controller attribute, route condition, Twig functions | Implemented, tested in a kernel |
Listing command with --fail-on-expired |
Implemented, tested in a kernel |
A flag per circuit of phil/circuit-breaker-bundle that forces it open |
Implemented and tested |
| A profiler panel of flags | Implemented, its collector tested |
Documentation
- Installation — requirements, Composer, registering the bundle, the database tables and their migration, the admin routes and the firewall in front of them.
- Configuration — every key of
phil_feature_flagswith its type, default, effect and the services it wires, a complete annotated example, and the errors that fail the container build. - Flags and evaluation — definitions, ordered providers and precedence, the state store, memoization per request and context, reset in workers, reasons, the context and the catalog.
- Targeting — membership and percentage rules, the stable
murmur3abucket, and why its function is a one-way door. - Experiments — weighted variants, assignment by cumulative weight, reading the variant, and exposures counted without context.
- Metadata and expiry — description, owner, the four kinds of flag, the expiry date and how it fails a build.
- Toggles and edits — the audited toggle and editor, the order authorize–read–audit–write, compare-and-set, first-state inserts, revisions, conflicts and transactions.
- Database store — the two tables, the schema listener, per-request reading, conditional writes and unreadable rows.
- Admin — routes, screens, HTTP statuses, the same-origin guard, the actor provider, and overriding the layout and templates.
- Placement — the controller attribute, the route condition, the Twig functions and flags in the core.
- Console —
phil:feature-flags:list, its columns and options, in CI. - Circuit breakers — the force-open flag of each circuit of
phil/circuit-breaker-bundle, and forcing a circuit open with an audited toggle. - Extension points — every port with its contract, invariants and an example adapter, and every service id, tag, route, Twig function and attribute.
- Testing — the bundle's suites and databases, mutation testing, and how to test an application that uses flags.
- Reference — the public API by layer, one line per type with its state.
Requirements
- PHP 8.5 or later, with
ext-mbstring(andext-hash, always compiled in); - Symfony 8.2 components (
config,console,dependency-injection,http-foundation,http-kernel,routing),twig/twig3.21 or later,psr/clock,phil/ddd-bundle,phil/circuit-breakerandphil/circuit-breaker-bundle; - Doctrine DBAL 4.5 and ORM 3.7;
- in the application: FrameworkBundle and
symfony/clock, TwigBundle for the Twig functions and the admin, DoctrineBundle for the database store,symfony/expression-languagefor route conditions.
Details in doc/installation.md.
Quick start
composer require phil/feature-flags-bundle
// config/bundles.php Phil\FeatureFlagsBundle\PhilFeatureFlagsBundle::class => ['all' => true],
# config/packages/phil_feature_flags.yaml phil_feature_flags: context_resolver: phil_feature_flags.context_resolver.anonymous # no targeting yet: an empty context flags: new-checkout: active: true owner: checkout-team kind: release expires_on: '2027-03-31'
use Phil\FeatureFlagsBundle\Infrastructure\Http\IsFeatureEnabled; #[IsFeatureEnabled('new-checkout')] // 404 while the flag is off final class CheckoutController { // … }
{% if feature_enabled('new-checkout') %}<a href="/checkout">Try the new checkout</a>{% endif %}
bin/console phil:feature-flags:list --fail-on-expired
Next: a context resolver for targeting, the database store, the audited toggle and the admin — see doc/installation.md.
Contributing
Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.
License
MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.