ylsideas/feature-flags
Extensible feature flags for Laravel to safely toggle code and features. Manage flags in application logic, routes, Blade views, scheduler tasks, and validation rules to support continuous integration and controlled rollouts.
Installation:
composer require ylsideas/feature-flags:^3.0
php artisan vendor:publish --provider="YlsIdeas\FeatureFlags\FeatureFlagsServiceProvider" --tag=config
config/features.php is published and configured (default uses in_memory driver).First Check:
use YlsIdeas\FeatureFlags\Facades\Features;
// Check if a feature is enabled
if (Features::accessible('new-dashboard')) {
// Feature is enabled
}
Define Flags:
Features::enable() method in a service provider or migration:
Features::enable('new-dashboard');
env() helper in the config file:
'flags' => [
'new-dashboard' => env('FEATURE_NEW_DASHBOARD', false),
],
Protect a route from being accessed if a feature is disabled:
Route::get('/admin/dashboard', function () {
return view('dashboard');
})->middleware('feature:new-dashboard');
app/Http/Kernel.php:
protected $routeMiddleware = [
'feature' => \YlsIdeas\FeatureFlags\Http\Middleware\FeatureMiddleware::class,
];
Use Features::accessible() for runtime checks:
if (Features::accessible('experimental-api')) {
$response = $this->callExperimentalApi();
} else {
$response = $this->fallbackApi();
}
Filter Eloquent queries dynamically:
$activeUsers = User::whenFeatureIsAccessible('premium-features')
->where('subscription', 'premium')
->get();
Skip scheduled tasks if a feature is disabled:
$schedule->command('send-promotional-emails')->when(Features::accessible('promo-campaign'));
Add feature-dependent validation:
$request->validate([
'premium_feature' => 'required_if:feature.enabled,new-premium-feature',
]);
AppServiceProvider:
Validator::extend('feature', function ($attribute, $value, $parameters, $validator) {
return Features::accessible($parameters[0]);
});
Hide/show Blade sections:
@feature('dark-mode')
<div class="dark-theme">...</div>
@endfeature
AppServiceProvider:
Blade::directive('feature', function ($expression) {
return "<?php if (\\YlsIdeas\\FeatureFlags\\Facades\\Features::accessible({$expression})): ?>";
});
Blade::directive('endfeature', function () {
return "<?php endif; ?>";
});
Mock flags in tests:
Features::fake(['new-dashboard' => true]);
// Test logic that depends on the flag
$this->assertTrue(Features::accessible('new-dashboard'));
// Reset fakes
Features::fake();
Extend the pipeline with custom drivers (e.g., database, Redis):
// config/features.php
'drivers' => [
\YlsIdeas\FeatureFlags\Drivers\DatabaseDriver::class,
\YlsIdeas\FeatureFlags\Drivers\CacheDriver::class,
],
Caching Issues:
CacheDriver may not reflect real-time changes. Use Features::refresh() or clear cache manually:
php artisan cache:clear
cache_ttl in features.php (e.g., 60 seconds for testing).Middleware Misconfiguration:
FeatureMiddleware in app/Http/Kernel.php will silently fail.php artisan route:list to verify middleware is applied.Boolean Casting:
$isAccessible = (bool) Features::accessible('flag-name');
Environment Overrides:
.env (e.g., FEATURE_NEW_DASHBOARD=true) take precedence over config file values.php artisan config:clear to reset overrides during development.Query Builder Scope Conflicts:
whenFeatureIsAccessible() with other scopes may cause SQL errors if conditions are mutually exclusive.orWhere or separate queries.Fake Scope Leaks:
Features::fake() after tests.Features::fake(['flag' => true])->andReturn(false) for partial mocking.Inspect Flag State:
php artisan feature:state new-dashboard
Enable Debug Logging:
// config/features.php
'debug' => env('APP_DEBUG', false),
storage/logs/laravel.log.Check Pipeline Order:
config/features.php. The first true result wins.DatabaseDriver) earlier in the array.Custom Drivers:
YlsIdeas\FeatureFlags\Contracts\Driver:
class SentryDriver implements Driver {
public function accessible(string $name): bool {
return Sentry::getUser()->hasRole('admin') && config("features.flags.{$name}");
}
}
config/features.php:
'drivers' => [
\App\Drivers\SentryDriver::class,
],
Event Handlers:
Features::enable('new-feature')->listen(function ($flag) {
Log::info("Flag {$flag->name} enabled by {$flag->enabledBy}");
});
Expiration Logic:
FeatureExpired listener for time-bound flags:
Features::expire('limited-offer')->after(function ($flag) {
// Send notification or clean up
});
IDE Support:
php artisan ide-helper:generate --nowrite
php artisan ide-helper:meta
Avoid Redundant Checks:
Features::accessible() in a variable if called multiple times in a method:
$canAccess = Features::accessible('flag');
if ($canAccess) { /* ... */ }
Lazy-Load Drivers:
Driver lazily.Batch Flag Updates:
Features::enable(['flag1', 'flag2']) to update multiple flags in a single operation.How can I help you explore Laravel packages today?