Installation:
composer require ajgarlag/feature-flag-bundle
Enable the bundle in config/bundles.php:
Ajgarlag\FeatureFlagBundle\FeatureFlagBundle::class => ['all' => true],
First Feature Flag:
Create a simple feature flag class with the #[AsFeature] attribute:
// src/Feature/XmasFeature.php
namespace App\Feature;
use Ajgarlag\FeatureFlagBundle\Attribute\AsFeature;
#[AsFeature('xmas')]
final class XmasFeature {
public function __invoke(): bool {
return date('m-d') === '12-25';
}
}
Usage in Code:
Inject the FeatureFlagChecker service and check flags:
use Ajgarlag\FeatureFlagBundle\FeatureFlagChecker;
class SomeService {
public function __construct(private FeatureFlagChecker $featureFlagChecker) {}
public function doSomething() {
if ($this->featureFlagChecker->isEnabled('xmas')) {
// Enable Christmas logic
}
}
}
Dynamic Feature Toggles:
Use #[AsFeature] on methods to dynamically enable/disable logic:
#[AsFeature('new_ui')]
public function isNewUIEnabled(): bool {
return $this->user->hasRole('premium');
}
Context-Aware Flags:
Implement a custom ProviderInterface to fetch flags from external sources (e.g., database, API):
class DatabaseProvider implements ProviderInterface {
public function get(string $featureName): ?callable {
return fn() => $this->db->getFlag($featureName, $this->context);
}
}
Route-Based Feature Gating:
Use feature_is_enabled in route conditions to hide/show endpoints:
#[Route('/admin', condition: "feature_is_enabled('admin_panel')")]
public function adminDashboard() {}
Twig Integration: Conditionally render UI elements:
{% if feature_is_enabled('dark_mode') %}
<link rel="stylesheet" href="{{ asset('css/dark.css') }}">
{% endif %}
FeatureFlagChecker over direct service calls for testability.#[AutoconfigureTag] with priority to control provider resolution order.GitlabProvider) to reduce external API calls.Provider Order: The first provider returning a non-null result wins. Ensure high-priority providers (e.g., database) are last if they should override defaults.
Attribute Naming:
Omitting the name in #[AsFeature] uses the FQCN, which can lead to verbose flag names (e.g., App\Feature\XmasFeature).
Route Conditions:
feature_is_enabled() in route conditions evaluates at request time, not compile time. Avoid complex logic here.
Testing:
Mock FeatureFlagChecker or use a test provider to isolate feature logic:
$this->featureFlagChecker->shouldReceive('isEnabled')->with('xmas')->andReturn(true);
Check Registered Flags: Dump all available flags via:
$flags = $this->container->get('feature_flag.checker')->getAllFeatures();
Provider Debugging: Implement a debug provider to log resolution:
class DebugProvider implements ProviderInterface {
public function get(string $featureName): ?callable {
return fn() => error_log("Checking flag: $featureName");
}
}
Custom Providers:
Extend ChainProvider to add logic (e.g., fallback to defaults if no provider matches).
Attribute Enhancements:
Add metadata to #[AsFeature] (e.g., description, defaultValue) via a custom attribute class.
Event Listeners: Trigger events when flags are evaluated (e.g., for analytics):
$dispatcher->addListener('feature_flag.checked', fn($event) => $this->logFlagCheck($event));
How can I help you explore Laravel packages today?