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

Toggle Bundle Laravel Package

qandidate/toggle-bundle

Symfony bundle for Qandidate Toggle that brings feature flags into your app. Define toggles in configuration, check them via the toggle manager, and safely enable or disable features per environment or user without redeploying. Useful for gradual rollouts and A/B tests.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the important framing: this is a Symfony bundle, not a Laravel package. composer require qandidate/toggle-bundle in a Laravel app gives you a bundle class, a Symfony DependencyInjection extension, YAML config loading, and a Twig extension — none of which Laravel will ever call. What you actually want as a Laravel developer is the underlying library, qandidate/toggle, which is framework-agnostic plain PHP. Pull that one in and treat the bundle as documentation-by-example for how to wire it up.

The library is small and worth reading in one sitting:

  • Qandidate\Toggle\Toggle — the entry point; holds a map of toggle name → operator.
  • Qandidate\Toggle\Context — an immutable bag of properties (user_id, environment, locale, …).
  • Qandidate\Toggle\Operator\* — the strategies: AlwaysActiveOperator, AlwaysInactiveOperator, AffirmativeOperator (AND), ContradictoryOperator (NOT), PercentageOperator (sticky bucketing).
  • Qandidate\Toggle\ContextFactory — builds a Context from a PSR-7/Symfony request (the bundle's main convenience).

Minimal Laravel wiring, ~15 lines:

// app/Providers/FeatureServiceProvider.php
$this->app->singleton(Toggle::class, function () {
    return new Toggle([
        'new_checkout' => new PercentageOperator(10, 'user_id'),
        'beta_banner'  => new AffirmativeOperator([
            new AlwaysActiveOperator(), // stand-in for an env check operator
        ]),
    ]);
});

$this->app->singleton(Context::class, fn () =>
    new Context(['user_id' => (string) optional(auth()->user())->id, 'env' => app()->environment()])
);

First use case: a percentage rollout of a new checkout flow, gated on a stable user_id. That is the 80% case for this library and the one it does best. Reach for it before you build a DB-backed flag table — the config-as-code model forces you to review toggles in PRs.

Then expose a Blade directive mirroring the bundle's Twig extension (Twig/ in the repo — check the exact function name there; it's an isActive-style call):

Blade::if('feature', fn (string $name) => app(Toggle::class)->isActive($name, app(Context::class)));
@feature('new_checkout')
    <x-checkout.v2 />
@else
    <x-checkout.v1 />
@endfeature

Implementation Patterns

Wrap it, always. Never let Qandidate\Toggle\* types leak into controllers or domain services. Define App\Features\Features with active(string $name): bool, and bind qandidate behind it. Adoption of this package is very low (89 stars, zero dependents), so the probability you migrate to Unleash/Flagsmith/LaunchDarkly in 18 months is high; a one-interface seam makes that a one-file change.

Operators over if statements. The value here is composition, not isActive(). Build named operators as services:

class StaffUserOperator implements Operator
{
    public function matches(Context $context): bool
    {
        return in_array($context->get('user_id'), config('features.staff_ids'), true);
    }
}

Then compose: new AffirmativeOperator([new PercentageOperator(50, 'user_id'), new StaffUserOperator()]) reads as "50% of staff."

Context via middleware, not per-call. A ResolveFeatureContext middleware that populates a request-scoped Context (user id, tenant id, environment, plan tier) keeps every call site honest. Ordering matters: it must run after auth.

Kill switches as a distinct category. Keep an ops namespace (ops.payments_provider_b) separate from release toggles (release.new_checkout). Ops toggles are permanent and should be evaluated even on error paths; release toggles are temporary and should be deleted within a sprint or two of hitting 100%. Enforce deletion with a TODO(expires: 2026-09-01) comment convention and a CI grep.

Testing. Two patterns, both essential:

// Unit: deterministic operator, no container
$toggle = new Toggle(['x' => new AlwaysActiveOperator()]);
$this->assertTrue($toggle->isActive('x', new Context([])));

For percentage tests, don't assert on a hardcoded user id (the hash is an implementation detail that can change). Instead assert distribution: evaluate 10,000 synthetic contexts and check the ratio lands in 10% ± 1%.

Debug command. Build php artisan feature:list --user=123 that prints every toggle, its operator class, and the evaluated result for that context. This is the single highest-ROI thing you can add — without it, "why is this on for me?" costs 20 minutes of dd().

Config-as-code review. Since definitions are static config, a toggle change shows up as a diff in a PR. Lean into it: put rollout percentages in config/features.php, not in env vars, so the change is auditable and code-reviewed rather than edited in a dashboard at 2am.

Gotchas and Tips

  • Bundle vs library is the #1 trap. Every README example you find (qandidate_toggle: YAML, qandidate.toggle.manager service, Twig feature() function) is Symfony-specific. Translate to config/features.php + a container binding; don't try to shim the bundle in.
  • No OR operator. AffirmativeOperator is strict AND. To get OR you must De Morgan it: new ContradictoryOperator(new AffirmativeOperator([new ContradictoryOperator($a), new ContradictoryOperator($b)])). This is genuinely ugly and is where most bugs live — wrap it in a anyOf() helper on day one.
  • Percentage bucketing is keyed on a property name string, not the value alone. Changing the key from user_id to email, or adding a property to the hash, re-buckets everyone. A user who was in the 10% can silently fall out. Treat the key name as a frozen API once shipped.
  • Unknown toggle names throw. Toggle::isActive() on an unregistered name raises UnknownToggleException. In a Blade directive that's a 500 on your marketing page. Your Features wrapper should try/catch and default to false, with a log entry — fail closed, not loud.
  • No storage layer, no admin UI. Definitions are compiled into code. If product wants to flip flags without a deploy, this package is the wrong tool; you need a DB-backed or API-backed provider. Decide that up front.
  • Evaluation is uncached. Each isActive() walks its operator tree. Harmless once per request; catastrophic inside a Blade @foreach over 500 rows. Resolve once in the controller and pass a boolean to the view.
  • Config caching. In Symfony the bundle config is container-compiled (cache clear after changes); the Laravel analogue is that your features.php is baked by config:cache, so any file-based toggle source read at runtime will go stale. Read toggle definitions from config, not from file_get_contents at runtime.
  • PHP/Symfony version constraints. The bundle is Symfony-2/3-era; its composer.json may pin constraints that conflict with a modern PHP 8.3 + Laravel 11 stack. This is another reason to take qandidate/toggle alone — verify its php constraint before committing.
  • Maintenance risk is real. Near-zero dependents means you are the de facto maintainer of your integration. Vendor it or keep the interface seam; don't build 40 toggles on top of a package nobody else uses.
  • Extension point to exploit: Operator is a one-method interface (matches(Context): bool). Custom operators — tenant allowlists, time-window rollouts, "only in staging" — are trivial and are where this library earns its keep over a boolean column.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route