本文目录导读:

我来详细说明如何用PHP项目实现多变量测试(Multivariate Testing,MVT)。
多变量测试的基本概念
多变量测试是同时测试多个页面元素的多个变体,以找到最佳组合的测试方法。
基础实现方案
1 定义测试变量和变体
<?php
// config/mvt-config.php
// 定义测试配置
$mvtConfig = [
'homepage_hero' => [
'enabled' => true,
'name' => '首页横幅测试',
'metrics' => ['click_rate', 'conversion_rate'],
'variables' => [
'headline' => [
'variants' => [
'A' => 'Welcome to Our Platform',
'B' => 'Transform Your Business Today',
'C' => 'Get Started for Free'
],
'weight' => [33, 33, 34] // 权重分配
],
'button_color' => [
'variants' => [
'A' => 'blue',
'B' => 'green',
'C' => 'red'
],
'weight' => [33, 33, 34]
],
'cta_text' => [
'variants' => [
'A' => 'Sign Up Now',
'B' => 'Get Started',
'C' => 'Try Free'
],
'weight' => [33, 33, 34]
]
]
]
];
2 核心引擎实现
<?php
// src/MVTEngine.php
class MVTEngine {
private $config;
private $db;
private $userId;
private $variants = [];
public function __construct($config, $db) {
$this->config = $config;
$this->db = $db;
$this->userId = $this->getUserId();
}
/**
* 获取用户分配的实验版本
*/
public function assignVariants($testName) {
if (!isset($this->config[$testName]) || !$this->config[$testName]['enabled']) {
return $this->getDefaultVariants($testName);
}
// 检查用户是否已有分配
$existing = $this->getExistingAssignment($testName);
if ($existing) {
return json_decode($existing['variants'], true);
}
// 为每个变量分配变体
$test = $this->config[$testName];
$assignedVariants = [];
foreach ($test['variables'] as $varName => $variable) {
$assignedVariants[$varName] = $this->getWeightedVariant(
$variable['variants'],
$variable['weight'],
$testName . '_' . $varName
);
}
// 保存分配
$this->saveAssignment($testName, $assignedVariants);
return $assignedVariants;
}
/**
* 加权随机选择变体
*/
private function getWeightedVariant($variants, $weights, $seed) {
$variantKeys = array_keys($variants);
$totalWeight = array_sum($weights);
$random = mt_rand(1, $totalWeight);
$cumulativeWeight = 0;
foreach ($variantKeys as $index => $key) {
$cumulativeWeight += $weights[$index];
if ($random <= $cumulativeWeight) {
return $key;
}
}
return $variantKeys[0];
}
/**
* 获取用户ID
*/
private function getUserId() {
// 使用session或cookie中的用户标识
if (!isset($_SESSION['user_id'])) {
$_SESSION['user_id'] = uniqid('user_', true);
}
return $_SESSION['user_id'];
}
/**
* 保存测试分配
*/
private function saveAssignment($testName, $variants) {
$sql = "INSERT INTO mvt_assignments (user_id, test_name, variants, assigned_at)
VALUES (?, ?, ?, NOW())";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$this->userId,
$testName,
json_encode($variants)
]);
}
/**
* 获取已存在的分配
*/
private function getExistingAssignment($testName) {
$sql = "SELECT variants FROM mvt_assignments
WHERE user_id = ? AND test_name = ?
ORDER BY assigned_at DESC LIMIT 1";
$stmt = $this->db->prepare($sql);
$stmt->execute([$this->userId, $testName]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 获取默认变体
*/
private function getDefaultVariants($testName) {
$defaults = [];
foreach ($this->config[$testName]['variables'] as $varName => $variable) {
$defaults[$varName] = array_key_first($variable['variants']);
}
return $defaults;
}
}
3 数据追踪类
<?php
// src/MVTTracker.php
class MVTTracker {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 记录事件
*/
public function trackEvent($testName, $eventName, $userId, $variants) {
$sql = "INSERT INTO mvt_events (test_name, event_name, user_id, variants, created_at)
VALUES (?, ?, ?, ?, NOW())";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$testName,
$eventName,
$userId,
json_encode($variants)
]);
}
/**
* 记录转化
*/
public function trackConversion($testName, $userId, $variants, $conversionValue = null) {
$sql = "INSERT INTO mvt_conversions (test_name, user_id, variants, conversion_value, created_at)
VALUES (?, ?, ?, ?, NOW())";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$testName,
$userId,
json_encode($variants),
$conversionValue
]);
}
}
4 前端集成示例
<?php
// templates/homepage.php
// 初始化MVT引擎
$mvtEngine = new MVTEngine($mvtConfig, $db);
$variants = $mvtEngine->assignVariants('homepage_hero');
// 获取当前页面的变体内容
$headline = $mvtConfig['homepage_hero']['variables']['headline']['variants'][$variants['headline']];
$buttonColor = $mvtConfig['homepage_hero']['variables']['button_color']['variants'][$variants['button_color']];
$ctaText = $mvtConfig['homepage_hero']['variables']['cta_text']['variants'][$variants['cta_text']];
?>
<!DOCTYPE html>
<html>
<head>MVT Test Page</title>
<style>
.cta-button {
background-color: <?php echo htmlspecialchars($buttonColor); ?>;
color: white;
}
</style>
</head>
<body>
<div class="hero-section">
<h1><?php echo htmlspecialchars($headline); ?></h1>
<button class="cta-button" onclick="trackEvent('click')">
<?php echo htmlspecialchars($ctaText); ?>
</button>
</div>
<script>
// 追踪数据
const testData = {
testName: 'homepage_hero',
userId: '<?php echo $userId; ?>',
variants: <?php echo json_encode($variants); ?>
};
// 追踪页面浏览
fetch('/api/track.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
...testData,
event: 'page_view'
})
});
// 追踪按钮点击
function trackEvent(eventName) {
fetch('/api/track.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
...testData,
event: eventName
})
}).then(() => {
// 可以添加转化追踪逻辑
if (eventName === 'signup_complete') {
trackConversion();
}
});
}
function trackConversion() {
fetch('/api/conversion.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
...testData,
conversionValue: 100 // 转化价值
})
});
}
</script>
</body>
</html>
数据库表结构
-- 分配记录表
CREATE TABLE mvt_assignments (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
test_name VARCHAR(100) NOT NULL,
variants JSON NOT NULL,
assigned_at DATETIME NOT NULL,
INDEX idx_user_test (user_id, test_name)
);
-- 事件追踪表
CREATE TABLE mvt_events (
id INT AUTO_INCREMENT PRIMARY KEY,
test_name VARCHAR(100) NOT NULL,
event_name VARCHAR(100) NOT NULL,
user_id VARCHAR(255) NOT NULL,
variants JSON NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_test_event (test_name, event_name, created_at)
);
-- 转化记录表
CREATE TABLE mvt_conversions (
id INT AUTO_INCREMENT PRIMARY KEY,
test_name VARCHAR(100) NOT NULL,
user_id VARCHAR(255) NOT NULL,
variants JSON NOT NULL,
conversion_value DECIMAL(10,2) DEFAULT NULL,
created_at DATETIME NOT NULL,
INDEX idx_test_user (test_name, user_id)
);
结果分析类
<?php
// src/MVTResultsAnalyzer.php
class MVTResultsAnalyzer {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 获取测试结果统计
*/
public function getTestResults($testName) {
// 获取所有变体组合的统计数据
$sql = "SELECT
e.variants,
COUNT(DISTINCT e.user_id) as visitors,
COUNT(DISTINCT c.user_id) as conversions,
COUNT(c.user_id) / COUNT(DISTINCT e.user_id) * 100 as conversion_rate
FROM mvt_events e
LEFT JOIN mvt_conversions c
ON e.test_name = c.test_name
AND e.user_id = c.user_id
WHERE e.test_name = ?
AND e.event_name = 'page_view'
GROUP BY e.variants
ORDER BY conversion_rate DESC";
$stmt = $this->db->prepare($sql);
$stmt->execute([$testName]);
$results = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$variants = json_decode($row['variants'], true);
$results[] = [
'variants' => $variants,
'visitors' => $row['visitors'],
'conversions' => $row['conversions'],
'conversion_rate' => round($row['conversion_rate'], 2)
];
}
return $results;
}
/**
* 获取每个变量的表现
*/
public function getVariablePerformance($testName) {
$config = $this->getTestConfig($testName);
$variablePerformance = [];
foreach ($config['variables'] as $varName => $variable) {
$performance = [];
foreach ($variable['variants'] as $variantKey => $variantValue) {
$sql = "SELECT
COUNT(DISTINCT e.user_id) as visitors,
COUNT(DISTINCT c.user_id) as conversions
FROM mvt_events e
LEFT JOIN mvt_conversions c
ON e.test_name = c.test_name
AND e.user_id = c.user_id
WHERE e.test_name = ?
AND JSON_EXTRACT(e.variants, '$.\"$varName\"') = ?
AND e.event_name = 'page_view'";
$stmt = $this->db->prepare($sql);
$stmt->execute([$testName, $variantKey]);
$data = $stmt->fetch(PDO::FETCH_ASSOC);
$performance[$variantKey] = [
'value' => $variantValue,
'visitors' => $data['visitors'],
'conversions' => $data['conversions'],
'conversion_rate' => $data['visitors'] > 0 ?
round($data['conversions'] / $data['visitors'] * 100, 2) : 0
];
}
$variablePerformance[$varName] = $performance;
}
return $variablePerformance;
}
/**
* 统计显著性检查(简化版)
*/
public function calculateSignificance($testName) {
$results = $this->getTestResults($testName);
if (count($results) < 2) {
return null;
}
// 以第一个结果为基准
$baseline = $results[0];
$significance = [];
for ($i = 1; $i < count($results); $i++) {
$variant = $results[$i];
$zScore = $this->calculateZScore(
$baseline['conversion_rate'] / 100,
$variant['conversion_rate'] / 100,
$baseline['visitors'],
$variant['visitors']
);
$significance[$i] = [
'variants' => $variant['variants'],
'z_score' => round($zScore, 2),
'p_value' => $this->calculatePValue($zScore)
];
}
return $significance;
}
private function calculateZScore($p1, $p2, $n1, $n2) {
$p = ($p1 * $n1 + $p2 * $n2) / ($n1 + $n2);
$se = sqrt($p * (1 - $p) * (1/$n1 + 1/$n2));
return ($p1 - $p2) / $se;
}
private function calculatePValue($zScore) {
// 简化实现,实际应用应使用更精确的算法
return 2 * (1 - 0.5 * (1 + erf($zScore / sqrt(2))));
}
}
高级优化建议
1 缓存机制
<?php
// 使用Redis缓存用户分配
class MVTEngineWithCache extends MVTEngine {
private $redis;
public function assignVariants($testName) {
$cacheKey = "mvt:{$this->userId}:{$testName}";
// 检查缓存
$cached = $this->redis->get($cacheKey);
if ($cached) {
return json_decode($cached, true);
}
// 调用父类方法
$variants = parent::assignVariants($testName);
// 设置缓存(24小时过期)
$this->redis->setex($cacheKey, 86400, json_encode($variants));
return $variants;
}
}
2 流量分配控制
<?php
// 使用MurmurHash实现一致性分配
function consistentAssignment($userId, $testName, $totalVariants) {
$hash = hash('murmur3f', $userId . '_' . $testName);
// 将hash转换为整数
$intHash = hexdec(substr($hash, 0, 8));
return $intHash % $totalVariants;
}
3 A/B/n 测试支持
<?php
// 扩展以支持A/B/n测试(多变量测试的特例)
class ABnTestEngine extends MVTEngine {
public function assignVariant($testName) {
if ($this->getExistingAssignment($testName)) {
return parent::assignVariants($testName);
}
// 简化为单一变量测试
return parent::assignVariants($testName);
}
}
最佳实践
- 性能优化:使用缓存减少数据库查询
- 防止污染:确保用户始终看到相同的变体组合
- 数据质量:过滤机器人流量和无效数据
- 实时监控:监控测试效果,及时发现异常
- 渐进式部署:先小流量测试,逐步扩大
这个实现方案提供了完整的多变量测试功能,可以根据实际需求进行调整和扩展。