Skip to content

Commit f982e8d

Browse files
oakbanimikeproeng37
authored andcommitted
Feature Toggle (#92)
1 parent 9a3072b commit f982e8d

File tree

7 files changed

+201
-37
lines changed

7 files changed

+201
-37
lines changed

src/Optimizely/Entity/Variation.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright 2016-2017, Optimizely
3+
* Copyright 2016-2018, Optimizely
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -47,10 +47,11 @@ class Variation
4747
private $_variableIdToVariableUsageInstanceMap;
4848

4949

50-
public function __construct($id = null, $key = null, $variableUsageInstances = [])
50+
public function __construct($id = null, $key = null, $featureEnabled = false, $variableUsageInstances = [])
5151
{
5252
$this->_id = $id;
5353
$this->_key = $key;
54+
$this->_featureEnabled = $featureEnabled;
5455

5556
$this->_variableUsageInstances = ConfigParser::generateMap($variableUsageInstances, null, VariableUsage::class);
5657

@@ -89,6 +90,22 @@ public function setKey($key)
8990
$this->_key = $key;
9091
}
9192

93+
/**
94+
* @return boolean featureEnabled property
95+
*/
96+
public function getFeatureEnabled()
97+
{
98+
return $this->_featureEnabled;
99+
}
100+
101+
/**
102+
* @param boolean $flag
103+
*/
104+
public function setFeatureEnabled($flag)
105+
{
106+
$this->_featureEnabled = $flag;
107+
}
108+
92109
/**
93110
* @return [VariableUsage] Variable usage instances in this variation
94111
*/

src/Optimizely/Optimizely.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,11 @@ public function isFeatureEnabled($featureFlagKey, $userId, $attributes = null)
512512
$experiment = $decision->getExperiment();
513513
$variation = $decision->getVariation();
514514

515+
if (is_null($variation) || !$variation->getFeatureEnabled()) {
516+
$this->_logger->log(Logger::INFO, "Feature Flag '{$featureFlagKey}' is not enabled for user '{$userId}'.");
517+
return false;
518+
}
519+
515520
if ($decision->getSource() == FeatureDecision::DECISION_SOURCE_EXPERIMENT) {
516521
$this->sendImpressionEvent($experiment->getKey(), $variation->getKey(), $userId, $attributes);
517522
} else {

tests/BucketerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public function testBucketValidExperimentInGroup()
208208
new Variation(
209209
'7722260071',
210210
'group_exp_1_var_1',
211+
true,
211212
[
212213
[
213214
"id" => "155563",
@@ -245,6 +246,7 @@ public function testBucketValidExperimentInGroup()
245246
new Variation(
246247
'7722360022',
247248
'group_exp_1_var_2',
249+
true,
248250
[
249251
[
250252
"id" => "155563",
@@ -375,6 +377,7 @@ public function testBucketVariationGroupedExperimentsWithBucketingId()
375377
new Variation(
376378
'7725250007',
377379
'group_exp_2_var_2',
380+
true,
378381
[
379382
[
380383
"id" => "155563",
@@ -408,6 +411,7 @@ public function testBucketWithRolloutRule()
408411
$expectedVariation = new Variation(
409412
'177778',
410413
'177778',
414+
true,
411415
[
412416
[
413417
"id"=> "155556",

tests/DecisionServiceTests/DecisionServiceTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public function testGetVariationReturnsWhitelistedVariationForGroupedExperiment(
150150
$expectedVariation = new Variation(
151151
'7722260071',
152152
'group_exp_1_var_1',
153+
true,
153154
[
154155
[
155156
"id" => "155563",

tests/OptimizelyTest.php

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,7 +2227,7 @@ public function testIsFeatureEnabledGivenInvalidFeatureFlag()
22272227
$this->assertSame($optimizelyObj->isFeatureEnabled('mutex_group_feature', "user_id"), false);
22282228
}
22292229

2230-
public function testIsFeatureEnabledGivenFeatureFlagIsNotEnabledForUser()
2230+
public function testIsFeatureEnabledGivenGetVariationForFeatureReturnsNull()
22312231
{
22322232
// should return false when no variation is returned for user
22332233
$optimizelyMock = $this->getMockBuilder(Optimizely::class)
@@ -2263,13 +2263,10 @@ public function testIsFeatureEnabledGivenFeatureFlagIsNotEnabledForUser()
22632263
->method('log')
22642264
->with(Logger::INFO, "Feature Flag 'double_single_variable_feature' is not enabled for user 'user_id'.");
22652265

2266-
$this->assertSame(
2267-
$optimizelyMock->isFeatureEnabled('double_single_variable_feature', 'user_id'),
2268-
false
2269-
);
2266+
$this->assertFalse($optimizelyMock->isFeatureEnabled('double_single_variable_feature', 'user_id'));
22702267
}
22712268

2272-
public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsBeingExperimented()
2269+
public function testIsFeatureEnabledGivenFeatureExperimentAndFeatureEnabledIsTrue()
22732270
{
22742271
$optimizelyMock = $this->getMockBuilder(Optimizely::class)
22752272
->setConstructorArgs(array($this->datafile, null, $this->loggerMock))
@@ -2289,6 +2286,9 @@ public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsBeingExper
22892286
$experiment = $this->projectConfig->getExperimentFromKey('test_experiment_double_feature');
22902287
$variation = $this->projectConfig->getVariationFromKey('test_experiment_double_feature', 'control');
22912288

2289+
// assert that featureEnabled for $variation is true
2290+
$this->assertTrue($variation->getFeatureEnabled());
2291+
22922292
$expected_decision = new FeatureDecision(
22932293
$experiment,
22942294
$variation,
@@ -2308,13 +2308,53 @@ public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsBeingExper
23082308
->method('log')
23092309
->with(Logger::INFO, "Feature Flag 'double_single_variable_feature' is enabled for user 'user_id'.");
23102310

2311-
$this->assertSame(
2312-
$optimizelyMock->isFeatureEnabled('double_single_variable_feature', 'user_id', []),
2313-
true
2311+
$this->assertTrue($optimizelyMock->isFeatureEnabled('double_single_variable_feature', 'user_id', []));
2312+
}
2313+
2314+
public function testIsFeatureEnabledGivenFeatureExperimentAndFeatureEnabledIsFalse()
2315+
{
2316+
$optimizelyMock = $this->getMockBuilder(Optimizely::class)
2317+
->setConstructorArgs(array($this->datafile, null, $this->loggerMock))
2318+
->setMethods(array('sendImpressionEvent'))
2319+
->getMock();
2320+
2321+
$decisionServiceMock = $this->getMockBuilder(DecisionService::class)
2322+
->setConstructorArgs(array($this->loggerMock, $this->projectConfig))
2323+
->setMethods(array('getVariationForFeature'))
2324+
->getMock();
2325+
2326+
$decisionService = new \ReflectionProperty(Optimizely::class, '_decisionService');
2327+
$decisionService->setAccessible(true);
2328+
$decisionService->setValue($optimizelyMock, $decisionServiceMock);
2329+
2330+
// Mock getVariationForFeature to return a valid decision with experiment and variation keys
2331+
$experiment = $this->projectConfig->getExperimentFromKey('test_experiment_double_feature');
2332+
$variation = $this->projectConfig->getVariationFromKey('test_experiment_double_feature', 'variation');
2333+
2334+
// assert that featureEnabled for $variation is false
2335+
$this->assertFalse($variation->getFeatureEnabled());
2336+
2337+
$expected_decision = new FeatureDecision(
2338+
$experiment,
2339+
$variation,
2340+
FeatureDecision::DECISION_SOURCE_EXPERIMENT
23142341
);
2342+
2343+
$decisionServiceMock->expects($this->exactly(1))
2344+
->method('getVariationForFeature')
2345+
->will($this->returnValue($expected_decision));
2346+
2347+
$optimizelyMock->expects($this->never())
2348+
->method('sendImpressionEvent');
2349+
2350+
$this->loggerMock->expects($this->at(0))
2351+
->method('log')
2352+
->with(Logger::INFO, "Feature Flag 'double_single_variable_feature' is not enabled for user 'user_id'.");
2353+
2354+
$this->assertFalse($optimizelyMock->isFeatureEnabled('double_single_variable_feature', 'user_id', []));
23152355
}
23162356

2317-
public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsNotBeingExperimented()
2357+
public function testIsFeatureEnabledGivenFeatureRolloutAndFeatureEnabledIsTrue()
23182358
{
23192359
$optimizelyMock = $this->getMockBuilder(Optimizely::class)
23202360
->setConstructorArgs(array($this->datafile, null, $this->loggerMock))
@@ -2334,6 +2374,10 @@ public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsNotBeingEx
23342374
$rollout = $this->projectConfig->getRolloutFromId('166660');
23352375
$experiment = $rollout->getExperiments()[0];
23362376
$variation = $experiment->getVariations()[0];
2377+
2378+
// assert variation's 'featureEnabled' is set to true
2379+
$this->assertTrue($variation->getFeatureEnabled());
2380+
23372381
$expected_decision = new FeatureDecision(
23382382
$experiment,
23392383
$variation,
@@ -2359,10 +2403,53 @@ public function testIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsNotBeingEx
23592403
->method('log')
23602404
->with(Logger::INFO, "Feature Flag 'boolean_single_variable_feature' is enabled for user 'user_id'.");
23612405

2362-
$this->assertSame(
2363-
$optimizelyMock->isFeatureEnabled('boolean_single_variable_feature', 'user_id', []),
2364-
true
2406+
$this->assertTrue($optimizelyMock->isFeatureEnabled('boolean_single_variable_feature', 'user_id', []));
2407+
}
2408+
2409+
public function testIsFeatureEnabledGivenFeatureRolloutAndFeatureEnabledIsFalse()
2410+
{
2411+
$optimizelyMock = $this->getMockBuilder(Optimizely::class)
2412+
->setConstructorArgs(array($this->datafile, null, $this->loggerMock))
2413+
->setMethods(array('sendImpressionEvent'))
2414+
->getMock();
2415+
2416+
$decisionServiceMock = $this->getMockBuilder(DecisionService::class)
2417+
->setConstructorArgs(array($this->loggerMock, $this->projectConfig))
2418+
->setMethods(array('getVariationForFeature'))
2419+
->getMock();
2420+
2421+
$decisionService = new \ReflectionProperty(Optimizely::class, '_decisionService');
2422+
$decisionService->setAccessible(true);
2423+
$decisionService->setValue($optimizelyMock, $decisionServiceMock);
2424+
2425+
// Mock getVariationForFeature to return a valid decision with experiment and variation keys
2426+
$rollout = $this->projectConfig->getRolloutFromId('166660');
2427+
$experiment = $rollout->getExperiments()[0];
2428+
$variation = $experiment->getVariations()[0];
2429+
$variation->setFeatureEnabled(false);
2430+
2431+
// assert variation's 'featureEnabled' is set to false
2432+
$this->assertFalse($variation->getFeatureEnabled());
2433+
2434+
$expected_decision = new FeatureDecision(
2435+
$experiment,
2436+
$variation,
2437+
FeatureDecision::DECISION_SOURCE_ROLLOUT
23652438
);
2439+
2440+
$decisionServiceMock->expects($this->exactly(1))
2441+
->method('getVariationForFeature')
2442+
->will($this->returnValue($expected_decision));
2443+
2444+
// assert that sendImpressionEvent is not called
2445+
$optimizelyMock->expects($this->never())
2446+
->method('sendImpressionEvent');
2447+
2448+
$this->loggerMock->expects($this->at(0))
2449+
->method('log')
2450+
->with(Logger::INFO, "Feature Flag 'boolean_single_variable_feature' is not enabled for user 'user_id'.");
2451+
2452+
$this->assertFalse($optimizelyMock->isFeatureEnabled('boolean_single_variable_feature', 'user_id', []));
23662453
}
23672454

23682455
public function testGetEnabledFeaturesGivenInvalidDataFile()

tests/ProjectConfigTest.php

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright 2016-2017, Optimizely
3+
* Copyright 2016-2018, Optimizely
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@
4141
use Optimizely\Logger\NoOpLogger;
4242
use Optimizely\Optimizely;
4343
use Optimizely\ProjectConfig;
44+
use Optimizely\Utils\ConfigParser;
4445

4546
class ProjectConfigTest extends \PHPUnit_Framework_TestCase
4647
{
@@ -309,17 +310,47 @@ public function testInit()
309310
);
310311

311312

312-
// Check variable usage
313+
// Check variation entity
313314
$variableUsages = [
314315
new VariableUsage("155560", "F"),
315316
new VariableUsage("155561", "red")
316317
];
317-
$expectedVariation = new Variation("122231", "Fred", $variableUsages);
318+
$expectedVariation = new Variation("122231", "Fred", true, $variableUsages);
318319
$actualVariation = $this->config->getVariationFromKey("test_experiment_multivariate", "Fred");
319320

320321
$this->assertEquals($expectedVariation, $actualVariation);
321322
}
322323

324+
public function testVariationParsingWithoutFeatureEnabledProp()
325+
{
326+
$variables = [
327+
[
328+
"id"=> "155556",
329+
"value"=> "true"
330+
]
331+
];
332+
333+
$data = [
334+
[
335+
"id"=> "177771",
336+
"key"=> "my_var",
337+
"variables"=> $variables
338+
]
339+
];
340+
341+
$variationIdMap = ConfigParser::generateMap($data, 'id', Variation::class);
342+
343+
$variation = $variationIdMap["177771"];
344+
$this->assertEquals("177771", $variation->getId());
345+
$this->assertEquals("my_var", $variation->getKey());
346+
347+
$variableUsageMap = ConfigParser::generateMap($variables, null, VariableUsage::class);
348+
$this->assertEquals($variableUsageMap, $variation->getVariables());
349+
350+
// assert featureEnabled by default is set to false when property not provided in data file
351+
$this->assertFalse($variation->getFeatureEnabled());
352+
}
353+
323354
public function testGetAccountId()
324355
{
325356
$this->assertEquals('1592310167', $this->config->getAccountId());

0 commit comments

Comments
 (0)