Variants
Variants enable A/B testing and multi-variant experiments by returning different values instead of just true/false.
Defining Variants
Section titled “Defining Variants”Basic Variant Definition
Section titled “Basic Variant Definition”use Cline\Toggl\Toggl;
Toggl::defineVariant('checkout-flow', [ 'control' => 40, // 40% of users 'v1' => 30, // 30% of users 'v2' => 30, // 30% of users]);
// With BackedEnumToggl::defineVariant(FeatureFlag::CheckoutFlow, [ 'control' => 40, 'v1' => 30, 'v2' => 30,]);
// Weights must sum to 100Retrieving Variants
Section titled “Retrieving Variants”$variant = Toggl::variant('checkout-flow');// Returns: 'control', 'v1', or 'v2'
// Contextual to specific user$variant = Toggl::for($user)->variant('checkout-flow');// Same user always gets same variant (sticky)
// With BackedEnum$variant = Toggl::for($user)->variant(FeatureFlag::CheckoutFlow);How Variants Work
Section titled “How Variants Work”Variants use consistent hashing (CRC32) to ensure:
- Same context always gets same variant
- Distribution matches specified weights
- No database lookups required
- Deterministic across requests
// User A always gets 'v1'Toggl::for($userA)->variant('experiment'); // 'v1'Toggl::for($userA)->variant('experiment'); // 'v1' (same)
// User B always gets 'control'Toggl::for($userB)->variant('experiment'); // 'control'Toggl::for($userB)->variant('experiment'); // 'control' (same)Use Cases
Section titled “Use Cases”A/B Testing
Section titled “A/B Testing”Toggl::defineVariant('pricing-page', [ 'control' => 50, 'price-first' => 25, 'features-first' => 25,]);
$layout = Toggl::for($user)->variant('pricing-page');
return match($layout) { 'price-first' => view('pricing.price-first'), 'features-first' => view('pricing.features-first'), default => view('pricing.control'),};Multi-Variant Experiments
Section titled “Multi-Variant Experiments”Toggl::defineVariant('button-color', [ 'blue' => 25, 'green' => 25, 'red' => 25, 'orange' => 25,]);
$buttonColor = Toggl::for($visitor)->variant('button-color');Gradual Feature Rollout
Section titled “Gradual Feature Rollout”Toggl::defineVariant('new-editor', [ 'legacy' => 70, // 70% stay on old 'new' => 30, // 30% get new editor]);
if (Toggl::for($user)->variant('new-editor') === 'new') { return $this->newEditor();}
return $this->legacyEditor();Algorithm Testing
Section titled “Algorithm Testing”Toggl::defineVariant('recommendation-algorithm', [ 'collaborative-filtering' => 33, 'content-based' => 33, 'hybrid' => 34,]);
$algorithm = Toggl::for($user)->variant('recommendation-algorithm');
$recommendations = match($algorithm) { 'collaborative-filtering' => $this->collaborativeFiltering($user), 'content-based' => $this->contentBased($user), 'hybrid' => $this->hybrid($user),};Checking Variants
Section titled “Checking Variants”Get Variant Names
Section titled “Get Variant Names”$variants = Toggl::variantNames('checkout-flow');// ['control', 'v1', 'v2']Get All Variant Configs
Section titled “Get All Variant Configs”$config = Toggl::getVariants('checkout-flow');// ['control' => 40, 'v1' => 30, 'v2' => 30]Check if Feature Has Variants
Section titled “Check if Feature Has Variants”if (Toggl::getVariants('my-feature')) { // Feature has variants $variant = Toggl::variant('my-feature');} else { // Regular boolean feature $active = Toggl::active('my-feature');}Updating Variant Weights
Section titled “Updating Variant Weights”// Start with small testToggl::defineVariant('new-search', [ 'legacy' => 90, 'new' => 10,]);
// Increase after positive resultsToggl::defineVariant('new-search', [ 'legacy' => 50, 'new' => 50,]);
// Full rolloutToggl::defineVariant('new-search', [ 'legacy' => 0, 'new' => 100,]);
// Or just switch to booleanToggl::define('new-search', true);Blade Usage
Section titled “Blade Usage”@php $variant = Toggl::for(auth()->user())->variant('landing-page');@endphp
@if($variant === 'hero-video') <x-hero-video />@elseif($variant === 'hero-carousel') <x-hero-carousel />@else <x-hero-static />@endifTracking Variant Performance
Section titled “Tracking Variant Performance”$variant = Toggl::for($user)->variant('checkout-flow');
// Track in analyticsAnalytics::track('checkout_started', [ 'user_id' => $user->id, 'variant' => $variant,]);
// Log conversionAnalytics::track('purchase_completed', [ 'user_id' => $user->id, 'variant' => $variant, 'amount' => $order->total,]);Combining with Other Features
Section titled “Combining with Other Features”Variants + Time Bombs
Section titled “Variants + Time Bombs”// Run experiment for 30 daysToggl::defineVariant('price-test', [ 'control' => 50, 'higher' => 25, 'lower' => 25,])->expiresAfter(days: 30);
// After expiration, pick winning variantToggl::define('price-test', 'lower'); // WinnerVariants + Contexts
Section titled “Variants + Contexts”// Different experiments per team$teamVariant = Toggl::for($team)->variant('team-dashboard');
// Different experiments per user$userVariant = Toggl::for($user)->variant('onboarding-flow');Variants + Dependencies
Section titled “Variants + Dependencies”Toggl::define('base-feature', true);
// Only run variant test if base feature is activeif (Toggl::active('base-feature')) { $variant = Toggl::variant('advanced-test');}Best Practices
Section titled “Best Practices”-
Keep experiments focused
// ✅ Good - test one thingToggl::defineVariant('button-text', ['buy-now' => 50,'purchase' => 50,]);// ❌ Avoid - too many variablesToggl::defineVariant('everything', ['variant-a' => 10,'variant-b' => 10,// ... 8 more variants]); -
Weights must sum to 100
// ✅ Good['a' => 50, 'b' => 30, 'c' => 20] // = 100// ❌ Invalid['a' => 50, 'b' => 30, 'c' => 30] // = 110 -
Use meaningful variant names
// ✅ Good['control', 'short-form', 'long-form']// ❌ Unclear['a', 'b', 'c'] -
Track everything
$variant = Toggl::variant('experiment');// Log variant assignmentLog::info('Variant assigned', ['feature' => 'experiment','variant' => $variant,'user' => $user->id,]); -
Plan your rollout
// Phase 1: Small testToggl::defineVariant('feature', ['old' => 95, 'new' => 5]);// Phase 2: Increase if positiveToggl::defineVariant('feature', ['old' => 50, 'new' => 50]);// Phase 3: Full rolloutToggl::define('feature', 'new'); // Switch to boolean
Testing Variants
Section titled “Testing Variants”test('variant returns consistent results', function () { Toggl::defineVariant('test', ['a' => 50, 'b' => 50]);
$user = User::factory()->create();
$variant1 = Toggl::for($user)->variant('test'); $variant2 = Toggl::for($user)->variant('test');
expect($variant1)->toBe($variant2); // Same user, same variant expect($variant1)->toBeIn(['a', 'b']); // Valid variant});
test('variant distribution is roughly correct', function () { Toggl::defineVariant('test', ['a' => 50, 'b' => 50]);
$results = ['a' => 0, 'b' => 0];
for ($i = 0; $i < 1000; $i++) { $variant = Toggl::for("user-{$i}")->variant('test'); $results[$variant]++; }
// Should be roughly 50/50 (allowing 10% variance) expect($results['a'])->toBeBetween(450, 550); expect($results['b'])->toBeBetween(450, 550);});Next Steps
Section titled “Next Steps”- Advanced Usage - Events, middleware, and commands
- Basic Usage - Core operations
- Strategies - Different resolution strategies