Skip to content

Commit c09009f

Browse files
oakbanialiabbasrizvi
authored andcommitted
feat (audience match types): Condition evaluator and Project Config PRs consolidation. (#150)
1 parent b0173f7 commit c09009f

15 files changed

+2205
-330
lines changed

src/Optimizely/ProjectConfig.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,15 @@ public function __construct($datafile, $logger, $errorHandler)
214214
$events = $config['events'] ?: [];
215215
$attributes = $config['attributes'] ?: [];
216216
$audiences = $config['audiences'] ?: [];
217+
$typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences']: [];
217218
$rollouts = isset($config['rollouts']) ? $config['rollouts'] : [];
218219
$featureFlags = isset($config['featureFlags']) ? $config['featureFlags']: [];
219220

220221
$this->_groupIdMap = ConfigParser::generateMap($groups, 'id', Group::class);
221222
$this->_experimentKeyMap = ConfigParser::generateMap($experiments, 'key', Experiment::class);
222223
$this->_eventKeyMap = ConfigParser::generateMap($events, 'key', Event::class);
223224
$this->_attributeKeyMap = ConfigParser::generateMap($attributes, 'key', Attribute::class);
225+
$typedAudienceIdMap = ConfigParser::generateMap($typedAudiences, 'id', Audience::class);
224226
$this->_audienceIdMap = ConfigParser::generateMap($audiences, 'id', Audience::class);
225227
$this->_rollouts = ConfigParser::generateMap($rollouts, null, Rollout::class);
226228
$this->_featureFlags = ConfigParser::generateMap($featureFlags, null, FeatureFlag::class);
@@ -249,12 +251,19 @@ public function __construct($datafile, $logger, $errorHandler)
249251
}
250252
}
251253

252-
$conditionDecoder = new ConditionDecoder();
253254
foreach (array_values($this->_audienceIdMap) as $audience) {
254-
$conditionDecoder->deserializeAudienceConditions($audience->getConditions());
255-
$audience->setConditionsList($conditionDecoder->getConditionsList());
255+
$audience->setConditionsList(json_decode($audience->getConditions(), true));
256256
}
257257

258+
// Conditions in typedAudiences are not expected to be string-encoded so they don't need
259+
// to be decoded unlike audiences.
260+
foreach (array_values($typedAudienceIdMap) as $typedAudience) {
261+
$typedAudience->setConditionsList($typedAudience->getConditions());
262+
}
263+
264+
// Overwrite audiences by typedAudiences.
265+
$this->_audienceIdMap = array_replace($this->_audienceIdMap, $typedAudienceIdMap);
266+
258267
$rolloutVariationIdMap = [];
259268
$rolloutVariationKeyMap = [];
260269
foreach ($this->_rollouts as $rollout) {

src/Optimizely/Utils/ConditionDecoder.php

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/Optimizely/Utils/ConditionEvaluator.php

Lines changed: 0 additions & 120 deletions
This file was deleted.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
/**
3+
* Copyright 2018, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace Optimizely\Utils;
19+
20+
class ConditionTreeEvaluator
21+
{
22+
const AND_OPERATOR = 'and';
23+
const OR_OPERATOR = 'or';
24+
const NOT_OPERATOR = 'not';
25+
26+
/**
27+
* Returns an array of supported operators.
28+
*
29+
* @return array List of operators.
30+
*/
31+
protected function getOperators()
32+
{
33+
return array(self::AND_OPERATOR, self::OR_OPERATOR, self::NOT_OPERATOR);
34+
}
35+
36+
/**
37+
* Returns corresponding evaluator method name for the given operator.
38+
*
39+
* @param mixed $operator Operator to get relevant evaluator method.
40+
*
41+
* @return string Corresponding method to the given operator.
42+
*/
43+
protected function getEvaluatorByOperatorType($operator)
44+
{
45+
$evaluatorsByOperator = array();
46+
$evaluatorsByOperator[self::AND_OPERATOR] = 'andEvaluator';
47+
$evaluatorsByOperator[self::OR_OPERATOR] = 'orEvaluator';
48+
$evaluatorsByOperator[self::NOT_OPERATOR] = 'notEvaluator';
49+
50+
return $evaluatorsByOperator[$operator];
51+
}
52+
53+
/**
54+
* Evaluates an array of conditions as if the evaluator had been applied
55+
* to each entry and the results AND-ed together.
56+
*
57+
* @param array $conditions Audience conditions list.
58+
* @param callable $leafEvaluator Method to evaluate leaf condition.
59+
*
60+
* @return null|boolean True if all the operands evaluate to true.
61+
* False if a single operand evaluates to false.
62+
* Null if conditions couldn't be evaluated.
63+
*/
64+
protected function andEvaluator(array $conditions, callable $leafEvaluator)
65+
{
66+
$sawNullResult = false;
67+
foreach ($conditions as $condition) {
68+
$result = $this->evaluate($condition, $leafEvaluator);
69+
70+
if($result === false) {
71+
return false;
72+
}
73+
74+
if($result === null) {
75+
$sawNullResult = true;
76+
}
77+
}
78+
79+
return $sawNullResult ? null : true;
80+
}
81+
82+
/**
83+
* Evaluates an array of conditions as if the evaluator had been applied
84+
* to each entry and the results OR-ed together.
85+
*
86+
* @param array $conditions Audience conditions list.
87+
* @param callable $leafEvaluator Method to evaluate leaf condition.
88+
*
89+
* @return null|boolean True if any operand evaluates to true.
90+
* False if all operands evaluate to false.
91+
* Null if conditions couldn't be evaluated.
92+
*/
93+
protected function orEvaluator(array $conditions, callable $leafEvaluator)
94+
{
95+
$sawNullResult = false;
96+
foreach ($conditions as $condition) {
97+
$result = $this->evaluate($condition, $leafEvaluator);
98+
99+
if($result === true) {
100+
return true;
101+
}
102+
103+
if($result === null) {
104+
$sawNullResult = true;
105+
}
106+
}
107+
108+
return $sawNullResult ? null : false;
109+
}
110+
111+
/**
112+
* Evaluates an array of conditions as if the evaluator had been applied
113+
* to a single entry and NOT was applied to the result.
114+
*
115+
* @param array $conditions Audience conditions list.
116+
* @param callable $leafEvaluator Method to evaluate leaf condition.
117+
*
118+
* @return null|boolean True if the operand evaluates to false.
119+
* False if the operand evaluates to true.
120+
* Null if conditions is empty or couldn't be evaluated.
121+
*/
122+
protected function notEvaluator(array $condition, callable $leafEvaluator)
123+
{
124+
if (empty($condition)) {
125+
return null;
126+
}
127+
128+
$result = $this->evaluate($condition[0], $leafEvaluator);
129+
return $result === null ? null: !$result;
130+
}
131+
132+
/**
133+
* Function to evaluate audience conditions against user's attributes.
134+
*
135+
* @param array $conditions Nested array of and/or/not conditions representing the audience conditions.
136+
* @param callable $leafEvaluator Method to evaluate leaf condition.
137+
*
138+
* @return null|boolean Result of evaluating the conditions using the operator rules.
139+
* and the leaf evaluator. Null if conditions couldn't be evaluated.
140+
*/
141+
public function evaluate($conditions, callable $leafEvaluator)
142+
{
143+
if (!Validator::doesArrayContainOnlyStringKeys($conditions)) {
144+
145+
if(in_array($conditions[0], $this->getOperators())) {
146+
$operator = array_shift($conditions);
147+
} else {
148+
$operator = self::OR_OPERATOR;
149+
}
150+
151+
$evaluatorFunc = $this->getEvaluatorByOperatorType($operator);
152+
return $this->{$evaluatorFunc}($conditions, $leafEvaluator);
153+
}
154+
155+
$leafCondition = $conditions;
156+
return $leafEvaluator($leafCondition);
157+
}
158+
}

0 commit comments

Comments
 (0)