Installation:
composer require filipe07/laravel-ab
php artisan vendor:publish --provider="AbTesting\AbTestingServiceProvider"
php artisan migrate
ab_experiments, ab_goals) are created.First Experiment Setup:
config/abtesting.php:
'experiments' => [
'logo' => [
'variants' => ['logo-big', 'logo-grayscale', 'brand-name'],
'default' => 'logo-big',
],
],
First Use Case:
@if (AbTesting::isVariant('logo-big'))
<div class="logo-big"></div>
@endif
AbTesting::trackGoal('logo_click', $userId);
Experiment Creation:
config/abtesting.php with variants and defaults.AbTestingServiceProvider):
AbTesting::addExperiment('header', ['variant1', 'variant2'], 'variant1');
Variant Assignment:
AbTesting::getVariant($experimentName) to fetch the assigned variant for a user (persistent via cookie or session).route('home')->where('variant', '[A-Za-z0-9_-]+')->middleware(function ($request, $next) {
AbTesting::setVariant('homepage', $request->variant);
return $next($request);
});
Goal Tracking:
// Controller
AbTesting::trackGoal('purchase', auth()->id());
// Blade (e.g., for link clicks)
<a href="{{ route('purchase') }}" onclick="AbTesting.trackGoal('add_to_cart', {{ auth()->id() }})">
Reporting:
$results = AbTesting::getExperimentResults('logo');
// Returns: ['logo-big' => 42, 'logo-grayscale' => 58, ...]
Blade Directives: Create a custom directive for cleaner syntax:
Blade::directive('ab', function ($expression) {
return "<?php if (AbTesting::isVariant({$expression})): ?>";
});
Usage:
@ab('logo-big')
<div class="logo-big"></div>
@endab
Middleware for Forced Variants: Override variants for testing:
public function handle($request, Closure $next) {
AbTesting::forceVariant('logo', 'logo-grayscale');
return $next($request);
}
Database Seeding: Pre-populate experiments/results for testing:
AbTesting::seedExperiment('logo', ['logo-big' => 100, 'logo-grayscale' => 0]);
Cookie/Session Persistence:
AbTesting::clearVariant('logo'); // Force re-randomization
AbTesting::useSessionStorage(true) in config to avoid cookie conflicts.Default Variant Fallback:
default from config is used. Ensure defaults are set:
'experiments' => [
'logo' => [
'variants' => ['A', 'B'],
'default' => 'A', // Required!
],
],
Goal Tracking Granularity:
AbTesting::trackGoal('signup', auth()->id());
null or a placeholder ID:
AbTesting::trackGoal('view', $request->ip());
Migration Conflicts:
ab_experiments, ab_goals).utf8mb4_unicode_ci for Laravel 8+).Performance:
AbTesting::getVariant() in loops or critical paths. Cache results:
$variant = cache()->remember("ab_variant_{$experiment}", now()->addHours(1), fn() =>
AbTesting::getVariant($experiment)
);
Variant Assignment: Log assigned variants to debug:
\Log::debug('AB Variant', ['experiment' => 'logo', 'variant' => AbTesting::getVariant('logo')]);
\Log::debug('AB Cookie', ['variant' => request()->cookie('ab_logo')]);
Goal Tracking: Verify goals are recorded:
$goals = \DB::table('ab_goals')->where('goal_name', 'purchase')->get();
Configuration:
Validate config/abtesting.php:
\Log::debug('AB Config', ['experiments' => config('abtesting.experiments')]);
Custom Storage:
Override storage engines (e.g., Redis) by binding a custom AbTesting\Contracts\Storage implementation:
$this->app->bind(
AbTesting\Contracts\Storage::class,
AbTesting\Storage\RedisStorage::class
);
Event Listeners:
Extend functionality via events (e.g., AbTesting\Events\VariantAssigned):
event(new \AbTesting\Events\VariantAssigned($experiment, $variant));
Custom Reports:
Query the ab_goals table directly for advanced analytics:
$results = \DB::table('ab_goals')
->where('experiment', 'logo')
->groupBy('variant')
->get();
Multi-Tenant Support:
Scope experiments to tenants by overriding the getUserIdentifier() method in a custom storage class:
public function getUserIdentifier(): string
{
return auth()->user()->tenant_id . '_' . auth()->id();
}
How can I help you explore Laravel packages today?