Separate Deployment from Release
Deploy code anytime. Release features when ready.
Basic Implementation
// config/features.php
return [
'new_checkout' => env('FEATURE_NEW_CHECKOUT', false),
'dark_mode' => env('FEATURE_DARK_MODE', true),
];
// Usage
if (config('features.new_checkout')) {
return view('checkout.new');
}
return view('checkout.old');
User-Based Flags
class FeatureFlag
{
public static function enabled(string $feature, ?User $user = null): bool
{
$flag = self::find($feature);
// Global flag
if ($flag->enabled_for_all) return true;
// User-specific
if ($user && $flag->users->contains($user->id)) return true;
// Percentage rollout
if ($user && $flag->percentage > 0) {
return (crc32($user->id . $feature) % 100) < $flag->percentage;
}
return false;
}
}
In Templates
@feature('new_checkout')
<x-new-checkout />
@else
<x-old-checkout />
@endfeature
Gradual Rollout
Day 1: 5% of users
Day 3: 25% of users
Day 5: 50% of users
Day 7: 100% of users
Monitor metrics at each stage.
Testing
public function test_new_checkout_flow()
{
// Enable flag for test
config(['features.new_checkout' => true]);
$response = $this->post('/checkout', $data);
$response->assertOk();
}
Clean Up Old Flags
Flags are debt. Remove when:
- Feature is 100% rolled out
- Feature is permanently removed
// TODO: Remove after 2024-02-01 when new_checkout is default
if (Feature::enabled('new_checkout')) {
Services
For more features, consider:
- LaunchDarkly
- Split
- Flagsmith
- Laravel Pennant
