AB Test
Ik ben bezig met een eigen project (https://www.pozzy.nl). En van daaruit kreeg ik de behoefte om verschillende versies van dezelfde website te serveren om de (business) performance van de verschillen te kunnen meten. Er bestaan al een aantal libraries die A/B testing implementeren en ik raad het iedereen aan om die libraries te onderzoeken en eventueel te gebruiken als het voldoet aan jou specifieke wensen. Deze library heb ik zelf geschreven. Voor mijzelf was het meer een stukje zelfstudie geweest om een A/B testing functionaliteit te bouwen. Een aantal wensen waren voor mij persoonlijk belangrijk: - Moet compact zijn - Ongelimiteerde aantal test varianten kunnen uitserveren - Makkelijk website variatie toe voegen - Gekozen A/B test moet persistent kunnen zijn - A/B testing lib moet niks afweten van de rest van de webapplicatie en dus niet intrusive zijn - De verschillende test varianten van de website moeten onafhankelijk van elkaar kunnen bestaan, maar mag ook elkaars code includen indien nodig/nuttig - Kunnen injecteren van zelf geschreven A/B select functie (moet uiteindelijk meer kunnen dan 50/50 serveren) De ini-file AB.ini die door de AB.php wordt gebruikt ziet er als volgt uit en verwacht dat deze op dezelfde locatie staat: 0=default 1=UA-XXXXXXXX-X Mijn project en dit voorbeeld van de A/B testing lib verwacht de volgende directory structuur. //includes/lib/AB.php //includes/templates/$ab/.php"); Bijgevoegde scripts zijn AB.php en index.php. De attachement index.php staat in en is waar de website zal "beginnen". index.php include de AB.php. AB.php bevat de singleton-class genaamd AB. index.php zal na de include de volgende functie aanroepen om de geselecteerde test te verkrijgen: $ab = AB::Instance()->getAb(); $ab is een String welke gebruikt wordt om een variabel deel in een pad naar de website te vullen. include_once("includes/templates/$ab/index.php"); Uitgaande van voorbeeld ini-file kan je verschillende versies van je website hebben op de volgende locaties: default: //includes/templates/default/index.php UA-XXXXXXXX-X: //includes/templates/UA-XXXXXXXX-X/index.php Door meer entries toe te voegen aan de ini-file voeg je een extra variant toe. De functie setAbSelectFunction in de class AB kan je gebruiken om een eigen A/B select functie te gebruiken in plaats van de default die ik heb meegeleverd. Het nut hiervan is dat je bijvoorbeeld verschillende websites kan uitserveren aan de hand van taal, geo-locatie, device type etc. De mogelijkheden reiken zover als je eigen fantasie.
<?php
if(!isset($_SESSION)) {
session_start();
}
/**
* A/B Testing.
*/
include_once("includes/lib/AB.php");
$ab = AB::Instance()->getAb();
/**
* Include the template selected by A/B test.
*/
include_once("includes/templates/$ab/index.php");
?>
<?php
if(!isset($_SESSION)) {
session_start();
}
/**
* Singleton AB class
*/
final class AB
{
private $abSelectFunction = "abSelectDefault";
/**
* Call this method to get singleton
*
* @return UserFactory
*/
public static function Instance()
{
static $inst = null; // PHP needs this to be set here.
if ($inst === null) {
$inst = new AB();
}
return $inst;
}
/**
* getAb returns the A/B test name.
* The test selection is determined by the abSelect function.
* You can provide your own abTest function by creating a globally accessible
* function and pass the name of that function to the setAbSelectFunction.
* @return the A/B test name.
*/
public function getAb() {
$ab = "default";
if(strlen($this->abSelectFunction) > 0) {
$ini_array = parse_ini_file(dirname(__FILE__) . "/AB.ini", true);
$func = $this->abSelectFunction;
$ab = $func($ini_array["Sites"]);
}
return $ab;
}
/**
* setAbSelectFunction can be used to set your own
* A/B test selection function.
* @param $functionName name of your function which does the selecting.
*/
public function setAbSelectFunction($functionName) {
$this->abSelectFunction = $functionName;
}
/**
* getAbSelectFunction returns the name of the A/B test selection function.
* @return name of the A/B test selection function.
*/
public function getAbSelectFunction() {
return $this->abSelectFunction;
}
/**
* Private constructor
*/
private function __construct()
{
// Use Instance()
}
/**
* Private clone
*/
private function __clone()
{
// Me not like clones! Me smash clones!
}
}
/**
* abSelectDefault generates a random outcome with the given input.
* The input is an array.
* The output is an element from that array.
* The test is stored in as a session variable and is reused during the session lifetime.
*/
function abSelectDefault($array) {
$ab = $array[mt_rand(0, (sizeof($array)-1))];
// Check if AB cookie has been set. If set then it overrides the session value.
// otherwise we use the session value if that has been set.
if(isset($_COOKIE["ab"]) && strlen($_COOKIE["ab"]) > 0) {
$_SESSION["ab"] = $_COOKIE["ab"];
$ab = $_SESSION["ab"];
}
else if(isset($_SESSION["ab"]) && strlen($_SESSION["ab"]) > 0) {
$ab = $_SESSION["ab"];
}
// Check for manual A/B test passed by URI. Don't allow path traversal characters.
if(isset($_REQUEST["ab"]) && strlen($_REQUEST["ab"]) > 0 && preg_match("/(\.|\/)/", $_REQUEST["ab"]) == false) {
$ab = $_REQUEST["ab"];
}
$_SESSION["ab"] = $ab;
setcookie("ab", $_SESSION["ab"], time()+(10 * 365 * 24 * 3600));
return $ab;
}
?>
Reacties
0