symfgenus/flagception-bundle
Symfony bundle integrating the Flagception feature-flag library. Toggle features via annotations, Twig, or service calls, with activators like cookie, session, environment, database, and expression-based rules for gradual rollouts and A/B testing.
symfgenus/flagception-bundle is a Symfony bundle around the flagception feature-flag core. The repository is a fork (2 stars, 0 dependents), so treat the upstream flagception/flagception-bundle docs as the real manual and diff this fork against upstream before committing to it.
Install and confirm compatibility:
composer require symfgenus/flagception-bundle
Open vendor/symfgenus/flagception-bundle/composer.json first — check the Symfony version constraint and whether the fork has diverged. If it targets an old Symfony, prefer upstream.
Register the bundle (config/bundles.php on Symfony 4+):
return [
// ...
Symfgenus\FlagceptionBundle\FlagceptionBundle::class => ['all' => true],
];
Confirm the actual bundle class name in src/ — forks sometimes rename the namespace.
Define a first feature in config/packages/flagception.yaml:
flagception:
features:
new_checkout:
default: false
constraints:
- name: environment
options:
env: ['dev', 'staging']
Use it. Inject the manager and call isActive():
use Flagception\Manager\FeatureManagerInterface;
final class CheckoutController
{
public function __construct(private FeatureManagerInterface $features) {}
public function __invoke(): Response
{
if ($this->features->isActive('new_checkout')) {
// new flow
}
}
}
If autowiring fails, check Resources/config/services.xml for the concrete service id (commonly flagception.manager.feature_manager) and alias it yourself.
First real use case: wrap one UI branch in Twig and one controller branch behind a config-defined flag. That gives you a deploy-without-release switch with zero database work.
Where to look when something is unclear, in order:
src/DependencyInjection/Configuration.php — the authoritative config schema for this fork.Resources/config/services.xml — service ids, tags, and autowiring aliases.flagception/flagception-bundle README and flagception/flagception core — activator names and expression syntax live there.src/Activator/ — available built-in activators (environment, date, expression, cookie/header/query, etc.).Declarative config first, code second. Keep flags in flagception.yaml until you need per-tenant or database-driven flags. Config-defined features are cache-warmable and reviewable in PRs; hardcoded if statements are not.
Pass context explicitly. The manager accepts a context array that activators and expressions read:
$active = $this->features->isActive('beta_dashboard', [
'user' => $user,
'ip' => $request->getClientIp(),
'locale' => $request->getLocale(),
]);
Build a small FeatureContextFactory that assembles this once (user, tenant, request metadata) so you don't scatter ad-hoc arrays across controllers.
Compose constraints; remember they AND together. Multiple entries under constraints are evaluated conjunctively. For "dev OR admin" use a single expression constraint rather than two entries:
constraints:
- name: expression
options:
expression: 'context["user"].isAdmin() or context["env"] == "dev"'
This requires symfony/expression-language; add it explicitly if it isn't pulled in.
Twig for presentation, manager for behavior. Use the bundle's Twig test/function (typically {% if feature('new_checkout') %} — verify the exact name in the bundle's Twig extension) for template branches, but gate controller/service behavior through the injected manager so it's testable and mockable.
Custom activators for rollout logic. Implement the core ActivatorInterface, return a boolean from activate(array $options, Context $context), and tag the service (commonly flagception.activator with an alias attribute). Keep activators stateless and cheap — they run on every isActive() call.
final class PercentageRolloutActivator implements ActivatorInterface
{
public function activate(array $options, Context $context): bool
{
$bucket = crc32((string) ($context['user']->getId() ?? '')) % 100;
return $bucket < (int) $options['percentage'];
}
}
Testing pattern. In config/packages/test/flagception.yaml, force flags to deterministic defaults (default: true or false) per test suite. For unit tests, implement FeatureManagerInterface with a stub returning a fixed map, or decorate the real manager. Avoid hitting the real manager in tests that don't care about flags.
Migration/cleanup discipline. Give every flag an owner and an expiry in a comment next to the YAML entry. Remove the flag and the dead branch in the same PR that flips the default. Feature flags rot faster than any other config.
DB-backed flags (optional). If you need runtime toggles, swap or decorate the feature manager with your own repository-backed implementation; keep the config-defined manager for static/emergency flags. Cache aggressively and expose an admin endpoint guarded by an existing flag.
diff -r against flagception/flagception-bundle and check the last commit date. If the fork only adds a namespace change, require upstream instead — you'll get security fixes and Symfony compatibility updates.isActive('typo') depends on the core version (often a FeatureNotFoundException, sometimes a default). Decide explicitly and wrap in your own FeatureFlags facade if you want a "false when undefined" policy.options; a typo like envs: instead of env: is silently ignored and your flag quietly stays off. Test each flag's on/off path once, and enable the bundle's debug mode if it offers one.bin/console debug:container --tag=flagception.activator) to confirm registration.expression option. If expressions come from a database/admin UI, restrict who can write them and consider a sandboxed evaluator.bin/console cache:clear in non-dev environments or the old flag map ships. In long-running workers (RoadRunner, Swoole), restart the worker on deploy.FeatureManagerInterface isn't autowirable, add an explicit alias in config/services.yaml rather than injecting the container. Container injection is the fast path to untestable code.is feature active) or function (feature('x')) in src/Twig/. A wrong call silently renders as false in some setups.composer.json ("symfgenus/flagception-bundle": "1.2.*" or a commit hash), and add a note in your upgrade runbook to re-evaluate switching to upstream at the next major Symfony upgrade.How can I help you explore Laravel packages today?