mordilion/split-test 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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, Static and Callback choosers, or implement your own via ChooserInterface.
  • 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 helperContainer::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 我们能提供哪些服务?
定制开发 / 二次开发

基于 mordilion/split-test 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 41.52k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 15
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 2
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2020-02-13