Skip to content

Configuration reference

phil/feature-flags-bundle

Everything under the phil_feature_flags key, as PhilFeatureFlagsBundle::configure() declares it, and what each option wires. Run bin/console config:dump-reference phil_feature_flags for the tree as Symfony prints it.

Annotated example

# config/packages/phil_feature_flags.yaml
phil_feature_flags:
    # Service id of the resolver of the evaluation context of a request.
    # Default: the service id `Phil\FeatureFlagsBundle\Application\Flag\EvaluationContextResolver`.
    # `phil_feature_flags.context_resolver.anonymous` gives every request an empty context.
    context_resolver: App\Flags\RequestContextResolver

    # The database store. Off by default; `doctrine: true` or any map turns it on.
    doctrine:
        connection: default            # DBAL connection name; default "default"

    # Provider service ids, in order of precedence. Default: the configuration provider, then the
    # database store when `doctrine` is enabled.
    providers:
        - phil_feature_flags.provider.configuration
        - phil_feature_flags.provider.doctrine

    # Service id of the store of active states. Default: the database store when enabled, otherwise none.
    state_store: phil_feature_flags.state_store.doctrine

    # Service id of the store the admin creates and edits flags in. Default: the database store when enabled.
    definition_store: phil_feature_flags.provider.doctrine

    # Service id of the recorder of A/B exposures. Default: none.
    exposure_recorder: App\Flags\ExposureCounter

    # The audited toggle and edit. Off by default; any map turns it on.
    toggle:
        authorizer: App\Flags\PermissionAuthorizer   # default: the ToggleAuthorizer interface id
        auditor: App\Flags\AuditTrailAuditor         # default: the ToggleAuditor interface id

    # The admin screens. Off by default; any map turns them on. They need `toggle`.
    admin:
        actor_provider: App\Flags\OperatorProvider   # default: the ActorProvider interface id
        layout: 'admin/layout.html.twig'             # default: '@PhilFeatureFlags/admin/layout.html.twig'

    # Service id of the PSR-20 clock. Default: "clock".
    clock: clock

    # The flags of the configuration provider, by name.
    flags:
        new-checkout:
            active: true
            rules:
                - { attribute: tenant, members: [tenant-a, tenant-b] }
                - { attribute: tenant, percentage: 20 }
            description: The one-page checkout.
            owner: checkout-team
            kind: release
            expires_on: '2027-03-31'
        checkout-layout:
            active: true
            experiment:
                attribute: tenant
                variants: { one-column: 50, two-columns: 50 }
            kind: experiment
        search.v2: false               # short for { active: false }
        maintenance-banner: true       # short for { active: true }: on for everyone

Keys

context_resolver

Typestring (service id), cannot be empty
DefaultPhil\FeatureFlagsBundle\Application\Flag\EvaluationContextResolver
EffectSecond argument of FlagEvaluator. Called on every evaluation, so it stays cheap.

The default is the interface name, so the application either registers a service under that id (an autowiring alias does) or names its own. phil_feature_flags.context_resolver.anonymous (AnonymousContextResolver) is always registered: every context is empty, so a flag with rules or an experiment is off for everyone and only flags without rules are on. See flags-and-evaluation.md.

doctrine

KeyTypeDefaultEffect
doctrineboolean or map (canBeEnabled)disabledtrue, ~ or a non-empty map enables it; false disables it.
doctrine.enabledbooleanfalseSet by the forms above.
doctrine.connectionstring, cannot be emptydefaultThe DBAL connection; the bundle references the service doctrine.dbal.<connection>_connection.

When enabled, the bundle registers:

  • phil_feature_flags.provider.doctrine — DoctrineFlagDefinitions, tagged kernel.reset: a provider and the writable definition store;
  • phil_feature_flags.state_store.doctrine — DoctrineFlagStore, tagged kernel.reset: the writable store of active states;
  • FlagSchemaListener, tagged doctrine.event_listener on postGenerateSchema for that connection.

It also changes the defaults of providers, state_store and definition_store. See database-store.md.

providers

Typelist of strings (service ids), each non-empty
Default[phil_feature_flags.provider.configuration], plus phil_feature_flags.provider.doctrine when doctrine is enabled
EffectFirst argument of FlagEvaluator and of FlagCatalog, in this order.

The first provider that knows a flag gives its whole definition; an empty list means the default. The catalog keys the providers by their service id, which is what the listing and the admin show as Source. Each service implements FlagProvider; implement EnumerableFlagProvider to appear in the listing. Naming phil_feature_flags.provider.doctrine without enabling doctrine fails the container compilation with a missing service. Leave out phil_feature_flags.provider.configuration and the flags of the configuration are never read (the provider is still registered).

state_store

Typestring (service id) or null
Defaultnull, or phil_feature_flags.state_store.doctrine when doctrine is enabled
EffectThird argument of FlagEvaluator and second of FlagCatalog; the store FlagToggler and FlagEditor write to.

Without toggle, any FlagStateSource will do. With toggle, it must be a WritableFlagStore.

definition_store

Typestring (service id) or null
Defaultnull, or phil_feature_flags.provider.doctrine when doctrine is enabled
EffectThe WritableFlagDefinitions the editor writes to. With toggle enabled, it registers FlagEditor; the admin's create and edit screens exist only then.

The admin shows the edit link only for flags whose catalog source is this exact service id, so the definition store should also be one of the providers.

exposure_recorder

Typestring (service id) or null
Defaultnull
EffectFourth argument of FlagEvaluator: an ExposureRecorder told the flag and the variant once per flag and context per request.

See experiments.md.

toggle

KeyTypeDefaultEffect
toggleboolean or map (canBeEnabled)disabledEnables FlagToggler (and FlagEditor when there is a definition store).
toggle.authorizerstring (service id), non-emptyPhil\FeatureFlagsBundle\Application\Toggle\ToggleAuthorizerAuthorizes every toggle, creation and edit.
toggle.auditorstring (service id), non-emptyPhil\FeatureFlagsBundle\Application\Toggle\ToggleAuditorWrites the audit line of every change.

Registers FlagToggler (authorizer, state store, auditor, clock, FlagCatalog) and, when definition_store is set, FlagEditor (authorizer, definition store, state store, auditor, clock, FlagCatalog). See toggles-and-edits.md.

admin

KeyTypeDefaultEffect
adminboolean or map (canBeEnabled)disabledRegisters FlagAdminController (public, controller.service_arguments).
admin.actor_providerstring (service id), non-emptyPhil\FeatureFlagsBundle\Application\Admin\ActorProviderNames the operator of a request.
admin.layoutstring, non-empty@PhilFeatureFlags/admin/layout.html.twigThe Twig template every admin screen extends; it defines the title and content blocks.

The controller also takes the twig and router services. The routes are not loaded automatically: import @PhilFeatureFlagsBundle/config/routes/admin.php (installation.md, admin.md).

clock

Typestring (service id), non-empty
Defaultclock (the PSR-20 clock FrameworkBundle registers when symfony/clock is installed)
EffectGiven to the database store (updated_at), the toggle and editor (audit timestamps), and the admin and the listing (expiry).

flags

A map of flag name to definition, read by ConfiguredFlagProvider (service phil_feature_flags.provider.configuration). Keys are not normalized, so dashes stay dashes.

KeyTypeDefaultConstraints
(name)map, or a boolean—Lowercase ASCII segments separated by ., _ or -; at most 100 bytes. A boolean b is short for { active: b }.
activebooleanfalseA flag is inactive unless declared active.
ruleslist of maps[]Active without rules, the flag is on for every context; with rules, it is on when any rule matches.
rules[].attributestring, required—tenant, project or subject.
rules[].memberslist of strings[]Non-empty identifiers. Exactly one of members or percentage.
rules[].percentageinteger or nullnull0 to 100.
experimentmap or nullnullattribute (tenant, project or subject) and variants, a map of variant name to whole weight; at least two variants, weights 0 to 100 adding up to 100.
descriptionstring or nullnullTrimmed; at most 500 characters; blank means none.
ownerstring or nullnullTrimmed; at most 100 characters; blank means none.
kindstring or nullnullrelease, experiment, ops or permission.
expires_onstring or nullnullYYYY-MM-DD. An unquoted YAML date (read as a timestamp) is converted back to its UTC date.

Variant names follow the alphabet of flag names, at most 50 bytes. A flag named circuit-open.<key> takes no rule and no experiment. See targeting.md, experiments.md and metadata-and-expiry.md.

For each circuit declared to phil/circuit-breaker-bundle, the configuration provider also declares circuit-open.<key>, inactive, without rules, of kind ops, with the description Forces the circuit "<key>" open., unless flags declares that name itself. See circuit-breakers.md.

Services registered in every configuration

Service idClassTags
phil_feature_flags.provider.configurationConfiguredFlagProvider (factory fromConfiguration)—
phil_feature_flags.context_resolver.anonymousAnonymousContextResolver—
Phil\FeatureFlagsBundle\Application\Flag\FlagEvaluatorFlagEvaluatorkernel.reset (method reset)
Phil\FeatureFlagsBundle\Infrastructure\Profiler\FeatureFlagsDataCollectorFeatureFlagsDataCollectordata_collector (id phil_feature_flags)
Phil\FeatureFlagsBundle\Application\Flag\FlagCatalogFlagCatalog—
Phil\FeatureFlagsBundle\Infrastructure\Console\ListFlagsCommandListFlagsCommandconsole.command
Phil\FeatureFlagsBundle\Infrastructure\Http\FeatureGateListenerFeatureGateListenerkernel.event_listener on kernel.controller
Phil\FeatureFlagsBundle\Infrastructure\Routing\FlagRoutingConditionFlagRoutingConditionrouting.condition_service (alias feature_flags)
Phil\FeatureFlagsBundle\Infrastructure\Twig\FeatureFlagsExtensionFeatureFlagsExtensiontwig.attribute_extension, twig.runtime
Phil\FeatureFlagsBundle\Application\Circuit\FlagCircuitOverrideFlagCircuitOverridephil_circuit_breaker.override

Conditional services are listed with their option above; the complete inventory is in extension-points.md. All services are private except FlagAdminController; the class-named ones can be autowired by type.

Build-time validation

Every flag is parsed while the container is built, so a malformed entry fails cache:clear rather than the first request. Errors are InvalidConfigurationExceptions:

Message (shape)Cause
phil_feature_flags.flags.<name>: <reason>A malformed flag, rule, experiment or metadata, e.g. phil_feature_flags.flags.new-checkout: A percentage is between 0 and 100; got 120.
phil_feature_flags.flags.<name>: "<name>" is not a flag name: …A name outside the alphabet, or longer than 100 bytes.
phil_feature_flags.flags.circuit-open.<key>: A flag that forces a circuit open takes no rule and no experiment: …Rules or an experiment on a force-open flag.
phil_feature_flags.admin needs the toggle: every change it makes is authorized and audited there.admin enabled without toggle.
phil_feature_flags.toggle needs a state_store: a toggle writes to the store the evaluator reads.toggle enabled with neither state_store nor doctrine.
phil_feature_flags.<path> must be a service id.A service id option given as a non-string (an integer, a boolean), e.g. phil_feature_flags.toggle.authorizer must be a service id.
phil_feature_flags.providers must be a list of service ids.A non-string entry in providers.

The Symfony configuration tree adds its own errors before these (a missing attribute in a rule, a non-integer percentage, an empty string where cannotBeEmpty() applies). A service id that does not exist is reported by the container compiler, not by the bundle.