What Are Feature Toggles/Feature Flags (PHP Example)

One of the hardest parts about shipping code is working on a large feature while other developers are working on the same portions of the code. There are two ways that we can do this.

The first is that we can create a branch in our revision control software and develop the whole feature in the branch over several weeks or months. Every day we’ll want to merge in the changes that others are making so we have our code up to date. The annoying part of this process is that we’re going to get stuck merging in other developers’ changes because they’re unaware of how our changes are affecting the same code. These long-lived branches tend to be hard to merge back into the main branch and are a headache for all involved.

The second method is to develop small changes that take no longer than a day or so to create and are constantly being merged into the main branch. It makes our job much easier. The downside to this is that we then have to hide the new feature from our users until it’s ready to be deployed.

In this article we’ll explain how to use feature toggles to do just that.

What Are Feature Toggles

Feature Toggles are a powerful technique that allows our team to modify system behavior without directly changing the code. This is done by setting configuration settings that hide features or that allow swapping out one set of logic for another dynamically. This allows us to develop new features in production code or only allow some users to access some features.

A downside to feature toggles is that they increase the complexity of our code and bugs thrive in complexity so it’s important that we do our best to implement them as cleanly as possible and if possible constrain the total number of toggles inside our software.

Feature toggles can be referred to by multiple names:

  • Feature toggle
  • Feature flipper
  • Conditional feature
  • Feature switch
  • Feature control
  • Release toggle

We’re going to try to stick to “feature toggle” in this article but we might flip them around.

When to Use Feature Toggles

Deciding how the feature toggle will be enabled is generally the first step to creating one. It can be decided by our product team, if the feature needs to be enabled on a per-client or per-user basis, by our OPs team if it needs to be disabled for maintenance, or by our development team if we need to roll out the changes or provide continuous integration. It could be a combination of all of them or we might decide we don’t need one.

There are various needs that feature toggles can fulfill.

Our example before was a case where we needed to make a large change to our system that we believe will take several days or weeks so maintaining the change becomes a maintenance task of its own. The solution to this is to merge in the changes often but then hide the feature from users. Once the feature is fully developed and tested we can then turn it on.

We could also use feature toggles to create a canary launch of a feature which is where we slowly roll out new features to our users. This is helpful if we’re wary of how the feature will be used in practice and want to get some test information back about how our users are interacting with the new feature. More and more software, especially SaaS, is deploying new features using this method.

We can also use feature toggles as a way to provide different levels of access to the system. For example, we might provide three tiers of subscription that give our users access to different features depending on their subscription level.

Another prime example is giving our operations team the ability to selectively shut down portions of our software when we need to perform maintenance. For example, we might be rebuilding or altering a large table and instead of letting our users run into an error when they attempt to use the feature, we can disable it so they’re aware of the issue with a helpful explanation of what’s going on and not a generic error page.

It’s important to remember that feature toggles add a development overhead to our project. Instead of just creating a new feature and letting everyone have access we have multiple paths our code can traverse that need to be tested. If the feature is eventually going to be rolled out to everyone the code needs to have a “cleanup” process to remove the extra logic to remove the toggle once it’s been enable for everyone.

Enabling a Feature Toggle

There are a couple of ways that we can determine who has access to a feature. Because we’re already using a database to store our users we can add a new column to the users table and make the determination using that column.

public function canSeeInterestedEventsPage(): bool
{
    return $this->can_see_interested_events_page == 1;
}

Then we’ll need a process to turn this column to 1 for a set of users every hour/day/week/month depending on how much data we want before proceeding.

It’s nice to have a set of thresholds so we “ramp” up the number of users with access to the feature.

  1. 0.05% of Users
  2. 0.5% of Users
  3. 1% of Users
  4. 5% of Users
  5. 25% of Users
  6. 50% of Users
  7. 100% of Users

We can do this manually or automatically. Automatic is nice because no additional involvement is required and stopping the rollout can happen if there’s a problem. Manual is nice because we can take a good look at our metrics to see how the feature is being used before the rollout continues.

The other option is to roll out the feature algorithmically.

public function canSeeInterestedEventsPage(): bool
{
    $percentageWithAccess = 1;
    return $this->id % 100 <= $percentageWithAccess;
}

In this code sample, the 1 indicates the percentage of users who should have access to this feature. In the least complex method for rolling this out, we can manually increase that value. A better process is to create a function to determine what that number is based on the date so the feature will continue to roll out to our users automatically based on the date. We could get complicated with the logic for this but it’s best to keep it simple. The downside to this function above is that the same users will be the “test subjects” every time and they might be annoyed at running into errors all the time.

We like the database rollout process better because we can randomize who gets the new feature or even assign the new feature to individuals who haven’t been in the earlier tiers the last couple times.

New Feature With A Toggle Example

As an example, let’s add a new feature to our system that allows users to view all of the events that they’re interested in attending. We want to slowly roll this feature out to our users so we’re going to use a feature toggle to do that.

Because it’s a new feature, when we’re developing the feature we’ll want to make sure we add logic to the controllers/endpoints that prevents users who don’t have the feature turned on from accessing the page.

public function index(): View
{
    // hard fail if they shouldn't see this
    if (!$user->canSeeInterestedEventsPage()) {
        abort(401)
    }

    // otherwise...
}

Then whenever we create a link to the new controllers we’ll hide them for users who don’t have the feature turned on.

<li><a href="/">Home</a></li>
<?php if ($user->canSeeInterestedEventsPage()): ?>
    <li><a href="/events">Events</a></li>
<?php endif;?>

This method of hiding the UI elements from our users is by far the simplest way to implement a feature toggle.

The cleanup for this example is that after we’ve rolled out the feature and know we’re not going back we need to remove the column from the users and delete the code that calls User::canSeeInterestedEventsPage(). It’s mostly an easy process but does require some testing.

Replacing Logic With A Toggle Example

As another example, we’re going to change how we charge our users. We currently charge differently depending on the size of the event being hosted but we need to raise our prices for all events. We’re going to grandfather in our existing clients until the end of the year so we need to be able to turn the new prices on for new users but still allow those old prices.

Our current pricing logic looks like this.

if ($event->size < 100) {
    $cost = 2;
} elseif ($event->size < 1000) {
    $cost = 20;
} else {
    $cost = 200;
}

Our new pricing logic will look like this.

if ($use2022Pricing) {
    if ($event->size < 100) {
        $cost = 2;
    } elseif ($event->size < 1000) {
        $cost = 20;
    } else {
        $cost = 200;
    }
} else {
    if ($event->size < 100) {
        $cost = 1;
    } elseif ($event->size < 1000) {
        $cost = 10;
    } else {
        $cost = 100;
    }
}

We’re not a huge fan of this because eventually, we’re going to have to clean up these if statements. A better solution is to create a class to handle which pricing model we’re using. We’re going to have it derive from a common interface so we can make sure the same functions exist in every type.

interface PricingModel {
    public function getPrice(Event $event): float;
}

Then we can create a class that implements our current pricing model. Notice how it’s reduce the number of if blocks as well.

class OriginalPricingModel implements PricingModel
{
    public function getPrice(Event $event): float
    {
        if ($event->size < 100) {
            return 1;
        }
        
        if ($event->size < 1000) {
            return 10;
        }

        return 100;
    }
}

Then we can quickly add a class for our updated pricing model.

class UpdatedPricingModel implements PricingModel
{
    public function getPrice(Event $event): float
    {
        if ($event->size < 100) {
            return 2;
        }
        
        if ($event->size < 1000) {
            return 20;
        }

        return 200;
    }
}

As before we’ll define a function in our User class to determine which logic to use. The database’s default value can be 1 so all new users automatically have the new pricing and we can set our existing users to 0

public function isUsingNewPricingModel(): bool
{
    return $this->new_pricing_model == 1;
}

We’ll also create a function that will load the appropriate class for the pricing.

public function getPricingModel(): PricingModel
{
    if ($this->isUsingNewPricingModel()) {
        return new UpdatedPricingModel();
    }

    return new OriginalPricingModel();
}

Finally in our logic where we calculate the price we can now use the getPricingModel() function.

$cost = $user->getPricingModel()->getPrice($event);

It was a lot of work getting to this point and it may seem like it’s too much but we’ve spent more time up front to make sure the pricing logic is simple.

The bonus is that cleanup is easy. We just need to change getPricingModel() so it only returns the UpdatePricingModel to have everyone be on the new model.

public function getPricingModel(): PricingModel
{
    return new UpdatedPricingModel();
}

Then we can delete the OriginalPricingModel class. We’ll have to decide if we get rid of the getPricingModel function. It’s going to depend on how likely we feel it is that this will change again in the future.

What You Need To Know

  • Feature Toggles allow modification of system behavior with no code changes
  • Examples Include:
    • New Features
    • Changes In Logic
    • Disable Modules for maintenance