Growth Book - PHP

Powerful A/B testing for PHP.

Build Status

  • No external dependencies
  • Lightweight and fast
  • No HTTP requests everything is defined and evaluated locally
  • PHP 7.1+ with 100% test coverage and phpstan on the highest level
  • Advanced user and page targeting
  • Use your existing event tracking (GA, Segment, Mixpanel, custom)
  • Adjust variation weights and targeting without deploying new code

Installation

Growth Book is available on Composer:

composer require growthbook/growthbook

Quick Usage

$client = new Growthbook\Client();

// Define the user that you want to run an experiment on
$user = $client->user(["id"=>"12345"]);

// Define the experiment
$experiment = new Growthbook\Experiment("my-experiment", ["A", "B"]);

// Put the user in the experiment
$result = $user->experiment($experiment);

echo $result->value; // "A" or "B"

At the end of the request, you would track all of the viewed experiments in your analytics tool or database (Segment, GA, Mixpanel, etc.):

$impressions = $client->getViewedExperiments();
foreach($impressions as $impression) {
  // Whatever you use for event tracking
  Segment::track([
    "userId" => $impression->userId,
    "event" => "Experiment Viewed",
    "properties" => [
      "experimentId" => $impression->experiment->key,
      "variationId" => $impression->result->variationId
    ]
  ])
}

Experiments

As shown above, the simplest experiment you can define has an id and an array of variations.

There is an optional 3rd argument, which is an associative array of additional options:

  • weights (float[]) - How to weight traffic between variations. Must add to 1 and be the same length as the number of variations.
  • status (string) - "running" is the default and always active. "draft" is only active during QA and development. "stopped" is only active when forcing a winning variation to 100% of users.
  • coverage (float) - What percent of users should be included in the experiment (between 0 and 1, inclusive)
  • url (string) - Users can only be included in this experiment if the current URL matches this regex
  • groups (string[]) - User groups that should be included in the experiment (e.g. internal employees, qa testers)
  • force (int) - All users included in the experiment will be forced into the specific variation index
  • randomizationUnit (string) - The type of user id to use for variation assignment. Defaults to id.

Running Experiments

Run experiments by calling $user->experiment() which returns an object with a few useful properties:

$result = $user->experiment(new Growthbook\Experiment(
  "my-experiment", ["A", "B"]
);

// If user is part of the experiment
echo $result->inExperiment; // true or false

// The index of the assigned variation
echo $result->variationId; // 0 or 1

// The value of the assigned variation
echo $result->value; // "A" or "B"

The inExperiment flag can be false if the experiment defines any sort of targeting rules which the user does not pass. In this case, the user is always assigned variation index 0 and the first variation value.

Client Configuration

The Growthbook\Client constructor takes an optional config arugment:

$config = new Growthbook\Config([
  // options go here
]);
$client = new Growthbook\Client($config);

The Growthbook\Config constructor takes an associative array of options. Below are all of the available options currently:

  • enabled - Default true. Set to false to completely disable all experiments.
  • logger - An optional psr-3 logger instance
  • url - The url of the page (defaults to $_SERVER['REQUEST_URL'] if not set)
  • enableQueryStringOverride - Default false. If true, enables forcing variations via the URL. Very useful for QA. https://example.com/?my-experiment=1

You can change configuration options at any time by setting properties directly:

$client->config->enabled = false;

User Configuration

The $client->user method takes two arguments: $ids and $groups (optional).

$user = $client->user([
  // Any user id you want to randomize an experiment by
  "id" => "123",
  "anonId" => "abc",
  "companyId" => "456",
], [
  // Any groups the user is a part of
  "internal" => true,
  "beta" => true,
  "qa" => false
]);

Randomization Units

On an experiment-by-experiment basis, you can choose which user id you want to use for randomization.

Most of the time, you'll want to use id if your experiment is for logged-in users only or anonId if your experiment includes any logged out users.

For some experiments, you may need other units like companyId, childId, geoRegion, etc.

new Experiment("company-test", ["A", "B"], [
  "randomizationUnit" => "companyId"
]);
// Now all users in the same company will see the same variation

Groups

Experiments can specify a list of user groups which are allowed in the experiment. If a user is not a member of one of the groups listed, they will be excluded.

$result = $user->experiment(
    "my-targeted-experiment",
    ["A", "B"],
    [
      "groups" => ["beta", "qa"]
    ]
]);

In the above example, if the user is not in either beta or qa, then $result->inExperiment will be false and they will be assigned variation index 0.

Overriding Weights and Targeting

It's common practice to adjust experiment settings after a test is live. For example, slowly ramping up traffic, stopping a test automatically if guardrail metrics go down, or rolling out a winning variation to 100% of users.

Instead of constantly changing your code, you can use client overrides. For example, to roll out a winning variation to 100% of users:

$client->overrides->set("experiment-key", [
    "status" => 'stopped',
    // Force variation index 1
    "force" => 1
]);

The full list of experiment properties you can override is:

  • status
  • force
  • weights
  • coverage
  • groups
  • url

This data structure can be easily seralized and stored in a database or returned from an API. There is a small helper function if you have all of your overrides in a single JSON object:

$json = '{
  "experiment-key-1": {
    "status": "stopped"
  },
  "experiment-key-2": {
    "weights": [0.8, 0.2]
  }
}';

// Convert to associative array
$overrides = json_decode($json, true);

// Import into the client
$client->importOverrides($overrides);

Tracking

It's likely you already have some event tracking on your site with the metrics you want to optimize (Google Analytics, Segment, Mixpanel, etc.).

For A/B tests, you just need to track one additional event - when someone views a variation.

You can call $client->getViewedExperiments() at the end of a request to forward to your analytics tool of choice.

$impressions = $client->getViewedExperiments();
foreach($impressions as $impression) {
  // Whatever you use for event tracking
  Segment::track([
    "userId" => $impression->userId,
    "event" => "Experiment Viewed",
    "properties" => [
      "experimentId" => $impression->experiment->key,
      "variationId" => $impression->result->variationId
    ]
  ])
}

Each impression object has the following properties:

  • experiment (the full experiment object)
  • result (the result of the $user->experiment call)
  • userId (the id used to randomize the experiment result)

Often times you'll want to do the event tracking from the front-end with javascript. To do this, simply add a block to your template (shown here in plain PHP, but similar idea for Twig, Blade, etc.).

<script>
<?php foreach($client->getViewedExperiments() as $impression): ?>
  // tracking code goes here
<?php endforeach; ?>
</script>

Below are examples for a few popular front-end tracking libraries:

Google Analytics

ga('send', 'event', 'experiment', 
  "<?= $impression->experiment->key ?>", 
  "<?= $impression->result->variationId ?>", 
  {
    // Custom dimension for easier analysis
    'dimension1': "<?= 
      $impression->experiment->key.':'.$impression->result->variationId 
    ?>"
  }
);

Segment

analytics.track("Experiment Viewed", <?=json_encode([
  "experimentId" => $impression->experiment->key,
  "variationId" => $impression->result->variationId 
])?>);

Mixpanel

mixpanel.track("Experiment Viewed", <?=json_encode([
  'Experiment name' => $impression->experiment->key,
  'Variant name' => $impression->result->variationId 
])?>);

Analysis

For analysis, there are a few options:

  • Online A/B testing calculators
  • Built-in A/B test analysis in Mixpanel/Amplitude
  • Python or R libraries and a Jupyter Notebook
  • The Growth Book App (more info below)

The Growth Book App

Managing experiments and analyzing results at scale can be complicated, which is why we built the open source Growth Book App - https://github.io/growthbook/growthbook. It's completely optional, but definitely worth checking out.

  • Query multiple data sources (Snowflake, Redshift, BigQuery, Mixpanel, Postgres, Athena, and Google Analytics)
  • Bayesian statistics engine with support for binomial, count, duration, and revenue metrics
  • Drill down into A/B test results (e.g. by browser, country, etc.)
  • Lightweight idea board and prioritization framework
  • Document everything! (upload screenshots, add markdown comments, and more)
  • Automated email alerts when tests become significant

Integration is super easy:

  1. Create a Growth Book API key
  2. Periodically fetch the latest experiment overrides from the API and cache in Redis, Mongo, etc.
  3. At the start of your app, run $client->importOverrides($listFromCache);

Now you can start/stop tests, adjust coverage and variation weights, and apply a winning variation to 100% of traffic, all within the Growth Book App without deploying code changes to your site.