Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Feature Flag Bundle Laravel Package

em411/feature-flag-bundle

A Symfony bundle for feature flags: enable or disable code paths based on context-aware callables. This is a Symfony 4.4 / PHP 7.4 backport of ajgarlag/feature-flag-bundle, keeping the same API and namespace for a drop-in upgrade from the upstream package.

View on GitHub
Deep Wiki
Context7

FeatureFlag Bundle

CI Latest Stable Version License

The FeatureFlag Bundle allows you to split the code execution flow by enabling features depending on context.

It provides a service that checks if a feature is enabled. Each feature is defined by a callable function that returns a value. The feature is enabled if the value matches the expected one (mostly a boolean but not limited to).

This bundle code has been borrowed from https://github.com/symfony/symfony/pull/53213.

Symfony 4.4 / PHP 7.4 backport. This is a fork of ajgarlag/feature-flag-bundle refactored to run on Symfony 4.4 and PHP 7.4.

Which package should I use?

  • Running Symfony 6+ / PHP 8.1+? Use the upstream ajgarlag/feature-flag-bundle — it targets modern Symfony and uses native PHP 8 attributes.
  • Stuck on Symfony 4.4 / PHP 7.4? Use this fork. It exists because the upstream cannot run on legacy stacks. The public API is intentionally kept compatible with the upstream so migration is a namespace-free drop-in.

Note on the namespace. The Composer package is em411/feature-flag-bundle but the PHP namespace remains Ajgarlag\FeatureFlagBundle\ on purpose, so code written against the upstream works here unchanged. Imports and the bundle class reference (Ajgarlag\FeatureFlagBundle\FeatureFlagBundle::class) stay the same.

🚀 Getting Started

Follow these steps to install and use the bundle in your Symfony application.

Step 1: Download the bundle

Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:

composer require em411/feature-flag-bundle

Step 2: Enable the bundle (for non-Flex applications)

Then, enable the bundle by adding it to the list of registered bundles in the config/bundles.php file of your project:

// config/bundles.php

return [
    // ...
    Ajgarlag\FeatureFlagBundle\FeatureFlagBundle::class => ['all' => true],
];

✨ Declaring features with annotations

You can declare features using the @AsFeature annotation. This allows you to autoconfigure your features as services.

On a class

You can use the annotation on an invokable class:

namespace App\Feature;

use Ajgarlag\FeatureFlagBundle\Attribute\AsFeature;

/**
 * @AsFeature("xmas")
 */
final class XmasFeature
{
    public function __invoke(): bool
    {
        return date('m-d') === '12-25';
    }
}

The feature will be named xmas. If you don't provide a name, the FQCN of the class will be used.

You can also use the method property of the annotation to specify a method to call on the service.

namespace App\Feature;

use Ajgarlag\FeatureFlagBundle\Attribute\AsFeature;

/**
 * @AsFeature(name="xmas", method="isXmas")
 */
final class XmasFeature
{
    public function isXmas(): bool
    {
        return date('m-d') === '12-25';
    }
}

On a method

You can also use the annotation on a method of a service. The method must be public.

namespace App\Feature;

use Ajgarlag\FeatureFlagBundle\Attribute\AsFeature;

final class FeatureService
{
    /**
     * @AsFeature(name="weekend")
     */
    public function isWeekend(): bool
    {
        return date('N') >= 6;
    }

    /**
     * @AsFeature
     */
    // The feature will be named "App\Feature\FeatureService::isAnotherFeature"
    public function isAnotherFeature(): bool
    {
        return true;
    }
}

Note: Unlike PHP 8 attributes, a Doctrine annotation cannot be repeated on the same class or method. To declare multiple features on one class, use the ajgarlag.feature_flag.feature service tag.

Gating routes by a feature

Symfony 4.4 cannot evaluate services inside route condition expressions, so route-level feature gating is done in the controller (or a kernel.controller listener). Inject FeatureCheckerInterface and throw a 404 when the feature is off:

use Ajgarlag\FeatureFlagBundle\FeatureCheckerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class CheckoutController
{
    private $featureChecker;

    public function __construct(FeatureCheckerInterface $featureChecker)
    {
        $this->featureChecker = $featureChecker;
    }

    public function index(): Response
    {
        if (!$this->featureChecker->isEnabled('new_checkout')) {
            throw new NotFoundHttpException();
        }
        // ...
    }
}

🧩 Providers

Providers are responsible for returning the feature callables. You can create your own provider by implementing the ProviderInterface.

Any service that implements ProviderInterface is automatically registered as a provider. The bundle comes with a ChainProvider that allows you to combine multiple providers. The first provider that returns a feature wins.

The ProviderInterface

To create your own provider, you need to implement the ProviderInterface.

namespace App\FeatureProvider;

use Ajgarlag\FeatureFlagBundle\Provider\ProviderInterface;

class MyProvider implements ProviderInterface
{
    public function get(string $featureName): ?callable
    {
        // ...
    }
}

The get method must return a callable if the provider has the feature, or null otherwise.

namespace App\FeatureProvider;

use Ajgarlag\FeatureFlagBundle\Provider\ProviderInterface;
use App\Repository\FeatureAssignmentRepository;

final class DoctrineProvider implements ProviderInterface
{
    private $featureAssignmentRepository;

    public function __construct(FeatureAssignmentRepository $featureAssignmentRepository)
    {
        $this->featureAssignmentRepository = $featureAssignmentRepository;
    }

    public function get(string $featureName): ?callable
    {
        // Set context. Example: user identifier, IP, hostname, etc.
        $context = [];

        return function () use ($featureName, $context) {
            return $this->featureAssignmentRepository->featureIsEnabled($featureName, $context);
        };
    }
}

First, declare a service to interact with the Unleash API.

# config/services.yaml
services:
    # A PSR-16 cache wrapping the "cache.unleash" pool, recommended to limit API calls.
    gitlab.unleash_cache:
        class: Symfony\Component\Cache\Psr16Cache
        arguments: ['@cache.unleash']

    gitlab.client_factory:
        class: Unleash\Client\UnleashBuilder
        factory: ['Unleash\Client\UnleashBuilder', 'createForGitlab']
        calls:
            - { method: withGitlabEnvironment, arguments: ['%env(GITLAB_ENVIRONMENT)%'], returns_clone: true }
            - { method: withAppUrl, arguments: ['%env(GITLAB_URL)%'], returns_clone: true }
            - { method: withInstanceId, arguments: ['%env(GITLAB_INSTANCE_ID)%'], returns_clone: true }
            - { method: withHttpClient, arguments: ['@psr18.http_client'], returns_clone: true }
            - { method: withCacheHandler, arguments: ['@gitlab.unleash_cache'], returns_clone: true }

    gitlab.client:
        class: Unleash\Client\Unleash
        factory: ['@gitlab.client_factory', 'build']

    App\FeatureProvider\GitlabProvider:
        arguments:
            $unleash: '@gitlab.client'
namespace App\FeatureProvider;

use Ajgarlag\FeatureFlagBundle\Provider\ProviderInterface;
use Symfony\Component\Security\Core\Security;
use Unleash\Client\Configuration\UnleashContext;
use Unleash\Client\Unleash;

class GitlabProvider implements ProviderInterface
{
    private $unleash;
    private $security;

    public function __construct(Unleash $unleash, Security $security)
    {
        $this->unleash = $unleash;
        $this->security = $security;
    }

    public function get(string $featureName): ?callable
    {
        $user = $this->security->getUser();

        // Set context. Example: user identifier, IP, hostname, etc.
        $context = new UnleashContext($user ? $user->getUsername() : null);

        return function () use ($featureName, $context) {
            return $this->unleash->isEnabled($featureName, $context);
        };
    }
}

Priority

You can control the order of the providers in the chain using the priority attribute of the ajgarlag.feature_flag.provider tag.

Set the priority on the service tag in your configuration:

# config/services.yaml
services:
    App\FeatureProvider\MyProvider:
        tags:
            - { name: 'ajgarlag.feature_flag.provider', priority: 10 }

Providers with a higher priority will be checked first.

🎨 Twig extension

The bundle provides two functions to use in Twig templates: feature_is_enabled and feature_get_value.

{% if feature_is_enabled('some_feature') %}
    {# ... #}
{% endif %}

{% if feature_get_value('some_feature') == 'some_value' %}
    {# ... #}
{% endif %}
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource