adespresso/feature-bundle
Symfony bundle for feature releases and rollouts. Manage feature flags and enable new functionality for specific subsets of users. Includes API docs, documentation in Resources/doc, tests, and Apache 2.0 license.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require adespresso/feature-bundle
Register the bundle in config/bundles.php (Symfony) or config/app.php (Laravel via Symfony bridge):
return [
// ...
Adespresso\FeatureBundle\AdespressoFeatureBundle::class => ['all' => true],
];
Configuration: Publish the default config:
php artisan vendor:publish --provider="Adespresso\FeatureBundle\AdespressoFeatureBundle" --tag="config"
Edit config/feature_bundle.php to define your feature flags (e.g., new_dashboard, beta_payment).
First Use Case:
Enable a feature for a user group (e.g., premium_users) via CLI:
php bin/console feature:enable new_dashboard premium_users
Check if a feature is enabled in a controller:
use Adespresso\FeatureBundle\FeatureChecker;
public function index(FeatureChecker $featureChecker)
{
if ($featureChecker->isEnabled('new_dashboard')) {
return view('new_dashboard');
}
return view('old_dashboard');
}
User Groups:
Define groups in config/feature_bundle.php under groups:
'groups' => [
'premium_users' => ['user_id' => 1, 'user_id' => 2], // Hardcoded (for testing)
// OR use a service to dynamically fetch users (e.g., from DB)
],
Feature Flag Management:
php bin/console feature:enable new_dashboard premium_users
php bin/console feature:disable new_dashboard
php bin/console feature:toggle new_dashboard --all
Dynamic User Grouping:
// src/Service/UserGroupProvider.php
class UserGroupProvider implements UserGroupProviderInterface
{
public function getGroupUsers(string $groupName): array
{
return User::where('role', $groupName)->pluck('id')->toArray();
}
}
config/feature_bundle.php:
'user_group_provider' => App\Service\UserGroupProvider::class,
Integration with Laravel:
FeatureChecker to Laravel’s container in AppServiceProvider:
public function register()
{
$this->app->bind(FeatureChecker::class, function ($app) {
return new FeatureChecker(
$app['feature.manager'],
$app['feature.storage'],
$app['feature.user_group_provider']
);
});
}
// app/Http/Middleware/FeatureGate.php
public function handle($request, Closure $next)
{
if (!$this->featureChecker->isEnabled('required_feature')) {
abort(403, 'Feature not available for your account.');
}
return $next($request);
}
Environment-Specific Flags:
environment key in config/feature_bundle.php to enable features per environment:
'features' => [
'new_dashboard' => [
'enabled' => env('FEATURE_NEW_DASHBOARD', false),
'groups' => ['premium_users'],
],
],
Database Backend:
doctrine/doctrine-bundle):
'storage' => [
'type' => 'database',
'connection' => 'default',
],
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
User Group Staleness:
UserGroupProvider) for production.Caching Issues:
php artisan cache:clear
php artisan config:clear
feature:clear-cache command if provided.Laravel-Symfony Bridge Quirks:
FeatureChecker is properly bound to the container.AppServiceProvider).FeatureBundle to add Laravel-specific logic.Permission Overrides:
--all can’t be overridden by user groups. Use --except carefully:
php bin/console feature:enable new_dashboard --all --except beta_testers
Database Schema Mismatch:
feature and feature_group tables exist. Run migrations if missing.Check Feature Status:
feature:list command to debug enabled features:
php bin/console feature:list
FeatureChecker and log results:
$featureChecker->isEnabled('feature_name', ['user_id' => 123]);
Log User Group Resolution:
UserGroupProvider to log which users are in which groups:
public function getGroupUsers(string $groupName): array
{
$users = User::where('role', $groupName)->pluck('id')->toArray();
\Log::debug("Group {$groupName} resolved to users: " . json_encode($users));
return $users;
}
Environment-Specific Debugging:
config/feature_bundle.php:
'debug' => env('APP_DEBUG', false),
.env:
APP_DEBUG=true
Custom Storage Backends:
Adespresso\FeatureBundle\Storage\StorageInterface for Redis, Memcached, etc.:
class RedisStorage implements StorageInterface
{
public function saveFeature(Feature $feature) { /* ... */ }
public function getFeature(string $name) { /* ... */ }
}
'storage' => [
'type' => 'custom',
'class' => App\Storage\RedisStorage::class,
],
Event Listeners:
// src/EventListener/FeatureListener.php
public static function getSubscribedEvents()
{
return [
FeatureEvents::FEATURE_ENABLED => 'onFeatureEnabled',
];
}
public function onFeatureEnabled(FeatureEnabledEvent $event)
{
\Log::info("Feature {$event->getFeatureName()} enabled for groups: " . implode(', ', $event->getGroups()));
}
Custom Feature Logic:
FeatureChecker to add conditions (e.g., time-based, geo-based):
class CustomFeatureChecker extends FeatureChecker
{
public function isEnabled(string $name, array $context = []): bool
{
if ($name === 'time_limited_feature' && now()->hour < 9) {
return false;
}
return parent::isEnabled($name, $context);
}
}
AppServiceProvider.API for Frontend:
Route::get('/api/features', function (FeatureChecker $featureChecker, Request $request) {
return [
'new_dashboard' => $featureChecker->isEnabled('new_dashboard', ['user_id' => $request->user()->id]),
];
});
Avoid Runtime Group Resolution:
// app/Console/Commands/RefreshUserGroups.php
public function handle()
{
$provider = $this->container->get(UserGroupProvider::class);
foreach (config('feature_bundle.groups') as $group => $users) {
$provider->refreshGroup($group);
}
}
How can I help you explore Laravel packages today?