jawabapp / remote-config
A standalone Laravel package for A/B testing, remote configuration, and feature experimentation
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:Blade
pkg:composer/jawabapp/remote-config
Requires
- php: ^8.1|^8.2|^8.3
- illuminate/cache: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- jfcherng/php-diff: ^6.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.0|^11.0
This package is auto-updated.
Last update: 2025-10-29 09:20:48 UTC
README
A standalone Laravel package for A/B testing, remote configuration management, and feature experimentation. Built to be framework-agnostic and easily integrable into any Laravel project.
Features
- ✅ A/B Testing: Create experiments with multiple variants and ratio-based distribution
- ✅ Remote Configuration: Manage app configurations remotely without deploying
- ✅ Winner Deployment: Deploy winning variants to specific user segments
- ✅ Test Overrides: IP-based testing for QA teams
- ✅ Polymorphic User Support: Works with any user model
- ✅ Comprehensive Targeting: Platform, country, and language targeting
- ✅ Audit Logging: Complete audit trail of all changes
- ✅ Flexible Architecture: Configurable table prefixes, routes, and middleware
- ✅ Helper Functions: Convenient helper functions for common operations
Installation
1. Install via Composer
composer require jawabapp/remote-config
2. Publish Configuration
php artisan vendor:publish --tag=remote-config-config
3. Run Migrations
php artisan migrate
4. (Optional) Publish Views for Customization
php artisan vendor:publish --tag=remote-config-views
5. (Optional) Publish Assets
php artisan vendor:publish --tag=remote-config-assets
Configuration
Edit config/remote-config.php to customize the package:
return [ // Enable/disable the entire system 'enabled' => true, // Enable IP-based testing (requires Redis) 'testing_enabled' => true, // Only users created after this date will be in experiments 'user_created_after_date' => '2022-08-29', // Admin panel routes configuration 'routes' => [ 'prefix' => 'remote-config', 'middleware' => ['web', 'auth'], ], // API routes configuration 'api_routes' => [ 'prefix' => 'api/config', 'middleware' => ['api'], ], // User model that will participate in experiments 'experimentable_model' => \App\Models\User::class, // Define flow types available 'flow_types' => [ 'general' => 'General Configuration', 'onboarding' => 'Onboarding Flow', // Add more types... ], // Targeting options 'targeting' => [ 'platforms' => ['ios', 'android', 'web'], 'countries' => ['SA', 'AE', 'US', ...], 'languages' => ['ar', 'en', 'fr', ...], ], ];
Setup
Add Trait to User Model
Add the Experimentable trait to your User model (or any model that should participate in experiments):
namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Jawabapp\RemoteConfig\Traits\Experimentable; class User extends Authenticatable { use Experimentable; // ... rest of your model }
Usage
API Endpoints
Get Remote Configuration
GET /api/config?type=default&platform=ios&country=SA&language=ar
Response:
{
    "success": true,
    "data": {
        "feature_flags": {...},
        "ui_config": {...}
    },
    "meta": {
        "type": "default",
        "has_experiment": true,
        "experiment_id": 5,
        "flow_id": 12
    }
}
Confirm Experiment
POST /api/config/confirm Content-Type: application/json { "experiment_name": "onboarding_v2", "metadata": { "completed_steps": 5, "time_spent": 120 } }
Report Validation Issue
POST /api/config/issue Content-Type: application/json { "path": "feature_flags.new_checkout", "invalid_value": "invalid_string", "platform": "ios", "type": "type_error", "error_message": "Expected boolean, got string" }
Using Helper Functions
// Get configuration for a user $config = get_user_config($user, 'onboarding', [ 'platform' => 'ios', 'country' => 'SA', 'language' => 'ar' ]); // Check if user is in an experiment if (is_in_experiment($user, 'checkout_redesign')) { // User is in experiment } // Get the variant user is assigned to $variant = experiment_variant($user, 'checkout_redesign'); // Get experiment statistics $stats = experiment_stats($experiment);
Using Trait Methods
// Assign user to experiment $assignment = $user->assignToExperiment('onboarding', null, [ 'platform' => 'ios', 'country' => 'SA', 'language' => 'ar' ]); // Apply winner configuration $config = $user->applyWinnerConfig($baseConfig, 'onboarding', [ 'platform' => 'ios', 'country' => 'SA', 'language' => 'ar' ]); // Confirm experiment $user->confirmExperiment('onboarding_v2', ['completed' => true]); // Check if confirmed if ($user->hasConfirmedExperiment('onboarding_v2')) { // User confirmed this experiment }
Database Structure
The package creates 11 tables:
| Table | Purpose | 
|---|---|
| flows | Configuration variants | 
| flow_logs | Audit log for flows | 
| experiments | A/B test definitions | 
| experiment_logs | Audit log for experiments | 
| experiment_flow | Pivot table linking experiments to flows with ratios | 
| experiment_assignments | User assignments to experiments (polymorphic) | 
| experiment_assignment_logs | Historical assignment logs | 
| winners | Deployed winning configurations | 
| winner_logs | Audit log for winners | 
| confirmations | User experiment confirmations | 
| validation_issues | Reported configuration errors | 
All tables support optional table prefix via config.
Core Concepts
Flows
Flows are JSON configuration variants. Each flow has:
- Type: Categorizes the configuration (e.g., 'onboarding', 'feature_flags')
- Content: JSON data containing the configuration
- Status: Active/inactive flag
- Overwrite ID: Allows multiple experiments on same base config
Experiments
Experiments are A/B tests that compare multiple flows:
- Name: Human-readable experiment name
- Targeting: Platform, country, language filters
- Flows: Multiple flows with ratio distribution (e.g., 50/50 split)
- Status: Active/inactive
- User Created After: Only include users created after this date
Assignments
When a user meets experiment criteria, an assignment is created:
- Polymorphic User: Works with any model
- Sticky: User always gets the same variant
- Logged: All assignments are logged for analysis
Winners
When an experiment concludes, deploy the winner:
- Target-Specific: Deploy to specific platform/country/language
- Priority: Winners override experiments
- Based on Flows: Can be based on a winning flow
Test Overrides
For QA testing (requires Redis):
- IP-Based: Testers get specific variants based on IP
- Temporary: Stored in Redis cache
- Override Everything: Highest priority
Priority Order
Configuration is applied in this order (highest to lowest):
- Test Override (if testing enabled and IP matches)
- Winner (if exists for user's platform/country/language)
- Experiment (if user is assigned to active experiment)
- Base Flow (default configuration)
Advanced Usage
Custom Experiment Logic
use Jawabapp\RemoteConfig\Services\ConfigService; use Jawabapp\RemoteConfig\Services\ExperimentService; $configService = app(ConfigService::class); $experimentService = app(ExperimentService::class); // Get configuration with custom logic $config = $configService->getConfig( $user, 'feature_flags', ['platform' => 'ios', 'country' => 'SA', 'language' => 'ar'], $testIp, $testWinnerId ); // Get assignment stats $stats = $configService->getAssignmentStats($experiment); // Remove assignment $configService->removeAssignment($user, $experimentId); // Select flow from experiment $flow = $experimentService->selectFlow($experiment); // Get experiment statistics $stats = $experimentService->getExperimentStats($experiment); // Clear counters $experimentService->clearExperimentCounters($experiment);
Working with Test Overrides
use Jawabapp\RemoteConfig\Models\TestOverride; use Jawabapp\RemoteConfig\Models\Flow; // Create test override $testOverride = new TestOverride('192.168.1.1', 'onboarding'); $testOverride->set($flowId, 3600); // TTL in seconds // Check if override exists if ($testOverride->exists()) { $content = $testOverride->getContent(); } // Delete override $testOverride->delete(); // Get all overrides for a type $overrides = TestOverride::getAllForType('onboarding');
Admin Panel
Admin routes are available at /remote-config (configurable):
- /remote-config/flows- Manage configuration variants
- /remote-config/experiments- Manage A/B tests
- /remote-config/winners- Manage deployed winners
- /remote-config/testing- Manage test overrides
Note: Admin controllers and views are not yet implemented. You can create custom controllers extending the base Controller class.
Events & Observers
The package automatically logs all changes via model observers:
- FlowObserver- Logs flow changes
- ExperimentObserver- Logs experiment changes
- WinnerObserver- Logs winner changes
- ExperimentAssignmentObserver- Logs user assignments
Disable logging in config:
'audit_logging' => [ 'enabled' => false, 'log_assignments' => false, ],
Testing
Testing Experiments in Development
- Use Test Overrides: Set up IP-based overrides via the testing endpoint
- Use Query Parameters: Pass variant via URL (e.g., ?experiment=variant_a)
- Use Test Winner ID: Pass test_winner_idin API request
PHPUnit Testing
use Jawabapp\RemoteConfig\Models\Experiment; use Jawabapp\RemoteConfig\Models\Flow; public function test_user_gets_experiment_variant() { // Create experiment $experiment = Experiment::factory()->create([ 'type' => 'test', 'platforms' => ['ios'], 'countries' => ['US'], 'languages' => ['en'], 'is_active' => true, ]); // Create flows $flowA = Flow::factory()->create(['type' => 'test']); $flowB = Flow::factory()->create(['type' => 'test']); // Attach to experiment $experiment->flows()->attach($flowA, ['ratio' => 50]); $experiment->flows()->attach($flowB, ['ratio' => 50]); // Test assignment $user = User::factory()->create(); $assignment = $user->assignToExperiment('test'); $this->assertNotNull($assignment); $this->assertEquals($experiment->id, $assignment->experiment_id); }
Requirements
- PHP >= 8.1
- Laravel >= 10.0
- Redis (optional, for test overrides)
- jfcherng/php-diff >= 6.0 (for audit diffs)
Roadmap / TODO
The package core is functional but still needs:
- Admin Panel Controllers (Flow, Experiment, Winner, Testing)
- Admin Panel Views with JSONEditor integration
- Admin Layout (Tailwind + Alpine.js)
- Request Validation Classes
- Event System (ExperimentAssigned, WinnerDeployed, etc.)
- Artisan Commands (clear cache, stats, etc.)
- Test Suite
- Documentation Site
Contributing
Contributions are welcome! Please submit pull requests or open issues.
License
MIT License
Credits
Developed by Jawab Team based on internal experimentation system.
Quick Start Example
// 1. Add trait to User model class User extends Authenticatable { use \Jawabapp\RemoteConfig\Traits\Experimentable; } // 2. Create a base flow $baseFlow = Flow::create([ 'type' => 'onboarding', 'content' => [ 'steps' => ['welcome', 'profile', 'preferences'], 'theme' => 'light', ], 'is_active' => true, ]); // 3. Create variant flows $variantA = Flow::create([ 'type' => 'onboarding', 'content' => [ 'steps' => ['welcome', 'tutorial', 'profile', 'preferences'], 'theme' => 'light', ], 'is_active' => true, ]); $variantB = Flow::create([ 'type' => 'onboarding', 'content' => [ 'steps' => ['welcome', 'quick_start'], 'theme' => 'dark', ], 'is_active' => true, ]); // 4. Create experiment $experiment = Experiment::create([ 'name' => 'Onboarding Redesign 2024', 'type' => 'onboarding', 'platforms' => ['ios', 'android'], 'countries' => ['US', 'SA'], 'languages' => ['en', 'ar'], 'is_active' => true, ]); // 5. Attach variants with 50/50 split $experiment->flows()->attach($variantA, ['ratio' => 50]); $experiment->flows()->attach($variantB, ['ratio' => 50]); // 6. Get config for user (automatically assigns to experiment) $config = get_user_config($user, 'onboarding', [ 'platform' => 'ios', 'country' => 'US', 'language' => 'en', ]); // 7. User sees either variant A or B configuration // Assignment is sticky - same user always gets same variant // 8. Track completion $user->confirmExperiment('Onboarding Redesign 2024', [ 'completed_at' => now(), 'time_spent' => 120, ]); // 9. After analysis, deploy winner $winner = Winner::create([ 'type' => 'onboarding', 'platform' => 'ios', 'country_code' => 'US', 'language' => 'en', 'content' => $variantA->content, 'flow_id' => $variantA->id, 'is_active' => true, ]); // Now all iOS/US/English users get variant A, regardless of experiment
That's it! You now have a fully functional A/B testing system.