A frequently used development workflow in version control systems is feature branching. The idea is that we develop new features in branches other than the master one. After a feature is tested and ready to be released, it is merged back into the master branch or a release branch for deployment. This approach helps us develop new features without disturbing the main code base.
However, developing a feature branch might take much longer than a normal release cycle. This will make merging the branch more difficult since we would have to deal with possible merge, logic, or dependency conflicts.
Key Takeaways
- Feature toggling allows developers to integrate new features into the master branch without exposing them to end users, avoiding the need for feature branches and reducing merge conflicts.
- Qandidate’s Toggle is a PHP library that enables the creation and management of feature toggles, allowing features to be activated or deactivated based on predefined conditions.
- Toggles can be categorized into release toggles, which hide unfinished features, and business toggles, which restrict features to specific user groups, enhancing flexibility in feature deployment.
- The Toggle library supports various operators for conditions, such as GreaterThan, LessThan, and Percentage, making it versatile for different toggling scenarios.
- Feature toggles can be integrated into web frameworks like Laravel, where they can be used to control access to UI components and URLs, ensuring that only eligible users can access certain features.
- Proper management of feature toggles is crucial; they should be removed once they are no longer needed to prevent codebase clutter and potential bugs in production.
Feature Toggling
One of the techniques widely used as an alternative to feature branching is feature toggling. Feature toggles (or feature flippers) act like on/off switches. They let us continue development on the master branch while not exposing the partially developed and risky features to the public. We can make sure our features remain fully compatible with the existing functionality in our application.
We can temporarily hide a partially built or risky feature (release toggles) or limit finished stable features to a certain group of users (business toggles). These two types of toggles are implemented in the same way but for different purposes. With release toggles, we hide our unfinished features from users except for the development and QA team. These toggles retire when the feature becomes stable. They are usually managed at the code level.
By using business toggles, we can make a feature available to a certain group of users, or we can completely disable it due to some conditions – think webshop sale, Xmas theme on your site, etc. They often require an interface like a dashboard to view the status of the existing toggles with the ability to switch them off and on.
Feature toggling is used by many large websites including Flickr, Facebook, Disqus, Etsy, Reddit, Gmail and Netflix.
Martin Fowler has a good write up on feature toggling, covering the pros and cons and how to use them the right way.
In this tutorial, we’re going to lean how to create feature toggles using Toggle, a PHP library developed by Quandidate labs.
Qandidate Toggle
The basic idea is to activate and deactivate a feature based on some conditions at runtime. As an example, we could activate a feature for the users who registered their accounts in our system a week ago.
Toggle is composed of several components.
Toggle Manager
Toggle manager, as the name implies, is responsible for managing feature toggles including storing toggles, retrieving toggles, checking toggles’ statuses, etc.
The toggle manager can store the feature toggles in two different ways. One method is an in-memory collection, which is a class wrapping an array of toggles. This class exposes some methods for storing and retrieving the toggles inside the collection. The second storage approach uses Redis to store and retrieve the toggles.
The storage class is injected as a dependency into the toggle manager, at the time it is instantiated. In this tutorial, we will use the in-memory collection to store the toggles.
<?php
// ...
$manager = new ToggleManager(new InMemoryCollection());
Toggles
In Toggle, every feature toggle is an object with a name and a group of conditions. We evaluate runtime values against these conditions, to decide whether the feature toggle should be enabled or disabled.
<?php
// ...
$conditions = [$condition1, $condition2];
$toggle = new Toggle('newFeature', $conditions);
Operators
Operators are the building blocks of conditions of a toggle. If we compare them with the operators in programming languages, they’re like the logical expressions in if
statements. The following operators are supported by Toggle out of the box:
- GreaterThan
- GreaterThanEqual
- LessThan
- LessThanEqual
- InSet
- Percentage
To create an operator:
<?php
// ...
$operator = new LessThan(1000)
In the preceding code, the operator applies to values less than 1000
. Operators don’t do anything on their own. They are meant to be used with condition objects.
The idea may have become a little confusing by now, but it will be all clear when we put it all together in the following sections.
Conditions
As noted earlier, each toggle must meet one or more conditions, to be activated. What is a condition in Toggle?
A condition is an object which takes an arbitrary key and an operator object:
<?php
// ...
$operator = new LessThan(100);
$conditions = [];
// This condition is met when user_id is less or equal than 100
$conditions[] = new OperatorCondition('user_id', $operator);
$toggle = new Toggle('newFeature', $conditions);
Context
Operators, conditions and toggle objects create instructions for activating a feature toggle at runtime. So basically nothing happens until we check some values against the conditions.
Context
is an object that allows us to assign values to toggle conditions. These values can be anything from ID numbers to any value based on our use case. We add values to a Context
object using the set()
method:
<?php
// ...
$operator = new LessThan(100);
$conditions = [];
$conditions[] = new OperatorCondition('user_id', $operator);
$toggle = new Toggle('newFeature', $conditions);
$context = new Context();
$context->set('user_id', 67);
user_id
refers to our condition’s key in the above example.
Now that we have the conditions and runtime values, we can check the toggle’s status using the manager’s active()
method:
<?php
$manager = new ToggleManager(new InMemoryCollection());
$operator = new LessThan(100);
$conditions = [];
$conditions[] = new OperatorCondition('user_id', $operator);
$toggle = new Toggle('newFeature', $conditions);
$context = new Context();
$context->set('user_id', 100);
$manager->add($toggle);
if ($manager->active('newFeature', $context)) {
echo 'The newFeature toggle is enabled';
}
active
accepts two parameters. The first parameter is the name of the toggle for which we want to check the status. The second parameter is the context object. active()
checks if such a toggle exists inside the collection. Consequently, it evaluates the context values against the respective conditions and returns a boolean value.
Please do notice that we need to add the toggle to the manager using the add()
method before the evaluation.
Instead of using the manager’s active()
method, we can directly use activeFor()
of the toggle object itself:
<?php
// ...
// Conditions here
$toggle = new Toggle('newFeature', $conditions);
if ($toggle->activeFor($context)) {
echo 'The toggle is active';
// Do something special here.
}
// ...
activeFor()
takes a Context
object as the argument.
Toggle in Action
We’ll use composer to install Toggle and its dependencies. To do this, first we create a directory in our web root directory and run the following command inside it:
composer require qandidate/toggle
Now, let’s create a file and name it ToggleConfig.php
. We’ll keep all the toggle definitions inside this file. Finally, we will evaluate the toggle’s status and return it as an array.
For this example, let’s create a feature toggle which is enabled before 8 PM:
<?php
//ToggleConfig.php
use Qandidate\Toggle\Context;
use Qandidate\Toggle\Operator\LessThan;
use Qandidate\Toggle\OperatorCondition;
use Qandidate\Toggle\Toggle;
use Qandidate\Toggle\ToggleCollection\InMemoryCollection;
use Qandidate\Toggle\ToggleManager;
$manager = new ToggleManager(new InMemoryCollection());
//-------------------------------------------------
// Toggle for featureOne
//-------------------------------------------------
$operator = new LessThan(20); // < 20
$conditions = [];
$conditions[] = new OperatorCondition('time', $operator); // time < 20
$toggle = new Toggle('featureOne', $conditions);
// Adding the toggle to the collection
$manager->add($toggle);
$context = new Context();
$context->set('time', (int) date('G'));
//----------------------------------------------------------
// Return the status array
return array(
'featureOne' => $manager->active('featureOne', $context),
);
In the preceding code, first we imported all the necessary classes into the script. We instantiated ToggleManager
and injected InMemoryCollection
as the storage media. Then we added a toggle with one condition. The condition is met when the current hour is less than 20 (8 PM).
Finally, we evaluated the toggle status and returned it as an array.
To use the feature toggle, let’s create another file named index.php
:
<?php
require_once 'vendor/autoload.php';
$toggles = require 'ToggleConfig.php';
if ($toggles['featureOne']) {
echo 'The toggle is active';
// Do something special here.
}
We assigned the output of ToggleConfig.php
(which is an array of toggle’ statuses) to the $toggles
variable.
Using Toggle with a Framework
When we’re deploying a partially developed feature into the wild, we have to hide access to it in UI components along with all the entry points leading to the functionality of said feature.
As an example, we’re going to use the toggling feature in a Laravel project to protect some UI components and a group of URLs. However, the idea applies to other frameworks as well.
Start up a Laravel skeleton project, then proceed.
First, let’s install Toggle. In our Laravel project’s root directory, we run:
composer require qandidate/toggle
Creating the Feature Toggles
The feature toggles can be defined anywhere as long as they are loaded when our application is bootstrapped. A good approach is to define all these in a middleware class.
To create a middleware, while in the terminal, we change directory to our Laravel installation directory and run the following command:
php artisan make:middleware TogglesMiddleware
As a result, a middleware class named ToggleMiddleware.php
will be created within our app/Http/Middleware
directory. We need to define all the toggles inside the handle()
method of this class.
For this example, let’s create a feature toggle to give access to a certain group of users. For example, users with the ID number under 100 – the “early adopters”.
To use the toggles’ statuses across our application, we will use Laravel’s Config
service.
<?php
namespace App\Http\Middleware;
use Closure;
use Config;
use Qandidate\Toggle\Context;
use Qandidate\Toggle\Operator\LessThan;
use Qandidate\Toggle\OperatorCondition;
use Qandidate\Toggle\Toggle;
use Qandidate\Toggle\ToggleCollection\InMemoryCollection;
use Qandidate\ToggleManager;
class TogglesMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
/*
* Toggle for NewFeature
*/
$manager = new ToggleManager(new InMemoryCollection())
$operator = new LessThan(100); // < 100
$condition = new OperatorCondition('uid', $operator); // uid < 100
$toggle = new Toggle('newFeature', [$condition]);
$context = new Context();
// Here we use the User model to get user information
$context->set('uid', User::get()->getId());
// Storing the statuses in the Config service
Config::set('toggles.newFeature', $manager->active($toggle, $context));
return $next($request);
}
}
In the next step, we need to register the middleware. Since we want to run the middleware during each HTTP request to our application, we register it as a global middleware. To do this, we add our middleware’s class name to the $middleware
property of our app/Http/Kernel.php
class:
<?php
/**
* The application's global HTTP middleware stack.
*
* @var array
*/
protected $middleware = [
//...
'App\Http\Middleware\TogglesMiddleware',
];
Hiding UI Components
To hide UI components related to our feature, we have two options. We can either access the Config
service and check the toggle’s status right from the views, or we can do all the logic in our controller and then pass a flag to the view. The latter approach seems to be more appropriate according to best MVC practices.
So inside our controller:
<?php
// ...
// Our business logic here.
$showFeature = \Config::get('toggles.newFeature');
View::render('users.dashboard', array(
'showNewFeature' => $showFeature,
));
// ...
And inside the view:
<div class="row">
<div class="col-md-3">Feature one</div>
<div class="col-md-3">Feature one</div>
@if ($showFeature)
<div class="col-md-3">Feature one</div>
@endif
</div>
In the preceding code, we used blade’s @if
statement to check if the flag is true
or false
. As a result, the last <div>
element will be rendered only if showFeature
is set to true
.
Protecting the URLs
If our new feature contains an API or any controller that we should hide from the users, we need to protect them as well. To do this, we create a before middleware, but this time instead of registering it as a global middleware, we assign it to a certain group of routes. Inside the middleware’s handle method, we check the toggle’s status, and if it is disabled, we throw a 404 error.
Another approach is that we put a logic barrier at the beginning of the controller classes and throw a 404
error if the toggle is off. However, using this approach, the request is dispatched to our controller even though the toggle is off.
For this example, we go with the first approach. Let’s start off with creating the middleware:
php artisan make:middleware APIToggleMiddleware
As a result, an APIToggleMiddleware.php
file is created within the app/Http/Middleware
directory. Since global middlewares are run before the route-specific ones, we’re going to use the toggle that we created in our global middleware. The reason why we didn’t define the toggle inside a route-specific middleware in the first place is that we might need those toggles in all controllers.
<?php
namespace App\Http\Middleware;
use Config;
use Closure;
class APIToggleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (Config::get('toggles.newFeature')) {
abort(404, 'Page Not Found!');
}
return $next($request);
}
}
In the preceding code, we used the Config
service to retrieve the status for the newFeature
toggle. If it is false, we simply throw a 404
error.
To register this middleware, we add the middleware’s class name to the $routeMiddleware
property inside app/Http/Kernel.php
. $routeMiddleware
is an associative array storing all the route specific middlewares, so, we’ll need to specify a key for our middleware as well. In our case: api-toggle
:
<?php
/**
* The application's route middleware.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'api-toggle' => '\App\Http\MiddleWare\APIToggleMiddleware',
];
Now, we can use the middleware inside our app/http/routes.php
file as a before
middleware:
<?php
Route::group('new-feature/api', ['middleware' => 'api-toggle'], function(){
Route::get('new-feature/', function(){...});
Route::get('new-feature/create', function(){...});
Route::put('new-feature/{id}', function(){...});
});
As a result, all the routes under new-feature/api
will be only available to users with ID numbers below 100.
Toggling Strategies
By default, a feature toggle is active when at least one of the conditions are met, but this can be adjusted using the toggling strategies that Toggle provides out of the box.
To do this, we need to specify the desired strategy when creating a toggle object:
<?php
// ...
$toggle = new Toggle('newFeature', $conditions, Toggle::STRATEGY_AFFIRMATIVE);
// ...
Affirmative
This is the default strategy that Toggle uses for checking toggle conditions. In this case, at least one condition must be met for a toggle to be active.
<?php
// ...
$manager = new ToggleManager(new InMemoryCollection());
$conditions = [];
$conditions[] = new OperatorCondition('user_id', new LessThan(100));
$conditions[] = new OperatorCondition('age', new GreaterThan(21));
$toggle = new Toggle('newFeature', $conditions, Toggle::STRATEGY_AFFIRMATIVE);
$manager->add($toggle);
$context = new Context();
$context->set('user_id', 100)
->set('age', 20);
if ($toggle->activeFor($context)) {
echo 'The toggle is active';
// Do something here
}
The above toggle will be enabled because one of the conditions (user_id
) is met.
Majority
In the Majority strategy, the majority of conditions must be met. For example, if we have three conditions in a toggle, at least two of them must be met.
<?php
// ...
$manager = new ToggleManager(new InMemoryCollection());
$conditions = [];
$conditions[] = new OperatorCondition('user_id', new LessThan(42));
$conditions[] = new OperatorCondition('age', new GreaterThan(24));
$conditions[] = new OperatorCondition('height', new GreaterThan(5.7));
$toggle = new Toggle('newFeature', $conditions, Toggle::STRATEGY_MAJORITY);
$manager->add($toggle);
$context = new Context();
$context->set('user_id', 41);
->set('age', 25);
->set('height', 5.6);
if ($toggle->activeFor($context)) {
echo 'The toggle is active.'
// Do something special here
}
In the preceding code, the toggle will be enabled, because two (user_id
and age
) of the three conditions are met.
Unanimous
In a toggle with a Unanimous strategy, all conditions must be met:
<?php
// ...
$manager = new ToggleManager(new InMemoryCollection());
$conditions = [];
$conditions[] = new OperatorCondition('user_id', new LessThan(42));
$conditions[] = new OperatorCondition('age', new GreaterThan(24));
$conditions[] = new OperatorCondition('height', new GreaterThan(5.7));
$toggle = new Toggle('newFeature', $conditions, Toggle::STRATEGY_MAJORITY);
$manager->add($toggle);
$context = new Context();
$context->set('user_id', 41);
->set('age', 25);
->set('height', 5.6);
if ($toggle->activeFor($context)) {
echo 'The toggle is active.'
// Do something special here
}
In the above example, the toggle will be disabled because the third condition (height
) is not met based on the context’s value.
Toggle Statuses
Each feature toggle can have three states: active, inactive and conditionally active. When a toggle is manually set to active, it will be always active regardless of the conditions. If a toggle is set as inactive, it will always be inactive. The third state is conditionally active, where the state of the toggle will be dependent on the conditions. This is the default state.
To change the state of a toggle manually, we use the activate()
method of the toggle object. This method takes a parameter which is the state of the toggle and can be one of the following constants:
Toggle::CONDITIONALLY_ACTIVE
(default state)Toggle::ACTIVE
Toggle::INACTIVE
<?php
// Conditions here
$toggle->activate(Toggle::CONDITIONALLY_ACTIVE);
$manager->add($toggle);
$context = new Context();
$context->set('user_id', 41);
->set('age', 25);
->set('height', 5.6);
if ($toggle->activeFor($context)) {
echo 'The toggle is active.'
// Do something special here
}
Using Arrays or YAML to Create the Toggles
So far, we have been instantiating different objects (operators, conditions, and toggles) to create feature toggles. We can also create feature toggles using arrays and YAML files, if we prefer configuration over code. In this approach, we define all the conditions and operators as a multidimensional associative array or as YAML objects. Consequently, we use the InMemoryCollectionSerializer
service (which is part of the Toggle library) to create all the necessary operators, conditions and toggles automatically.
To define toggles using an array:
<?php
// ...
// Array value
$data = [
'some-feature' => [
'name' => 'newFeatureToggle',
'conditions' => [
[
'name' => 'operator-condition',
'key' => 'user_id',
'operator' => ['name' => 'greater-than', 'value' => 41],
],
],
'status' => 'conditionally-active',
],
];
As we can see, we have an array of toggles. Inside each array, we have two elements: name
and condition
. name
is the toggle’s name and the condition is an array of conditions. In the conditions
sub-array, we need to specify the condition’s class (OperatorCondition) but in kebab-case format: operator-condition
. The same rule applies for other class names that we use in the configuration array. We also need to define the condition key (in this case user_id
) and the operator.
To create the toggles, we deserialize the above array using InMemoryCollectionSerializer
like so:
<?php
use Qandidate\Toggle\Context;
use Qandidate\Toggle\ToggleManager;
use Qandidate\Toggle\Serializer\InMemoryCollectionSerializer;
// $data array (
// ...
// );
$serializer = new InMemoryCollectionSerializer();
$collection = $serializer->deserialize($data);
$manager = new ToggleManager($collection);
$context = new Context();
$context->set('user_id', 42);
if ($manager->active('some-feature', $context)) {
echo 'The toggle is Active';
// Do something special here
}
Yaml mode is almost identical, but requires the Symfony/yaml package. For more information, refer to the examples.
Wrapping up
Feature toggles let us develop directly on the master branch without having to deal with merge conflicts. They also help us conceal unfinished and risky features from end users, and we can always roll back in case something goes wrong with the code. However, if we don’t use them cautiously, they can become a nightmare in the long term.
We need to make sure that we really need a feature toggle. If we use feature toggles whenever we can, even though they are not needed, at the end of the day we’ll have a pile of barely traceable toggles scattered throughout our code base. This will increase the chances of us exposing buggy features to the public without even noticing it.
While feature toggling is fantastic for one-off easily deletable switches like Xmas themes, special promotions, temporary profile features etc, they should be considered a second option when developing complicated features. Instead, we should see if we can develop and deploy our upgrades in small commits in each release cycle.
Likewise, we should always remove the feature toggles as soon as they are no longer needed. This includes removing the middleware classes and configuration files in which we define them.
That said, Qandidate Toggle is a PHP library that makes managing feature toggling much easier than before. We hope you enjoyed this tutorial, in which we started with a basic example and expanded it into a Laravel environment.
How do you do feature toggling? Let us know! And if you have any questions or comments, please leave them below!
Frequently Asked Questions (FAQs) about Feature Toggling
What is the main purpose of feature toggling in software development?
Feature toggling, also known as feature flagging, is a technique in software development that allows teams to modify the system’s behavior without changing the code. It provides a way to hide, enable, or disable certain features during runtime. This is particularly useful in continuous integration and continuous delivery (CI/CD) environments where new features can be tested in production without being visible to end-users. It also allows for A/B testing, canary releases, and gradual rollouts, thus reducing the risk of introducing new features.
How does feature toggling differ from traditional software testing methods?
Traditional software testing methods often involve creating separate branches in the version control system for new features, which are then merged into the main codebase once the feature is fully developed and tested. This can lead to “merge hell” when multiple branches need to be merged at once. Feature toggling, on the other hand, allows developers to work on a single, shared codebase where new features can be hidden behind toggles until they’re ready for release. This reduces the complexity and risk associated with merging code.
What are the different types of feature toggles?
There are several types of feature toggles, each serving a different purpose. Release toggles allow incomplete and un-tested codepaths to be shipped to production as latent code which can be turned on later. Experiment toggles, also known as A/B testing toggles, are used to perform controlled experiments on the user population. Ops toggles are used to control operational aspects of the system’s behavior. Permissioning toggles can change the features or product experience that certain users receive.
How can feature toggles be implemented in code?
Feature toggles can be implemented in code using various methods. One common approach is to use configuration files or environment variables to control the state of the toggles. These can be checked in the code to determine whether a feature should be enabled or disabled. Another approach is to use a feature toggle service or library, which provides a more robust and flexible way to manage toggles.
What are the potential risks or drawbacks of feature toggling?
While feature toggling offers many benefits, it also comes with potential risks. If not managed properly, feature toggles can lead to code complexity and technical debt. For example, old toggles that are no longer needed must be removed from the codebase to prevent bloat and confusion. Additionally, since toggles introduce different paths through the code, they can make testing more complex.
How can feature toggles support A/B testing?
Feature toggles can be used to implement A/B testing by enabling a new feature for a subset of users and comparing their behavior with users who don’t have access to the new feature. This allows teams to gather data on how the new feature affects user behavior and make data-driven decisions about whether to roll out the feature to all users.
Can feature toggles be used in conjunction with microservices?
Yes, feature toggles can be used in a microservices architecture. They can be particularly useful in this context for coordinating the release of changes that span multiple services. By hiding new functionality behind a toggle, teams can deploy changes to different services independently without exposing incomplete features to users.
How does feature toggling contribute to continuous integration and continuous delivery (CI/CD)?
Feature toggling is a key enabler of CI/CD. It allows teams to integrate and deploy their changes more frequently and reliably by reducing the risk associated with each deployment. By hiding new features behind toggles, teams can deploy code to production even if it’s not fully complete or tested, and then turn on the features when they’re ready.
What tools are available for managing feature toggles?
There are many tools available for managing feature toggles, ranging from simple configuration files or environment variables to dedicated feature toggle services. Some popular feature toggle services include LaunchDarkly, Split, and Togglz. These services provide features like toggle management, user segmentation, and analytics.
How can feature toggles be used for canary releases?
Canary releases are a pattern where new features are rolled out to a small subset of users before being made available to everyone. Feature toggles can be used to implement this pattern by enabling the feature for a small percentage of users. This allows teams to test the new feature in production with a small amount of risk, and then gradually increase the percentage of users with access to the feature as confidence in its stability grows.
A web developer with a solid background in front-end and back-end development, which is what he's been doing for over ten years. He follows two major principles in his everyday work: beauty and simplicity. He believes everyone should learn something new every day.