gracious/feature-flag-bundle
Composer 安装命令:
composer require gracious/feature-flag-bundle
包简介
Lightweight feature flag management for Symfony applications.
README 文档
README
Lightweight feature flags for Symfony. Define flags in config, then check them from services, controllers, Twig, routes, and controller attributes.
- PHP
>=8.3, Symfony^7.0 || ^8.0, Twig 3 (optional).
Features
- Config-driven flags with optional descriptions.
- One default manager plus any number of named managers (separate flag groups).
- Check flags in PHP, Twig, route defaults, or
#[RequireFeature]attributes. - Per-process runtime overrides (
enable/disable/reset). - Optional read-only REST endpoint with a kill switch.
Installation
composer require gracious/feature-flag-bundle
With Symfony Flex this is all you need. Without Flex, register the bundle manually:
// config/bundles.php return [ // ... Gracious\FeatureFlagBundle\GraciousFeatureFlagBundle::class => ['all' => true], ];
Quick start
# config/packages/gracious_feature_flag.yaml gracious_feature_flag: flags: new_checkout: enabled: true description: 'New checkout flow' beta_search: enabled: false # 'enabled' defaults to false
use Gracious\FeatureFlagBundle\Flag\FeatureFlagManagerInterface; final class CheckoutService { public function __construct(private FeatureFlagManagerInterface $flags) {} public function run(): void { if ($this->flags->isEnabled('new_checkout')) { // new flow } } }
Configuration
gracious_feature_flag: # default manager flags flags: new_checkout: { enabled: true, description: 'New checkout flow' } beta_search: { enabled: false } # optional: extra named managers, each its own flag group managers: billing: flags: invoices_v2: { enabled: true } # optional: exception thrown when a guard fails (route / attribute) exception: class: Gracious\FeatureFlagBundle\Exception\FeatureNotAvailableException status_code: 404 factory: ~ # service id implementing ExceptionFactoryInterface; wins over 'class' # optional: REST endpoint kill switch api: enabled: true # false => endpoints return 404
Manager API
$flags->isEnabled('new_checkout'); // bool $flags->has('new_checkout'); // bool: flag is defined $flags->get('new_checkout'); // Flag VO (name, enabled, description) $flags->all(); // array<string, Flag> // runtime overrides (this PHP process only; see Limitations) $flags->enable('beta_search'); $flags->disable('new_checkout'); $flags->reset('beta_search'); // back to the configured value
Unknown flag names throw UnknownFeatureException.
Named managers
Each named manager is its own service. Autowire it by the variable name <name>Manager:
public function __construct(FeatureFlagManagerInterface $billingManager) {}
To resolve a manager by name at runtime, inject the ManagerRegistry:
use Gracious\FeatureFlagBundle\Flag\ManagerRegistry; public function __construct(private ManagerRegistry $registry) {} $this->registry->get('billing')->isEnabled('invoices_v2'); $this->registry->getDefault()->isEnabled('new_checkout');
Unknown manager names throw UnknownManagerException.
Twig
{% if feature('new_checkout') %}
<a href="/checkout/new">Try the new checkout</a>
{% endif %}
{# named manager (second argument) #}
{% if feature('invoices_v2', 'billing') %} ... {% endif %}
{# as a test #}
{% if 'beta_search' is feature_enabled %} ... {% endif %}
The Twig extension registers only when Twig is installed.
Route guards
Guard a route with the _feature_flag default. The string form requires the flag enabled:
beta_page: path: /beta controller: App\Controller\BetaController defaults: _feature_flag: beta_search
The array form sets the required state and an optional manager:
legacy_page: path: /legacy controller: App\Controller\LegacyController defaults: _feature_flag: { name: legacy, enabled: false, manager: default }
When the requirement is not met, the configured exception is thrown (404 by default).
Attribute guards
#[RequireFeature] works on a controller class or method and is repeatable:
use Gracious\FeatureFlagBundle\Attribute\RequireFeature; #[RequireFeature('new_checkout')] // class: require enabled final class CheckoutController { #[RequireFeature('legacy', enabled: false)] // require disabled public function index(): Response { /* ... */ } #[RequireFeature('invoices_v2', manager: 'billing')] public function invoices(): Response { /* ... */ } }
Custom exception
For full control over the failure response, provide a factory service:
use Gracious\FeatureFlagBundle\Exception\ExceptionFactoryInterface; use Gracious\FeatureFlagBundle\Flag\Flag; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; final class AccessDeniedExceptionFactory implements ExceptionFactoryInterface { public function create(Flag $flag, bool $required): \Throwable { return new AccessDeniedHttpException( sprintf('Feature "%s" gate failed.', $flag->name), ); } }
gracious_feature_flag: exception: factory: App\FeatureFlag\AccessDeniedExceptionFactory
Alternatively set exception.class to any class with a (string $name, int $statusCode)
constructor. A factory always wins over class.
REST endpoint
The endpoint is read-only and opt-in. Import the routes to enable it:
# config/routes/gracious_feature_flag.yaml feature_flags: resource: '@GraciousFeatureFlagBundle/config/routes.php' prefix: /_feature-flags trailing_slash_on_root: false
| Method | Path | Description |
|---|---|---|
| GET | /_feature-flags |
list all flags (default manager) |
| GET | /_feature-flags/{name} |
read a single flag |
Both accept ?manager=<name>. Unknown flag or manager returns 404.
curl http://localhost/_feature-flags
# [{"name":"new_checkout","enabled":true,"description":"New checkout flow"}]
trailing_slash_on_root: false keeps the list route at /_feature-flags so the request
returns 200 directly instead of a 301 redirect to /_feature-flags/.
Disabling the endpoint. Set api.enabled: false. Both routes then return 404 even if
still imported, a hard kill switch independent of routing.
Security: these endpoints expose flag names and states and are not protected by the bundle. Restrict the prefix to a dev/internal firewall or guard the import with access control before exposing it in production.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-02