This package is abandoned and no longer maintained. No replacement package was suggested.

Feature Control System

1.4.2 2022-12-16 00:01 UTC

This package is auto-updated.

Last update: 2022-12-16 00:13:52 UTC


README

What is it and why do I need it?

FCS (Feature Control System) is a simple library for feature-toggling and can be used to define list of features with simple and complex conditions and allowing to define environment settings and tags to resolve the status of each feature.

For example you can define features for specific users, specific active cookies, development environments, tags or even all of them combined in some logic pattern.

All your features can be managed from one place and easily updated to wider audience, rolled back to previous environment or completely removed.

FCS can be especially useful in Continues Integration workflow, where multiple changes are merged in short time to the mainline and not all of them are ready to be deployed to production.

It can be also used to limit some feature to very specific conditions, e.g. due to market regulations or conditions of some promotions.

It can be also useful for A/B testing and when enabling new features only for some segment of users to confirm new code working fine in real environment without risking issues for all the users.

How to configure list of features?

In case of small projects, you can simply keep configuration in array in some PHP or JSON file.

In case of bigger projects, it's better to load it from redis or other cache provider, to distribute the list of features and their flags among different code repositories.

In case of redis integration, it's best to prepare some simple deployment script in backoffice to easily update flags for every feature, ideally with basic user interface. This way it can be used in emergency by anyone in your team with permissions, including PM or QA or even marketing team on handling particular temporary promotions (offering access to only some flags for different users).

FCS

Configuration methods:

Example of code to configure some settings:

    use \FeatureControlSystem\FCS;

    FCS::Instance()->setTag('env', 'preproduction');
    FCS::Instance()->setTag('username', 'john123');
    FCS::Instance()->setTag('lang', 'en');
    
    // set flags manually
    
    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_ACTIVE],
        'my-feature2' => ['flag' => FCS::FLAG_TAG, 'name' => 'username', 'value' => 'john123']
    ]);
    
    // or load flags from redis cache
    
    $redis = new \Redis();
    // ...
    FCS::Instance()->loadFlags($redis, 'flags');
  • setTag

Set a tag with a name and value

    FCS::Instance()->setTag(string $name, string $value);
    
    // e.g. FCS::Instance()->setTag('lang', 'en')
    
  • setFlags

Register a list of features with their flags, that can be used to determine if feature is active or not

    FCS::Instance()->setFlags(array $flags);
    
    // e.g. FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_ACTIVE],
        'my-feature2' => ['flag' => FCS::FLAG_TAG, 'name' => 'username', 'value' => 'john123'],
        'my-feature3' => ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => ['pl', 'de']]
    ])
  • loadFlags

Register a list of features with their flags loaded from redis cache; data in redis needs to be encoded with JSON and stored under simple key

First argument can be either instance of Predis client or Redis PHP extension

    FCS::Instance()->loadFlags(object $redis, string $key);
    
    // e.g. FCS::Instance()->loadFlags($redis, 'flags')

Getter methods:

  • feature

Check if feature is active and use returned boolean to enable some code

    if (<bool> = FCS::Instance()->feature(string $name)) {
        // code is enabled
    }
    
    // e.g. FCS::Instance()->feature('my-feature') === true
  • getBodyCssClasses

Get string with list of CSS classes that can be appended to body element in your frontend

Returned CSS classes have "fcs-" prefix

    <string> = FCS::Instance()->getBodyCssClasses();
    
    // e.g. FCS::Instance()->getBodyCssClasses() === 'fcs-my-feature fcs-my-feature2 fcs-my-feature100'
  • getActiveFeatures

Get array of names of features that can be for example injected to JS to enable some dynamic code on frontend

    <array:string> = FCS::Instance()->getActiveFeatures();
    
    // e.g. FCS::Instance()->getActiveFeatures() === ['my-feature', 'my-feature2', 'my-feature100']

Available flags:

    use \FeatureControlSystem\FCS;

    FCS::FLAG_INACTIVE = 0
    FCS::FLAG_ACTIVE = 1
    FCS::FLAG_COOKIE = 2
    FCS::FLAG_TAG = 3
    FCS::FLAG_MULTI_AND = 4
    FCS::FLAG_MULTI_OR = 5
  • FLAG_INACTIVE

Inactive flag means that feature is disabled for everyone

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_INACTIVE] // FCS::FLAG_INACTIVE = 0
    ]);

    if (FCS::Instance()->feature('my-feature')) { // false
        // no one can see it
    }
    
  • FLAG_ACTIVE

Active flag means that feature is enabled for everyone

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_ACTIVE] // FCS::FLAG_ACTIVE = 1
    ]);

    if (FCS::Instance()->feature('my-feature')) { // true
        // always working
    }
    
  • FLAG_COOKIE

Cookie flag means that feature is enabled for everyone who make request to the server with active cookie matching feature by its name

Cookies need to have "fcs-" prefix

Note: slash (/) in name of feature is automatically turned into double dash (--)

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_COOKIE] // FCS::FLAG_COOKIE = 2
    ]);
    
    $_COOKIE['fcs-my-feature'] = true;

    if (FCS::Instance()->feature('my-feature')) { // true, if fcs-my-feature cookie exists
        // working for anyone with enabled cookie "fcs-my-feature" in this case
    }
    
  • FLAG_TAG

Tag flag means that feature is enabled for a tag defined by name and for single value (string) or for list of values (array:string)

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => 'en'], // FCS::FLAG_TAG = 3
        'my-feature2' => ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => ['pl', 'de']] // FCS::FLAG_TAG = 3
    ]);
    
    FCS::Instance()->setTag('lang', 'en');

    if (FCS::Instance()->feature('my-feature')) { // true
        // working for "lang" tag if its value is "en"
    }

    if (FCS::Instance()->feature('my-feature2')) { // false
        // working for "lang" tag if its value is "pl" or "de"
    }
    
  • FLAG_MULTI_AND

Multi-and flag allows to define group of multiple flags and means that feature is enabled if all of the subflags meet their conditions

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_MULTI_AND, 'flags' => [ // FCS::FLAG_MULTI_AND = 4
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain.com'], // FCS::FLAG_TAG = 3
            ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => 'en'] // FCS::FLAG_TAG = 3
        ]],
        'my-feature2' => ['flag' => FCS::FLAG_MULTI_AND, 'flags' => [ // FCS::FLAG_MULTI_AND = 4
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain.com'], // FCS::FLAG_TAG = 3
            ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => 'pl'] // FCS::FLAG_TAG = 3
        ]]
    ]);
    
    FCS::Instance()->setTag('domain', 'domain.com');
    FCS::Instance()->setTag('lang', 'en');

    if (FCS::Instance()->feature('my-feature')) { // true
        // available only if domain tag is equal "domain.com" AND lang tag is "en"
    }

    if (FCS::Instance()->feature('my-feature2')) { // false
        // available only if domain tag is equal "domain.com" AND lang tag is "pl"
    }
    
  • FLAG_MULTI_OR

Multi-or flag allows to define group of multiple flags and means that feature is enabled if any of the subflags meets its condition

    FCS::Instance()->setFlags([
        'my-feature' => ['flag' => FCS::FLAG_MULTI_OR, 'flags' => [ // FCS::FLAG_MULTI_OR = 5
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain.com'], // FCS::FLAG_TAG = 3
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain2.com'] // FCS::FLAG_TAG = 3
        ]],
        'my-feature2' => ['flag' => FCS::FLAG_MULTI_OR, 'flags' => [ // FCS::FLAG_MULTI_OR = 5
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain3.com'], // FCS::FLAG_TAG = 3
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain4.com] // FCS::FLAG_TAG = 3
        ]]
    ]);
    
    FCS::Instance()->setTag('domain', 'domain.com');

    if (FCS::Instance()->feature('my-feature')) { // true
        // available only if domain tag is equal "domain.com" OR "domain2.com"
    }

    if (FCS::Instance()->feature('my-feature2')) { // false
        // available only if domain tag is equal "domain3.com" OR "domain4.com"
    }
    

Examples:

    use \FeatureControlSystem\FCS;

    // Define initial settings for your environment + project + user, usually set just once at the beginning
    FCS::Instance()->setTag('env', 'preproduction');
    FCS::Instance()->setTag('username', 'test123');
    FCS::Instance()->setTag('IP', $_SERVER['REMOTE_ADDR']);
    FCS::Instance()->setTag('role', 'user');
    FCS::Instance()->setTag('country', 'UK');
    FCS::Instance()->setTag('product', 'product1');
    FCS::Instance()->setTag('domain', 'domain.com');
    FCS::Instance()->setTag('lang', 'en');
    
    // Sometimes you can define also extra tags later
    FCS::Instance()->setTag('section', 'promotions');
    FCS::Instance()->setTag('promotion', '1');
    
    // Define list of features from array (loaded from configuration file or cache provider e.g. redis)
    FCS::Instance()->setFlags([
        'experiment' => ['flag' => FCS::FLAG_INACTIVE],
        'blue-background' => ['flag' => FCS::FLAG_TAG, 'name' => 'country', 'value' => ['UK', 'DE']],
        'xmas-promo' => ['flag' => FCS::FLAG_MULTI_AND, 'flags' => [
            ['flag' => FCS::FLAG_TAG, 'name' => 'domain', 'value' => 'domain.com'],
            ['flag' => FCS::FLAG_TAG, 'name' => 'lang', 'value' => 'pl']
        ]],
        'chat' => ['flag' => FCS::FLAG_MULTI_OR, 'flags' => [
            ['flag' => FCS::FLAG_COOKIE],
            ['flag' => FCS::FLAG_TAG, 'name' => 'env', 'value' => ['test123', 'test456']]
        ]],
        'auth' => ['flag' => FCS::FLAG_ACTIVE],
        'new-registration' => ['flag' => FCS::FLAG_TAG, 'name' => 'env', 'value' => 'preproduction'],
        'my-carousel' => ['flag' => FCS::FLAG_COOKIE],
        'new-bonus' => ['flag' => FCS::FLAG_TAG, 'name' => 'env', 'value' => ['test123', 'test456']]
    ]);
    
    // Get list of features to inject to your frontend to JS variable
    $activeFeatures = FCS::Instance()->getActiveFeatures(); // ['blue-background', 'chat', 'auth', 'new-registration', 'new-bonus']
    
    // Get CSS classes to append them to <body> element
    $cssClasses = FCS::Instance()->getBodyCssClasses(); // 'fcs-blue-background fcs-chat fcs-auth fcs-new-registration fcs-new-bonus'
    
    // Activate features in PHP
    if (FCS::Instance()->feature('auth')) { // true
        // active feature
    }
    if (FCS::Instance()->feature('experiment')) { // false
        // inactive feature
    }
    $_COOKIE['fcs-my-carousel'] = 1;
    if (FCS::Instance()->feature('my-carousel')) { // true
        // feature active if $_COOKIE['fcs-my-carousel'] cookie is enabled
    }
    if (FCS::Instance()->feature('new-bonus')) { // true
        // feature active for this user when logged in (test123)
    } else {
        // old feature for everyone else
    }
    if (FCS::Instance()->feature('new-registration')) { // true
        // feature active in preproduction environment
    }
    if (FCS::Instance()->feature('xmas-promo')) { // false
        // feature active if both tags domain AND lang meet the current settings
    }
    if (FCS::Instance()->feature('chat')) { // true
        // feature active if cookie is enabled ($_COOKIE['fcs-chat']) OR if one of listed users is logged in
    }
    

License

MIT License