yannickl88/features-bundle
Symfony bundle for adding feature flags to your application. Define features, enable or disable them per environment, and gate code paths with simple checks for safer rollouts, gradual releases, and controlled deployments.
Install and register
composer require yannickl88/features-bundle
Symfony Flex usually registers the bundle automatically. If not, add it to config/bundles.php:
return [
// ...
Yannickl88\FeaturesBundle\FeaturesBundle::class => ['all' => true],
];
Where to look first (read the vendor source — the README is short and there are no dependents to learn from):
vendor/yannickl88/features-bundle/README.md — usage examples and the public API.vendor/yannickl88/features-bundle/DependencyInjection/Configuration.php — the exact config tree keys and defaults. This is the authoritative answer to "what can I configure".vendor/yannickl88/features-bundle/Resources/config/services.* — service ids and autowiring aliases. Confirm the id before injecting.Twig/ and EventListener/ (if present) — how tags are exposed to templates and whether a request listener resolves tags automatically.Minimal configuration
# config/packages/features.yaml
features:
tags:
new_checkout: true
beta_dashboard: false
First use case — gate a new code path behind a tag:
final class CheckoutController
{
public function __construct(private FeatureTagManager $features) {}
// or inject the service id you found via `bin/console debug:container features`
public function checkout(): Response
{
if ($this->features->isEnabled('new_checkout')) {
return $this->newFlow();
}
return $this->legacyFlow();
}
}
Verify the wiring immediately with bin/console debug:container --show-private | grep -i feature and bin/console debug:config features.
1. Centralize access behind your own interface. The bundle is small (19 stars, 0 dependents), so don't let its service leak through your codebase:
interface FeatureFlags
{
public function isEnabled(string $tag): bool;
}
final class BundleFeatureFlags implements FeatureFlags
{
public function __construct(private FeatureTagManager $manager) {}
public function isEnabled(string $tag): bool
{
return $this->manager->isEnabled($tag);
}
}
Alias the interface in services.yaml. If you later swap to a different flag library, only one class changes.
2. Config-per-environment is the sweet spot. Put defaults in features.yaml and override per environment or per deployment:
# config/packages/features.yaml
features:
tags:
new_checkout: false
# config/packages/prod/features.yaml
features:
tags:
new_checkout: '%env(bool:FEATURE_NEW_CHECKOUT)%'
This gives ops a runtime switch without a redeploy of code — just an env var change and container rebuild/cache clear.
3. Template branching. If the bundle ships a Twig extension, use {% if feature('new_checkout') %} around the new markup and keep the legacy branch. If no extension is registered, expose a Twig global from your own service instead of calling the manager from templates.
4. Controller attributes / annotations. If the bundle supports tagging actions (check the README), prefer that for coarse endpoint gating and fall back to explicit isEnabled() checks for finer logic. Don't mix both styles for the same feature.
5. Progressive rollout workflow. Treat tags as short-lived:
false.Keep a config/features.yaml-adjacent inventory (or a simple markdown table) listing tag name, owner, and removal date. Feature tags rot fast; with a low-traffic package nobody will enforce cleanup for you.
6. Testing.
config/packages/test/features.yaml, set every tag explicitly so tests are deterministic — never rely on production defaults.7. Integration tips.
Entity//Doctrine/ in the bundle), run migrations as part of deploy and warm any cache the bundle provides.%env()% indirection so you can toggle without touching code, but remember container values are compiled.Strict config tree. Symfony's Configuration rejects unknown keys with a InvalidConfigurationException at cache:clear. If a key seems ignored, run bin/console debug:config features to see the normalized values and compare against DependencyInjection/Configuration.php — don't guess from the README.
Autowiring may not be configured. Small bundles sometimes register services without a class-name alias. If FeatureTagManager can't be autowired, bind it explicitly:
services:
Yannickl88\FeaturesBundle\FeatureTagManager:
alias: 'features' # confirm the real id via debug:container
Config is compiled — changes need a cache clear. Toggling a tag defined in YAML does nothing until the container is rebuilt (bin/console cache:clear). This is the most common "the flag didn't work" report. If you need runtime toggles without a rebuild, drive the value through an env var (still requires rebuild for compiled params) or use whatever runtime/storage backend the bundle offers.
Environment precedence bites. Files under config/packages/prod/ override the base file; an env-var fallback written as %env(bool:FEATURE_X)% will fail hard if the variable is unset. Provide a default: %env(default:false:bool:FEATURE_X)%.
Templates. If the Twig extension isn't auto-registered on your Symfony version, register it manually and confirm with bin/console debug:twig | grep -i feature. A missing function throws at render time, often only on the page you gated — check both branches in a smoke test.
Version/Symfony compatibility. Last release was June 2024; confirm the constraint in composer.json supports your Symfony major (6.x/7.x). With only 19 stars and zero dependents there is no community pressure to keep up with new Symfony releases — treat upgrades as your responsibility and pin the version in composer.json.
Debugging checklist.
bin/console debug:container <service> — is it registered and public/aliasable?bin/console debug:config features — did my key get merged as expected?bin/console cache:clear — did the compiled config actually change?Extension points. Decorate the bundle's tag manager (or wrap it in your FeatureFlags adapter) to add cross-cutting behavior: audit logging of flag evaluations, per-user/cohort resolution, or a fallback to a remote flag provider. Prefer decoration over forking; the package is small enough that a fork is tempting, but a decorator survives vendor updates and keeps you on the MIT-licensed upstream.
Risk note. Zero dependents and a low star count mean no external battle-testing. Read the tag-resolution code before relying on it for anything security-sensitive, avoid using feature tags as an authorization mechanism (a flag is not a permission check), and keep the gated code paths independently tested so a broken flag evaluation fails safe rather than opening a path you didn't intend.
How can I help you explore Laravel packages today?