qandidate/toggle
Qandidate Toggle is a PHP library for managing feature toggles (feature flags) in your applications. Define toggles, activate them conditionally with strategies, and roll features out safely without redeploying code.
Architecture fit. qandidate/toggle is a framework-agnostic PHP library built around a narrow, useful domain model: a toggle (named feature) holds a collection of conditions, each condition being an operator + key + operand evaluated against a context array; a repository abstraction (ToggleRepository, with in-memory and key-value/Redis-style implementations) persists toggle definitions. That is a decision engine, not a flag-management platform. It has no HTTP layer, no admin API, no audit trail, no events, and no Laravel coupling — which is simultaneously its strength (clean seam, easy to bind into the container) and its cost (all the platform around it is yours to build).
Integration feasibility. composer require qandidate/toggle is trivial; the real work is glue you must write: a service provider, a config file, a facade/contract, and a repository choice wired to your Redis instance. Effort: roughly 1–3 engineer-days for a production-credible wrapper with tests, assuming Redis is already available; 1–2 weeks if you also want an admin surface, per-flag caching, and rollout semantics. There is a first-party Symfony bundle in a separate repo — do not install it in a Laravel app; it drags Symfony dependencies for functionality you will reimplement in ~80 lines.
Technical risk.
user_id conditions that are only as trustworthy as the caller's input.Key questions to answer before committing.
ext-redis majors does composer.json actually permit versus our Laravel 11 / PHP 8.3 baseline?Stack fit. The library's shape fits Laravel well — bind a repository in a service provider, expose a thin facade, keep evaluation in the domain layer. The version era is the concern: it is a PHP 7-generation package. Validate against PHP 8.2/8.3 for deprecations before any design work; if it emits notices under 8.3, that is a fork-and-patch decision, not a blocker.
Recommended pattern — wrap, don't call directly. Define an application-owned contract (e.g. App\FeatureFlags\Flags with active(string $flag, array $context = []): bool) and implement it with qandidate internally. Every call site depends on your interface. This costs half a day, makes the package swappable, gives you a single place to add caching, logging, fail-open/fail-closed policy, and test doubles, and is the single highest-leverage decision in this assessment.
Migration path. Typical starting state is env()/config flags or a hand-rolled config('features.*') array. Sequence: (1) introduce the Flags contract backed by config, so call sites change once; (2) swap the implementation to qandidate with the in-memory repository to keep dev/test hermetic; (3) point staging/prod at Redis. This ordering means feature-flag call sites are converted before the provider is risked. If instead you are migrating off a hosted vendor, keep the contract provider-shaped so both can run in parallel for a release cycle.
Compatibility specifics to pin.
ext-redis and one adapter.FLUSHDB or key-scan operations from other systems cannot wipe your flag set.Sequencing (suggested 4–5 weeks elapsed, part-time).
Maintenance. Plan for custodianship, not consumption. Budget a named owner plus a recurring dependency-review task (PHP minor bumps, Predis majors, security advisories). Because MIT permits forking, the realistic end state for a long-lived dependency is "vendored fork with our patches" — acceptable, but it should be a conscious, documented decision rather than an incident-driven one.
Support. Community-only, small audience, no SLA and no visible CVE process. That is fine for internal rollout flags and unacceptable as the sole control for a security boundary. Pair any entitlement flag with an independent server-side authorisation check.
Scaling. The workload is read-heavy and write-rare — a good architectural fit. Load the full toggle collection once per request (not once per flag), memoise within request scope, and reset on worker recycle. With that pattern, cost is a single Redis GET per request and negligible evaluation overhead. Writes go through your admin layer, not the request path; keep them rare. Multi-region is the sharp edge: independent Redis stores mean flags can disagree across regions for seconds to minutes, so global kill switches should live in config/deploy artefacts, not in a replicated key-value store.
Failure modes to design for explicitly.
Ramp-up. The API surface is small; a mid-level Laravel developer is productive in under a day, and the wrapper contract means they never touch qandidate directly. The real ramp-up cost is organisational, not technical: naming conventions, default-state policy, fail-mode classification, expiry discipline, and a documented list of who may flip what in production. Write that one-page convention document before the pilot, not after — it is cheaper than retrofitting semantics onto a few hundred call sites.
How can I help you explore Laravel packages today?