Table of Contents
Feature flags are supposed to give you control. But a commented-out check can silently break that control across your entire application. Here’s the bug that took us 3 hours to find.
The Setup
We had a database column for a feature flag:
// Migration
Schema::table('products', function (Blueprint $table) {
$table->boolean('enable_advanced_pricing')->default(false);
});
And a method on the model to check it:
class Product extends Model
{
public function hasAdvancedPricing(): bool
{
// return $this->enable_advanced_pricing;
return true; // TODO: Remove after testing
}
}
See the problem?
The Bug
That “TODO” never got removed. The actual database check was commented out, and the method always returned true.
This meant:
- The feature flag column in the database was completely ignored
- All products behaved as if advanced pricing was enabled
- Setting the flag to
falsein the database had zero effect - We thought we had gradual rollout control – we didn’t
How We Found It
We were debugging why pricing wasn’t working correctly for certain products. We checked the database:
SELECT id, name, enable_advanced_pricing
FROM products
WHERE id = 12345;
-- Result: enable_advanced_pricing = 0
Flag was off. But the code was still running advanced pricing logic. We dumped the method output:
dd($product->hasAdvancedPricing()); // bool(true)
Wait, what? The column says false, but the method returns true?
That’s when we found the commented-out check.
The Fix
Remove the hardcoded return value. Actually read the database:
class Product extends Model
{
public function hasAdvancedPricing(): bool
{
return (bool) $this->enable_advanced_pricing;
}
}
Or use a cast for cleaner syntax:
class Product extends Model
{
protected $casts = [
'enable_advanced_pricing' => 'boolean',
];
public function hasAdvancedPricing(): bool
{
return $this->enable_advanced_pricing;
}
}
The Lesson
Never hardcode feature flag return values. Not even for testing. If you need to force a value temporarily:
- Use environment variables:
return config('features.force_advanced_pricing', $this->enable_advanced_pricing); - Use a dedicated testing flag:
if (app()->environment('testing')) return true; - Better yet: use a proper feature flag service like Laravel Pennant
Catching This in Code Review
Look for these patterns in PRs:
// RED FLAG 1: Hardcoded return
public function hasFeature(): bool
{
return true; // or false
}
// RED FLAG 2: Commented database check
public function hasFeature(): bool
{
// return $this->feature_enabled;
return config('features.default');
}
// RED FLAG 3: TODO with hardcoded value
public function hasFeature(): bool
{
return true; // TODO: Fix this
}
Testing Feature Flags
Write tests that verify the flag actually controls behavior:
/** @test */
public function advanced_pricing_respects_database_flag()
{
$product = Product::factory()->create([
'enable_advanced_pricing' => false
]);
$this->assertFalse($product->hasAdvancedPricing());
$product->update(['enable_advanced_pricing' => true]);
$product->refresh();
$this->assertTrue($product->hasAdvancedPricing());
}
This test would have caught the commented-out check immediately.
Real Impact
This bug meant we couldn’t control the rollout of advanced pricing. All products got the new behavior simultaneously, instead of the gradual rollout we planned. It worked fine in development (where we wanted it on), but broke the production deployment strategy.
The fix took 2 minutes. Finding it took 3 hours.
Leave a Reply