Advanced Usage
Events
Section titled “Events”Toggl dispatches events that you can listen to for logging, analytics, or custom behavior.
UnknownFeatureResolved
Section titled “UnknownFeatureResolved”Triggered when an undefined feature is checked:
use Cline\Toggl\Events\UnknownFeatureResolved;
Event::listen(UnknownFeatureResolved::class, function ($event) { Log::warning('Unknown feature accessed', [ 'feature' => $event->feature, 'context' => $event->context, ]);});Custom Event Listeners
Section titled “Custom Event Listeners”// In EventServiceProviderprotected $listen = [ UnknownFeatureResolved::class => [ LogUnknownToggl::class, NotifyTeam::class, ],];Middleware
Section titled “Middleware”Custom Middleware
Section titled “Custom Middleware”namespace App\Http\Middleware;
use Cline\Toggl\Toggl;use Closure;
class RequireBetaAccess{ public function handle($request, Closure $next) { // Works with strings or enums if (! Toggl::for($request->user())->active('beta-access')) { abort(403, 'Beta access required'); }
return $next($request); }}Using an enum for type safety:
namespace App\Http\Middleware;
use App\Enums\FeatureFlag;use Cline\Toggl\Toggl;use Closure;
class RequireBetaAccess{ public function handle($request, Closure $next) { if (! Toggl::for($request->user())->active(FeatureFlag::BetaAccess)) { abort(403, 'Beta access required'); }
return $next($request); }}You can then register this middleware in your routes:
Route::middleware([RequireBetaAccess::class])->group(function () { Route::get('/beta/dashboard', [BetaController::class, 'dashboard']);});Custom Drivers
Section titled “Custom Drivers”Create Custom Driver
Section titled “Create Custom Driver”namespace App\Drivers;
use Cline\Toggl\Contracts\Driver;
class RedisDriver implements Driver{ public function __construct( protected \Illuminate\Redis\RedisManager $redis ) {}
public function get(string $feature, mixed $context): mixed { $key = "features:{$feature}:{$this->serializeContext($context)}"; return $this->redis->get($key); }
public function set(string $feature, mixed $context, mixed $value): void { $key = "features:{$feature}:{$this->serializeContext($context)}"; $this->redis->set($key, $value); }
// Implement other Driver methods...}Register Custom Driver
Section titled “Register Custom Driver”// In AppServiceProvideruse Cline\Toggl\Toggl;use App\Drivers\RedisDriver;
public function boot(): void{ Toggl::extend('redis', function ($app, $config) { return new RedisDriver($app->make('redis')); });}Use Custom Driver
Section titled “Use Custom Driver”'stores' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ],],Caching Strategies
Section titled “Caching Strategies”Eager Loading
Section titled “Eager Loading”// Load all features at once$user = User::with('features')->find(1);
// Access without additional queriesToggl::for($user)->active('feature-1');Toggl::for($user)->active('feature-2');Cache Warming
Section titled “Cache Warming”// Warm cache for common featuresforeach ($users as $user) { Toggl::for($user)->load([ 'premium-access', 'beta-features', 'advanced-analytics', ]);}
// With enums for type safetyforeach ($users as $user) { Toggl::for($user)->load([ FeatureFlag::PremiumAccess, FeatureFlag::BetaFeatures, FeatureFlag::AdvancedAnalytics, ]);}Manual Cache Control
Section titled “Manual Cache Control”// Flush all cached feature statesToggl::flushCache();
// Forget specific featureToggl::forget('feature-name');Testing
Section titled “Testing”Pest Helpers
Section titled “Pest Helpers”use Cline\Toggl\Toggl;
test('premium features require subscription', function () { $user = User::factory()->create(['subscription' => 'basic']);
Toggl::define('premium-support', fn($u) => $u->subscription === 'premium');
expect(Toggl::for($user)->active('premium-support'))->toBeFalse();
$user->subscription = 'premium'; $user->save();
Toggl::flushCache(); // Clear cached results
expect(Toggl::for($user)->active('premium-support'))->toBeTrue();});Activate Features in Tests
Section titled “Activate Features in Tests”beforeEach(function () { Toggl::activateForEveryone([ 'testing-mode', 'debug-toolbar', ]);});
test('feature is active in tests', function () { expect(Toggl::active('testing-mode'))->toBeTrue();});
// With enumsbeforeEach(function () { Toggl::activateForEveryone([ FeatureFlag::TestingMode, FeatureFlag::DebugToolbar, ]);});
test('feature is active in tests', function () { expect(Toggl::active(FeatureFlag::TestingMode))->toBeTrue();});Fake Features
Section titled “Fake Features”test('can fake features', function () { Toggl::define('feature-1', false); Toggl::define('feature-2', true);
// Override for test Toggl::activate('feature-1');
expect(Toggl::active('feature-1'))->toBeTrue();});Scheduled Tasks
Section titled “Scheduled Tasks”Monitor Feature Usage
Section titled “Monitor Feature Usage”protected function schedule(Schedule $schedule): void{ $schedule->call(function () { $features = Toggl::stored();
foreach ($features as $feature) { Metrics::gauge('feature.usage', 1, [ 'feature' => $feature, 'active' => Toggl::active($feature) ? 'true' : 'false', ]); } })->everyFiveMinutes();}Warn About Expiring Features
Section titled “Warn About Expiring Features”protected function schedule(Schedule $schedule): void{ $schedule->call(function () { $expiring = Toggl::expiringSoon(days: 7);
if (count($expiring) > 0) { Notification::route('slack', config('slack.webhook')) ->notify(new FeatureExpiringNotification($expiring)); } })->daily();}Performance Tips
Section titled “Performance Tips”-
Use array driver for ephemeral features
// Fast, in-memory, no persistence'default' => 'array', -
Batch load features
// ✅ Good - one queryToggl::for($user)->load(['f1', 'f2', 'f3']);// ❌ Avoid - multiple queriesToggl::for($user)->active('f1');Toggl::for($user)->active('f2');Toggl::for($user)->active('f3'); -
Cache resolver results
Toggl::define('expensive-check', function ($user) use ($cache) {return $cache->remember("feature-check-{$user->id}",3600,fn() => $this->expensiveCalculation($user));}); -
Use percentage strategy over database
// ✅ Fast - no DB lookupToggl::define('rollout')->strategy(new PercentageStrategy(25));// ❌ Slower - DB lookup per checkToggl::define('rollout', fn($u) =>DB::table('rollouts')->where('user_id', $u->id)->exists());
Best Practices
Section titled “Best Practices”-
Centralize feature definitions
app/Providers/FeatureServiceProvider.php public function boot(): void{$this->defineAllFeatures();}private function defineAllFeatures(): void{// All features in one placeToggl::define('feature-1', ...);Toggl::define('feature-2', ...);} -
Document feature purpose
// Purpose: Enable new checkout flow for Q1 2025 launch// Owner: Team Ecommerce// Rollout: 10% → 50% → 100% over 2 weeksToggl::define('new-checkout')->strategy(new PercentageStrategy(10))->expiresAfter(weeks: 2); -
Clean up old features
// Schedule regular auditsprotected function schedule(Schedule $schedule): void{$schedule->command('feature:audit')->monthly();} -
Monitor feature flags
Event::listen(UnknownFeatureResolved::class, function ($event) {// Alert if production code references undefined featureif (app()->environment('production')) {Sentry::captureMessage("Unknown feature: {$event->feature}");}});
Next Steps
Section titled “Next Steps”- Getting Started - Installation and setup
- Basic Usage - Core operations
- Strategies - Resolution strategies