Open Source · Bundle Symfony

Shizuku Feature Flags.

Feature flag management pour Symfony, backed by Doctrine ORM zéro configuration, intégration Profiler.

PHP ≥ 8.2 Symfony 7.4 · 8.0 Doctrine ORM 3.x MIT
$ composer require devexploris/shizuku-feature-flags
01 — Introduction

Juste Doctrine et du cache

Shizuku stocke tes feature flags en base de données via l'ORM DoctrineM. Pas de service externe, pas de fichier YAML.

Origine
Dans mon entreprise, chaque feature vivait sur sa propre longue branche. Le jour du merge, c'était la guerre — conflits partout, déploiements bloqués, tout le monde attendait tout le monde. Le vrai problème n'était pas le code, c'était le couplage entre livrer et activer. On avais besoin de merger tôt, déployer souvent, et décider quand activer une feature sans dépendre d'un dashboard externe, d'un service tiers, ni d'un fichier YAML à redéployer.

Alors j'ai construit Shizuku. Un flag en base, une commande en console, et rien d'autre.
Zéro config
Cache et Profiler se branchent automatiquement si les packages Symfony sont présents.
Lifecycle complet
Create · Enable · Disable · Lock · Delete avec des garde-fous explicites à chaque étape.
Outillage dev
Profiler panel, fonction Twig feature(), cache TTL, caller-chain tracking.
02 — Requirements

Dépendances

PackageVersionRôle
php≥ 8.2Requis par Symfony 7.4+
symfony/http-kernel^7.4 · ^8.0AbstractBundle, DataCollector
symfony/console^7.4 · ^8.0Commandes CLI
doctrine/orm^3.0Persistance de l'entité
doctrine/doctrine-bundle^2.0 · ^3.0ServiceEntityRepository
twig/twig^3.0Fonction feature()
symfony/cache-contracts^3.0Interface de cache (optionnel)
Optionnel : symfony/cache pour le cache des flags et symfony/web-profiler-bundle pour le panel Profiler — branchés automatiquement si présents.
03 — Installation

Mise en place

01 — Require

$ composer require devexploris/shizuku-feature-flags

02 — Enregistrer le bundle

// config/bundles.php
return [
    Devexploris\ShizukuFeatureFlags\ShizukuFeatureFlagsBundle::class => ['all' => true],
];

03 — Créer la table

Le bundle ne livre pas de migrations. Génère-en une depuis ton application :

$ php bin/console doctrine:migrations:diff
$ php bin/console doctrine:migrations:migrate
04 — Usage

Utilisation

PHP

Injecte FeatureFlagService et appelle isEnabled(). Un flag inconnu retourne toujours false.

use Devexploris\ShizukuFeatureFlags\Service\FeatureFlagService;

class MyService
{
    public function __construct(private FeatureFlagService $flags) {}

    public function doSomething(): void
    {
        if ($this->flags->isEnabled('my_feature')) {
            // do something
        }
    }
}

Twig

Le bundle enregistre la fonction globale feature(). Les flags inconnus retournent silencieusement false — aucune exception.

{% if feature('my_feature') %}
    {# nouveau comportement #}
{% endif %}

{# inline — ex. bascule de classe CSS #}
Flag inconnu ? Si le flag est absent de la base, isEnabled() et feature() retournent false. Le Profiler Symfony le signale dans son panel pour éviter les typos silencieuses.
05 — Commands

Commandes console

commands

Les noms de flags doivent être en snake_case : lettres minuscules, chiffres et underscores, commençant par une lettre.

List

$ php bin/console shizuku:list [filter]

filter est optionnel. All, Enabled, Disabled, Locked. Sans argument, un prompt interactif s'affiche.

Create

$ php bin/console shizuku:flag:create
$ php bin/console shizuku:flag:create --name=my_feature --description="My feature" --enable

Les flags sont créés désactivés par défaut. Passe --enable pour les démarrer actifs.

Enable / Disable

$ php bin/console shizuku:flag:enable --name=my_feature
$ php bin/console shizuku:flag:disable --name=my_feature

Les flags verrouillés ne peuvent être ni activés ni désactivés.

Lock

$ php bin/console shizuku:flag:lock --name=my_feature

Le verrouillage gèle l'état du flag définitivement. Il ne peut plus être activé ni désactivé. C'est une opération à sens unique: il n'existe pas de commande de déverrouillage. Un flag verrouillé signale que sa feature a atteint son état final et que les appels isEnabled() qui l'entourent sont devenus du code mort à supprimer.

Delete

$ php bin/console shizuku:flag:delete --name=my_feature
$ php bin/console shizuku:flag:delete --name=my_feature --force

Seuls les flags verrouillés peuvent être supprimés par défaut. --force contourne la restriction (avec confirmation).

⚠ Nettoyez votre code avant de supprimer

Supprimer un flag ne supprime pas ses appels dans votre code.
Tout isEnabled('my_feature') ou feature('my_feature') encore présent retournera silencieusement false une fois le flag supprimé, votre feature semblera cassée sans erreur, sans exception, sans log.

Un flag verrouillé et activé n'a pas vocation à rester dans le code indéfiniment. Supprimez chaque morceau if (isEnabled(...)), puis supprimez le flag. Le panel Profiler liste les flags verrouillés comme rappel jusqu'à leur suppression.

06 — Lifecycle

Cycle de vie d'un flag

Créé
disabled
Enabled
actif
Disabled
inactif
Locked
gelé
Deleted
supprimé
01Flag créé désactivé par défaut (ou activé avec --enable).
02Peut être togglé librement entre enabled et disabled jusqu'au verrouillage.
03Une fois verrouillé, l'état est gelé et ne peut plus être modifié. Il n'existe pas de déverrouillage. La feature est considérée comme livrée ou abandonnée — le flag est désormais de la dette technique.
04Nettoyer le code — supprimer chaque appel isEnabled() et feature(). Conserver le chemin gagnant, supprimer l'autre.
05Seulement ensuite, supprimer le flag. Un flag supprimé sans nettoyage du code retournera silencieusement false — la feature se casse sans avertissement.
07 — Caching

Mise en cache

Quand cache.app est disponible dans le container, le bundle le branche automatiquement. L'état des flags est mis en cache 300 secondes et invalidé immédiatement à chaque commande (enable, disable, lock, delete, create).

Pool personnalisé

# config/services.yaml
Devexploris\ShizukuFeatureFlags\Service\FeatureFlagService:
    arguments:
        $cache: '@cache.my_custom_pool'

Désactiver le cache

# config/services.yaml
Devexploris\ShizukuFeatureFlags\Service\FeatureFlagService:
    arguments:
        $cache: ~
Les modifications directes en base de données contournent l'invalidation du cache. Les changements seront visibles après expiration du TTL (300 s).
08 — Profiler

Symfony Profiler

Quand symfony/web-profiler-bundle est installé, un panel Feature Flags est ajouté à la toolbar automatiquement.

ÉlémentDescription
Checked / enabledNombre d'appels isEnabled() et combien ont retourné true.
Locked flagsFlags gelés en attente de nettoyage, affichés comme "Must be cleaned before removal".
Unknown flagsFlags appelés dans le code mais absents de la base. La toolbar se surligne en orange.
Caller chainChaîne complète Class::method ayant déclenché chaque isEnabled(), frames vendor exclues.
09 — Entity Reference

Référence de l'entité

PropriétéTypeDescription
namestringIdentifiant unique snake_case, validé à la création.
descriptionstringLabel lisible par un humain.
isEnabledboolSi le flag est actuellement actif.
isLockedboolSi le flag est gelé en attente de nettoyage.
createdAtDateTimeImmutableDéfini automatiquement à la persistance.
enabledAtDateTimeImmutable|nullDéfini lors de la première activation, remis à null à la désactivation.
lockedAtDateTimeImmutable|nullDéfini lors du verrouillage.