mordilion/split-test
Composer 安装命令:
composer require mordilion/split-test
包简介
A library to get the ability of split testing (A/B testing)
README 文档
README
A lightweight PHP library for deterministic A/B and multivariate testing. Assign visitors to experiment variations using seed-based bucketing, keep their choice consistent across requests via cookies or headers, and plug in your own selection strategies whenever the defaults are not enough.
Features
- Deterministic bucketing — the same seed always produces the same variation, so visitors see a consistent experience across sessions and requests.
- Weighted distributions — split traffic across any number of variations with arbitrary weights.
- Pluggable chooser strategies — ship with
Random,Balanced,StaticandCallbackchoosers, or implement your own viaChooserInterface. - Groups — override the default variations for specific visitor cohorts (logged-in users, regions, feature flags, …).
- String serialization — round-trip experiments via cookies or headers with
fromString()/__toString(). - Persistence helper —
Container::deliver()writes the assignment cookie and header in one call. - Selection callbacks — hook variation assignment into your analytics or tracking pipeline.
- Small surface area — four model classes, four choosers, one container.
Requirements
- PHP 8.1+
ext-iconv
Installation
composer require mordilion/split-test
Quick Start
<?php use Mordilion\SplitTest\Container; use Mordilion\SplitTest\Model\Experiment; use Mordilion\SplitTest\Model\Experiment\Variation; // Reuse the visitor's seed if we have already seen them. $seed = (int) ($_COOKIE['seed'] ?? time()); $container = new Container($seed); $experiment = new Experiment('homepage-hero', true); $experiment->addVariation(new Variation('a', 50)); $experiment->addVariation(new Variation('b', 50)); $container->addExperiment($experiment); $variation = $container->getExperimentVariation('homepage-hero'); echo $variation->getName() === 'b' ? 'Variation B' : 'Variation A'; // Remember the seed so this visitor keeps their assignment. setcookie('seed', (string) $seed, time() + 30 * 86400);
Core Concepts
Container
The Container owns the seed and a collection of experiments. The seed is the single source of randomness — combined with each experiment's name it yields a per-experiment seed, so bucket decisions stay reproducible and independent between experiments.
$container = new Container($seed); // default RandomChooser $container = new Container($seed, $customChooser); // or bring your own $container->addExperiment($experiment); $variation = $container->getExperimentVariation('homepage-hero');
Experiment
An experiment has a name, an enabled flag, one or more variations, optional groups, and an optional callback that fires on selection.
$experiment = new Experiment( name: 'checkout-button', enabled: true, groups: [], // optional Group[] seed: 0, // optional, normally managed by the Container variations: [], // optional, can also be added via addVariation() callback: null, // optional, called on each selection when enabled ); $experiment->addVariation(new Variation('control', 80)); $experiment->addVariation(new Variation('variant', 20));
Variation
A named bucket with a weight between 0 and 100. Weights do not have to sum to 100 — the library normalises them internally — but picking values that sum to 100 makes the intended percentages easy to read in config and logs.
new Variation('control', 75); new Variation('variant', 25);
Group (optional)
Groups let you serve a different variation set to a specific cohort. When a visitor matches a group, its variations override the experiment's defaults for that group only.
use Mordilion\SplitTest\Model\Experiment\Group; $experiment = new Experiment('checkout-button', true); $experiment->addVariation(new Variation('control', 50)); $experiment->addVariation(new Variation('variant', 50)); // B2B users always see the enterprise flow. $experiment->addGroup(new Group('b2b', [ new Variation('enterprise-flow', 100), ])); $variation = $container->getExperimentVariation( experimentName: 'checkout-button', groupName: 'b2b', );
Chooser Strategies
All choosers implement Mordilion\SplitTest\Chooser\ChooserInterface. Pass an instance to Container (the default is RandomChooser).
RandomChooser (default)
Seeded mt_rand weighted by variation distribution — deterministic for a given seed.
use Mordilion\SplitTest\Chooser\RandomChooser; $container = new Container($seed, new RandomChooser());
BalancedChooser
Deterministic percentage-based bucketing. Splits the 1..100 range according to the cumulative share of each variation — useful when you want predictable bucket sizes instead of a random draw.
use Mordilion\SplitTest\Chooser\BalancedChooser; $container = new Container($seed, new BalancedChooser());
StaticChooser
Always picks the same variation — handy for tests, ?forceVariation= query flags, or admin overrides.
use Mordilion\SplitTest\Chooser\StaticChooser; // By name … $container = new Container($seed, new StaticChooser('variant')); // … or by positional index. $container = new Container($seed, new StaticChooser(1));
CallbackChooser
Delegates selection to your own callable — connect a feature-flag service, ML model, or any custom logic.
use Mordilion\SplitTest\Chooser\CallbackChooser; use Mordilion\SplitTest\Model\Experiment; $chooser = new CallbackChooser(function (Experiment $experiment, array $variations) use ($user) { return $user->isBeta() ? $variations['variant'] : $variations['control']; }); $container = new Container($seed, $chooser);
Writing a custom chooser
use Mordilion\SplitTest\Chooser\ChooserInterface; use Mordilion\SplitTest\Model\Experiment; use Mordilion\SplitTest\Model\Experiment\Variation; final class TimeOfDayChooser implements ChooserInterface { public function choose(Experiment $experiment, array $variations): ?Variation { $key = ((int) date('G')) < 12 ? 'morning' : 'evening'; return $variations[$key] ?? null; } }
Selection Callbacks
Attach a callback to an experiment to fire analytics or tracking when a visitor is assigned. Callbacks only run while the experiment is enabled.
$experiment = new Experiment( name: 'pricing-page', enabled: true, callback: static function (Experiment $experiment, Variation $variation): void { track('experiment.assigned', [ 'experiment' => $experiment->getName(), 'variation' => $variation->getName(), ]); }, );
Persistence: cookies and headers
Container::deliver() serialises the current assignments and writes them as both a cookie and a response header, so downstream services (CDN, logs, analytics) can see which variations the visitor was assigned to.
$container->deliver( cookie: 'SplitTests', header: 'X-Split-Tests', );
On the next request, restore the container from the cookie:
$container = Container::fromString($_COOKIE['SplitTests'] ?? '', $seed);
Serialization format
Each experiment serializes to:
name:seed:enabled[:groups]=variation:weight[,variation:weight]...
Multiple experiments are pipe-separated:
homepage-hero:4711:1=a:50,b:50|checkout-button:815:1:b2b=enterprise-flow:100
Pass such a string back to Container::fromString() or Experiment::fromString() to reconstruct the objects.
Filtering experiments by group
// Experiments matching at least one of the given groups. $experiments = $container->getExperiments(['b2b']); // Experiments that match *all* of the given groups. $experiments = $container->getExperiments(['b2b', 'europe'], mustMatchAll: true);
Putting it all together
use Mordilion\SplitTest\Chooser\BalancedChooser; use Mordilion\SplitTest\Container; use Mordilion\SplitTest\Model\Experiment; use Mordilion\SplitTest\Model\Experiment\Group; use Mordilion\SplitTest\Model\Experiment\Variation; $seed = (int) ($_COOKIE['seed'] ?? random_int(1, PHP_INT_MAX)); $container = new Container($seed, new BalancedChooser()); $hero = new Experiment('homepage-hero', true); $hero->addVariation(new Variation('a', 50)); $hero->addVariation(new Variation('b', 50)); $container->addExperiment($hero); $cta = new Experiment('checkout-cta', true); $cta->addVariation(new Variation('control', 50)); $cta->addVariation(new Variation('variant', 50)); $cta->addGroup(new Group('b2b', [new Variation('enterprise', 100)])); $container->addExperiment($cta); $heroVariation = $container->getExperimentVariation('homepage-hero'); $ctaVariation = $container->getExperimentVariation('checkout-cta', groupName: $user->segment()); $container->deliver(); setcookie('seed', (string) $seed, time() + 30 * 86400);
Testing
composer install vendor/bin/phpunit
License
Released under the MIT License.
mordilion/split-test 适用场景与选型建议
mordilion/split-test 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 41.52k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2020 年 02 月 13 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 mordilion/split-test 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 mordilion/split-test 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 41.52k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 2
- 点击次数: 15
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2020-02-13