Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pennant Laravel Package

laravel/pennant

Laravel Pennant is a simple, lightweight feature flag library for Laravel. Define and evaluate feature flags to safely roll out, test, and target functionality in your app. Official docs available on the Laravel website.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require laravel/pennant
   php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider" --tag="pennant-config"
   php artisan migrate
  • Publishes config (config/pennant.php) and runs migrations to create the feature_flags table.
  1. First Feature Flag:

    use Laravel\Pennant\Facades\Pennant;
    
    // Enable a boolean flag
    Pennant::enable('new-dashboard');
    
    // Check if a flag is active
    if (Pennant::isEnabled('new-dashboard')) {
        // Feature logic
    }
    
  2. Blade Directive (for views):

    @feature('new-dashboard')
        <div>New Dashboard Content</div>
    @endfeature
    

Key First Use Case

A/B Testing for a New Checkout Flow:

// Enable for 50% of users (randomized)
Pennant::enableForUsers('new-checkout', 0.5);

// Check in middleware
public function handle(Request $request, Closure $next) {
    if (Pennant::isEnabledForUser('new-checkout', $request->user())) {
        return redirect()->route('new.checkout');
    }
    return $next($request);
}

Implementation Patterns

Core Workflows

1. Flag Management

  • Enable/Disable:
    Pennant::enable('feature-x'); // Boolean (true)
    Pennant::disable('feature-x'); // Boolean (false)
    Pennant::set('feature-x', ['config' => 'value']); // Complex value
    
  • Bulk Operations:
    Pennant::setMany([
        'feature-a' => true,
        'feature-b' => ['tier' => 'premium'],
    ]);
    

2. Scoped Flags

  • User-Based:
    // Enable for specific users
    Pennant::enableForUser('premium-features', $user);
    
    // Check
    if (Pennant::isEnabledForUser('premium-features', $user)) { ... }
    
  • Role-Based (via scope):
    Pennant::enableForScope('admin-dashboard', 'admin');
    
    // Check
    if (Pennant::isEnabledForScope('admin-dashboard', 'admin')) { ... }
    

3. Middleware Integration

  • Global Check:
    use Laravel\Pennant\Http\Middleware\EnsureFeatureIsActive;
    
    // Route middleware
    Route::middleware([EnsureFeatureIsActive::class => 'new-api'])->group(...);
    
    // All features check
    Route::middleware([EnsureFeatureIsActive::class => 'all'])->group(...);
    
  • Custom Logic:
    public function handle($request, Closure $next, $feature) {
        if (!Pennant::isEnabled($feature)) {
            abort(403, "Feature {$feature} is disabled.");
        }
        return $next($request);
    }
    

4. Blade Directives

  • Conditional Rendering:
    @feature('dark-mode')
        <button class="dark-mode-toggle">Toggle Dark Mode</button>
    @endfeature
    
  • Fallback Content:
    @feature('new-header', false)
        <header class="new-header">...</header>
    @else
        <header class="old-header">...</header>
    @endfeature
    

5. Events & Hooks

  • Listen for Changes:
    use Laravel\Pennant\Events\FeatureUpdated;
    
    FeatureUpdated::listen(function (FeatureUpdated $event) {
        Log::info("Flag {$event->feature->name} updated to {$event->value}");
    });
    
  • Before Hooks (for async validation):
    Pennant::enable('payment-gateway', function ($feature) {
        if ($feature->value === 'stripe' && !config('services.stripe.key')) {
            throw new \Exception('Stripe key not configured!');
        }
    });
    

Integration Tips

1. Database Driver

  • Custom Table: Configure in config/pennant.php:
    'driver' => 'database',
    'table' => 'custom_feature_flags',
    
  • Connection: Specify for multi-database setups:
    'connection' => 'mysql_replica',
    

2. Cache Optimization

  • Flush Cache: After bulk updates:
    Pennant::setMany([...]);
    Pennant::flushCache(); // Clear cached flags
    
  • Cache Tags: Use tags in config to invalidate specific flags:
    'cache' => [
        'prefix' => 'pennant_',
        'tags' => ['features'], // Invalidate by tag
    ],
    

3. Testing

  • Mock Flags:
    Pennant::shouldReceive('isEnabled')->with('test-flag')->andReturn(true);
    
  • Test Helpers:
    use Laravel\Pennant\Testing\TestsPennant;
    
    class FeatureTest extends TestCase {
        use TestsPennant;
    
        public function test_flag() {
            $this->enable('test-flag');
            $this->assertEnabled('test-flag');
        }
    }
    

4. Custom Drivers

  • Extend Driver:
    use Laravel\Pennant\Contracts\Driver;
    
    class RedisDriver implements Driver {
        public function isEnabled($name) { ... }
        public function set($name, $value) { ... }
        // ...
    }
    
  • Register Driver:
    Pennant::extend('redis', function () {
        return new RedisDriver();
    });
    

5. API Endpoints

  • Admin Panel:
    Route::get('/flags/{name}', function ($name) {
        return Pennant::get($name);
    });
    
    Route::post('/flags', function (Request $request) {
        Pennant::set($request->name, $request->value);
        return response()->json(['status' => 'updated']);
    });
    

Gotchas and Tips

Pitfalls

  1. Scope Mismatches:

    • Issue: Forgetting to specify a scope when checking flags.
      // Fails silently if scope is missing
      Pennant::isEnabledForUser('flag', null); // Avoid!
      
    • Fix: Always pass a scope or use defaultScope():
      Pennant::setDefaultScope('user');
      Pennant::isEnabled('flag'); // Uses 'user' scope
      
  2. Cache Invalidation:

    • Issue: Flags not updating due to stale cache.
    • Fix: Call flushCache() after bulk operations or use cache tags:
      Pennant::setMany([...]);
      Cache::tags(['features'])->flush();
      
  3. Type Safety:

    • Issue: Primitive types (e.g., int, bool) failing scope validation.
    • Fix: Explicitly cast or use nameMap in decorators:
      Pennant::decorate('flag', function ($feature) {
          return (bool) $feature->value; // Force boolean
      });
      
  4. Migration Conflicts:

    • Issue: Table name collisions with existing migrations.
    • Fix: Customize the table name in config:
      'table' => 'app_feature_flags',
      
  5. Middleware Order:

    • Issue: Flags checked before user authentication.
    • Fix: Place EnsureFeatureIsActive after auth middleware:
      Route::middleware(['auth', EnsureFeatureIsActive::class => 'dashboard'])->group(...);
      
  6. Blade Directive Scope:

    • Issue: @feature not respecting the current scope.
    • Fix: Pass the scope explicitly:
      @feature('flag', 'user', auth()->user())
      
  7. Zero Values:

    • Issue: Boolean flags returning 0 as false (e.g., from DB).
    • Fix: Use Pennant::get('flag') to inspect raw values:
      $value = Pennant::get('flag'); // Returns 0 or 1
      if ($value === 1) { ... }
      

Debugging Tips

  1. Log Flag Checks:

    Pennant::enableLogging();
    // Logs all flag evaluations to storage/logs/pennant.log
    
  2. Inspect Raw Data:

    // Dump all flags for a scope
    dd(Pennant::getAllForScope('user', auth()->user()));
    
  3. Check Cache:

    // Clear cache and
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport