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

Flagception Bundle Laravel Package

flagception/flagception-bundle

A Symfony bundle for feature flags and toggles. Define flags, activate them by context or constraints, and check them in controllers, services, Twig templates, and annotations. Integrates Flagception with Symfony configuration and dependency injection.

View on GitHub
Deep Wiki
Context7

Getting Started

The first thing to internalize: this is a Symfony bundle, not a Laravel package. composer require flagception/flagception-bundle in a Laravel app will install fine and then do nothing — there is no service provider, no facade, no Blade directive, and the bundle's DI extension only runs under Symfony's kernel. (As of 6.1.1 this is unchanged; the only release activity is Symfony 5.4 re-added to the CI matrix, which tells you the bundle still targets older Symfony LTS lines, not Laravel.) A Laravel developer has two realistic entry points:

  1. Use the framework-agnostic core (flagception/flagception) and wire it yourself in a service provider. The bundle is mostly glue: it reads YAML, builds FeatureManager, registers validators, and (since 6.1.1) reports inactive features into route metadata for the profiler. You can replicate the useful 90% in ~40 lines.
  2. Skip the package and copy the model. The valuable idea here is "feature = default + a list of named constraints, all of which must pass." Laravel Pennant + a config/features.php gets you the same shape with first-class framework support.

If you are in a Symfony app, the minimal path is:

# config/packages/flagception.yaml
flagception:
    features:
        checkout.new_flow:
            default: false
            constraints:
                - name: environment
                  options: { env: ['dev', 'test'] }
public function __construct(private FeatureManagerInterface $features) {}

if ($this->features->isActive('checkout.new_flow')) {
    // new path
}

Where to read first, in order:

  • DependencyInjection/Configuration.php — this is the entire feature schema. Everything you can express is here.
  • Resources/config/*.xml — shows exactly which services get built and with what arguments. This is your wiring blueprint for Laravel.
  • The core package's validators (vendor/flagception/flagception/src/...) — the built-in vocabulary (environment, cookie, request, user, expression, time-based, etc.). These are the whole product.
  • New in 6.1.1: EventListener/RouteMetadataSubscriber.php (or wherever the subscriber now lives) — it annotates matched routes with the list of features that evaluated inactive. Worth thirty seconds because it doubles as documentation of the intended "flag per route" integration style.
  • FeatureManager::isActive($name, array $context) — the single method you'll call day-to-day.

Your first use case should be deliberately boring: gate one code path that is on in dev/test and off in prod, checked from exactly one place. Don't start with expression constraints or percentage rollouts.

Implementation Patterns

Config-first, code-second. Features belong in a config file (YAML in Symfony, config/features.php in Laravel), not scattered as booleans. Code should only ever ask isActive('some.flag'). If a flag's logic lives in a closure inside a controller, you've lost the audit trail that makes feature toggles worth having.

Build a request-scoped context once. The context array is how constraints see the world. Assemble it in a middleware and reuse it:

// app/Http/Middleware/FeatureContext.php
public function handle($request, Closure $next)
{
    app(FeatureContext::class)->set([
        'user'    => $request->user()?->only('id', 'roles'),
        'request' => ['ip' => $request->ip(), 'header' => $request->headers->all()],
        'env'     => app()->environment(),
    ]);
    return $next($request);
}

Wrap it at the boundaries, not in the domain. Blade directive, route middleware, and a job-level check are the right seams:

Blade::if('feature', fn (string $name) =>
    app(FeatureManagerInterface::class)->isActive($name, app(FeatureContext::class)->all())
);
// @feature('checkout.new_flow') ... @endfeature

Never let isActive() leak into entities, models, or domain services — it makes them untestable and turns flags into permanent branches.

All constraints are ANDed. If you need "admin OR internal env," express it inside a single expression constraint or split it into two flags with an explicit any at the call site. Trying to make the constraint list itself disjunctive is the most common source of confusion.

Rollout pattern that actually works: environment constraint for non-prod, plus an expression/percentage constraint keyed on user.id in prod. Keep both in the same flag so dev and prod don't diverge structurally. Hash on a stable user identifier, never on session or request ID, or users will flip mid-session.

Route-level gating is a first-class path in Symfony. The bundle already hooks route metadata (extended in 6.1.1 to surface inactive features), so "this controller action is behind flag X" is a supported shape rather than something you invent. Mirror it in Laravel with a route middleware that reads $route->getName() → flag map from config, rather than sprinkling isActive() in controllers.

Testing: bind a stub FeatureManagerInterface in the container, or add a test helper Feature::force('checkout.new_flow', true). Assert both branches of every gated path at least once. If your tests exercise expression constraints, you're testing the expression language, not your code — stub those out.

Retire flags on a schedule. Add a features:list Artisan command that greps for isActive('...') and reports flags with no call sites. A bundle that makes flags cheap to add makes them expensive to remove; budget for it.

Gotchas and Tips

  • Not installable in Laravel as-is. No auto-discovery, no service provider. Plan for a provider or for using the core library directly. The Resources/config/services.xml is your copy-paste source for constructor arguments.
  • Unknown flags return the default (almost always false) silently. A typo in isActive('checkout.new_flwo') disables your feature with zero diagnostics. Build a lint step that extracts flag names from your code and diffs them against the config.
  • Queue and CLI have no context. user/request/cookie constraints evaluate against an empty array in workers and console commands, so features silently turn off in jobs. Either pass context explicitly into the job, or evaluate the flag at dispatch time and put the boolean in the payload.
  • default vs. constraints semantics are subtle. The default is the fallback; once constraints exist they decide. Verify against the source before relying on defaults for "constraints didn't match" — the distinction matters for flags that are on-by-default with an opt-out.
  • Expression constraints are eval-adjacent. They're powerful and they'll be written by whoever is on call. No sandbox, arbitrary context access, and errors may surface as "inactive" rather than as exceptions. Treat expression strings as production code: review them, and log when one throws.
  • Caching has two layers to trip over. Symfony's config cache plus any PSR-6 cache pool for feature state means a config change can be invisible until you clear. In Laravel, config:cache plus an opcache will do the same thing to your features.php.
  • Debugging just got easier — in Symfony only. 6.1.1 makes the route metadata subscriber record inactive features, so the profiler's routing panel tells you why a page rendered the old branch without you adding dump() calls. Don't expect an equivalent in Laravel; if you build a wrapper, consider logging the evaluated flag set (name → active/inactive → matched constraint) once per request behind a debug switch, because "silently false" is this package's defining failure mode.
  • Annotations/attributes are edge-only. If the bundle still ships Doctrine annotations, PHP 8 attributes may not be supported — check the Annotations/ directory before adopting the attribute style. In Laravel, use a middleware attribute instead; don't pull in doctrine/annotations just for this.
  • Extension points are the redeeming feature. Custom validators and context providers let you plug in tenancy, plan tiers, or region without forking. If you build a Laravel wrapper, keep that seam: interface Constraint { public function passes(array $context): bool; } and a registry keyed by config name.
  • Upgrade posture: 6.1.1 is a patch — a CI matrix tweak plus an additive subscriber change, no deprecations or BC breaks, so upgrading from 6.1.x is a no-op for application code. The notable signal is that the project is still adding Symfony 5.4 to its test matrix, i.e. maintenance is compatibility-focused, not feature-focused (30 stars, 0 dependents, single small maintainer). MIT and a tiny surface means forking or vendoring is cheap — but pin the version, keep it behind your own FeatureManager interface, and assume you may own it eventually.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core