Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/Optimizely/ProjectConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,15 @@ public function __construct($datafile, $logger, $errorHandler)
$events = $config['events'] ?: [];
$attributes = $config['attributes'] ?: [];
$audiences = $config['audiences'] ?: [];
$typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences']: [];
$rollouts = isset($config['rollouts']) ? $config['rollouts'] : [];
$featureFlags = isset($config['featureFlags']) ? $config['featureFlags']: [];

$this->_groupIdMap = ConfigParser::generateMap($groups, 'id', Group::class);
$this->_experimentKeyMap = ConfigParser::generateMap($experiments, 'key', Experiment::class);
$this->_eventKeyMap = ConfigParser::generateMap($events, 'key', Event::class);
$this->_attributeKeyMap = ConfigParser::generateMap($attributes, 'key', Attribute::class);
$typedAudienceIdMap = ConfigParser::generateMap($typedAudiences, 'id', Audience::class);
$this->_audienceIdMap = ConfigParser::generateMap($audiences, 'id', Audience::class);
$this->_rollouts = ConfigParser::generateMap($rollouts, null, Rollout::class);
$this->_featureFlags = ConfigParser::generateMap($featureFlags, null, FeatureFlag::class);
Expand Down Expand Up @@ -249,12 +251,19 @@ public function __construct($datafile, $logger, $errorHandler)
}
}

$conditionDecoder = new ConditionDecoder();
foreach (array_values($this->_audienceIdMap) as $audience) {
$conditionDecoder->deserializeAudienceConditions($audience->getConditions());
$audience->setConditionsList($conditionDecoder->getConditionsList());
$audience->setConditionsList(json_decode($audience->getConditions(), true));
}

// Conditions in typedAudiences are not expected to be string-encoded so they don't need
// to be decoded unlike audiences.
foreach (array_values($typedAudienceIdMap) as $typedAudience) {
$typedAudience->setConditionsList($typedAudience->getConditions());
}

// Overwrite audiences by typedAudiences.
$this->_audienceIdMap = array_replace($this->_audienceIdMap, $typedAudienceIdMap);

$rolloutVariationIdMap = [];
$rolloutVariationKeyMap = [];
foreach ($this->_rollouts as $rollout) {
Expand Down
44 changes: 0 additions & 44 deletions src/Optimizely/Utils/ConditionDecoder.php

This file was deleted.

120 changes: 0 additions & 120 deletions src/Optimizely/Utils/ConditionEvaluator.php

This file was deleted.

158 changes: 158 additions & 0 deletions src/Optimizely/Utils/ConditionTreeEvaluator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php
/**
* Copyright 2018, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Optimizely\Utils;

class ConditionTreeEvaluator
{
const AND_OPERATOR = 'and';
const OR_OPERATOR = 'or';
const NOT_OPERATOR = 'not';

/**
* Returns an array of supported operators.
*
* @return array List of operators.
*/
protected function getOperators()
{
return array(self::AND_OPERATOR, self::OR_OPERATOR, self::NOT_OPERATOR);
}

/**
* Returns corresponding evaluator method name for the given operator.
*
* @param mixed $operator Operator to get relevant evaluator method.
*
* @return string Corresponding method to the given operator.
*/
protected function getEvaluatorByOperatorType($operator)
{
$evaluatorsByOperator = array();
$evaluatorsByOperator[self::AND_OPERATOR] = 'andEvaluator';
$evaluatorsByOperator[self::OR_OPERATOR] = 'orEvaluator';
$evaluatorsByOperator[self::NOT_OPERATOR] = 'notEvaluator';

return $evaluatorsByOperator[$operator];
}

/**
* Evaluates an array of conditions as if the evaluator had been applied
* to each entry and the results AND-ed together.
*
* @param array $conditions Audience conditions list.
* @param callable $leafEvaluator Method to evaluate leaf condition.
*
* @return null|boolean True if all the operands evaluate to true.
* False if a single operand evaluates to false.
* Null if conditions couldn't be evaluated.
*/
protected function andEvaluator(array $conditions, callable $leafEvaluator)
{
$sawNullResult = false;
foreach ($conditions as $condition) {
$result = $this->evaluate($condition, $leafEvaluator);

if($result === false) {
return false;
}

if($result === null) {
$sawNullResult = true;
}
}

return $sawNullResult ? null : true;
}

/**
* Evaluates an array of conditions as if the evaluator had been applied
* to each entry and the results OR-ed together.
*
* @param array $conditions Audience conditions list.
* @param callable $leafEvaluator Method to evaluate leaf condition.
*
* @return null|boolean True if any operand evaluates to true.
* False if all operands evaluate to false.
* Null if conditions couldn't be evaluated.
*/
protected function orEvaluator(array $conditions, callable $leafEvaluator)
{
$sawNullResult = false;
foreach ($conditions as $condition) {
$result = $this->evaluate($condition, $leafEvaluator);

if($result === true) {
return true;
}

if($result === null) {
$sawNullResult = true;
}
}

return $sawNullResult ? null : false;
}

/**
* Evaluates an array of conditions as if the evaluator had been applied
* to a single entry and NOT was applied to the result.
*
* @param array $conditions Audience conditions list.
* @param callable $leafEvaluator Method to evaluate leaf condition.
*
* @return null|boolean True if the operand evaluates to false.
* False if the operand evaluates to true.
* Null if conditions is empty or couldn't be evaluated.
*/
protected function notEvaluator(array $condition, callable $leafEvaluator)
{
if (empty($condition)) {
return null;
}

$result = $this->evaluate($condition[0], $leafEvaluator);
return $result === null ? null: !$result;
}

/**
* Function to evaluate audience conditions against user's attributes.
*
* @param array $conditions Nested array of and/or/not conditions representing the audience conditions.
* @param callable $leafEvaluator Method to evaluate leaf condition.
*
* @return null|boolean Result of evaluating the conditions using the operator rules.
* and the leaf evaluator. Null if conditions couldn't be evaluated.
*/
public function evaluate($conditions, callable $leafEvaluator)
{
if (!Validator::doesArrayContainOnlyStringKeys($conditions)) {

if(in_array($conditions[0], $this->getOperators())) {
$operator = array_shift($conditions);
} else {
$operator = self::OR_OPERATOR;
}

$evaluatorFunc = $this->getEvaluatorByOperatorType($operator);
return $this->{$evaluatorFunc}($conditions, $leafEvaluator);
}

$leafCondition = $conditions;
return $leafEvaluator($leafCondition);
}
}
Loading