diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e63c45e..f9e680cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,11 @@ name: ci on: - pull_request: - branches: - - develop - - master push: - branches: - - develop - - master + branches-ignore: + - none + pull_request: + branches-ignore: + - none concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} @@ -26,13 +24,12 @@ jobs: fail-fast: false matrix: version: - - '7.3' - '8.0' - '8.1' - '8.2' steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -67,7 +64,6 @@ jobs: projectBaseDir: . args: > -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - -Dsonar.projectVersion=${{ env.VERSION }} - name: SonarQube Scan (Pull Request) if: matrix.version == '8.2' && github.event_name == 'pull_request' @@ -79,7 +75,6 @@ jobs: projectBaseDir: . args: > -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - -Dsonar.projectVersion=${{ env.VERSION }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} - -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} + -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} \ No newline at end of file diff --git a/.github/workflows/update-license-year.yml b/.github/workflows/update-license-year.yml index 9e582543..13aaac8a 100644 --- a/.github/workflows/update-license-year.yml +++ b/.github/workflows/update-license-year.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/CHANGES.txt b/CHANGES.txt index d4ff616a..f3338d24 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,17 @@ +8.0.0 (Feb XX, 2023) + - BREAKING CHANGE: Removed support from versions older than PHP 7.4. + - BREAKING CHANGE: Added support for Multiple Factory Instantiation. + +7.2.0 (Jan 24, 2024) + - Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation): + - Added new variations of the get treatment methods to support evaluating flags in given flag set/s. + - getTreatmentsByFlagSet and getTreatmentsByFlagSets + - getTreatmentWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets + - Added `defaultTreatment` and `sets` properties to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager. + +7.1.8 (Jul 24, 2023) + - Fixed input validation for empty keys. + 7.1.7 (May 16, 2023) - Updated terminology on the SDKs codebase to be more aligned with current standard without causing a breaking change. The core change is the term split for feature flag on things like logs and phpdoc comments. - Fixed php 8.2 warnings in code. diff --git a/bin/splitio.dist.ini b/bin/splitio.dist.ini index f56922d2..bdef2c51 100644 --- a/bin/splitio.dist.ini +++ b/bin/splitio.dist.ini @@ -10,8 +10,8 @@ ;NOTE: This file is read by default. If you would like customize it, please copy this file and replace the values. ; -;API-KEY for Split environment -api-key = 'YOUR_SPLITIO_APIKEY' +;SDK-KEY for Split environment +sdk-key = 'YOUR_SDK_KEY' ;--------------------- ;LOG configuration @@ -53,26 +53,3 @@ log-level = 'error' cache-adapter = 'predis' predis-parameters = '{"scheme":"tcp","host":"localhost","port":6379,"timeout":881}' predis-options = '{"prefix":""}' - - -;--------------------- -;PROCESS configuration -;--------------------- - -;SPLITS -;Set the rate to fetch the Splits definitions from server -features-refresh-rate = 30 - -;SEGMENTS -;Set the rate to fetch the Segments keys from server -segments-refresh-rate = 60 - -;IMPRESSIONS -;Set the rate to send the treatments impressions to server -impressions-refresh-rate = 60 - -;METRICS -;Set the rate to send the SDK metrics to server -metrics-refresh-rate = 60 - - diff --git a/composer.json b/composer.json index 16f5a3f0..25897e5b 100644 --- a/composer.json +++ b/composer.json @@ -22,16 +22,17 @@ "bin": ["bin/splitio"], "require": { - "php": ">=7.3", + "php": ">=8.0", "psr/log": "1 - 3", "predis/predis": "^2.0", "symfony/yaml": "^5.3|^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.0.0", + "phpunit/phpunit": "^9.6", "squizlabs/php_codesniffer": "3.*", - "rogervila/php-sonarqube-scanner": "1.1.0" + "rogervila/php-sonarqube-scanner": "1.1.0", + "mockery/mockery": "^1.5" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 43432876..bedd3c7d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -30,12 +30,15 @@ tests/Suite/Component/ - + tests/Suite/DynamicConfigurations/ tests/Suite/Engine/ + + tests/Suite/TrafficAllocation/ + tests/Suite/InputValidation/ @@ -59,6 +62,7 @@ tests/Suite/Component/ tests/Suite/DynamicConfigurations/ tests/Suite/Engine/ + tests/Suite/TrafficAllocation/ tests/Suite/InputValidation/ tests/Suite/Matchers/ tests/Suite/Metrics/ diff --git a/src/SplitIO/Component/Cache/EventsCache.php b/src/SplitIO/Component/Cache/EventsCache.php index a99066b3..b8baf739 100644 --- a/src/SplitIO/Component/Cache/EventsCache.php +++ b/src/SplitIO/Component/Cache/EventsCache.php @@ -1,18 +1,32 @@ cache = $cache; + } + + public function addEvent(EventQueueMessage $message) { $queueJSONmessage = json_encode($message->toArray()); - Di::getLogger()->debug("Adding event item into queue: ". $queueJSONmessage); - return (Di::getCache()->rightPushInList(self::KEY_EVENTS_LIST, $queueJSONmessage) > 0); + Context::getLogger()->debug("Adding event item into queue: ". $queueJSONmessage); + return ($this->cache->rightPushInList(self::KEY_EVENTS_LIST, $queueJSONmessage) > 0); } } diff --git a/src/SplitIO/Component/Cache/ImpressionCache.php b/src/SplitIO/Component/Cache/ImpressionCache.php index 9d1c5f4c..ff088a92 100644 --- a/src/SplitIO/Component/Cache/ImpressionCache.php +++ b/src/SplitIO/Component/Cache/ImpressionCache.php @@ -1,15 +1,28 @@ cache = $cache; + } + public function logImpressions($impressions, QueueMetadataMessage $metadata) { $toStore = array_map( @@ -30,10 +43,10 @@ function ($imp) use ($metadata) { $impressions ); - Di::getLogger()->debug("Adding impressions into queue: ". implode(",", $toStore)); - $count = Di::getCache()->rightPushInList(self::IMPRESSIONS_QUEUE_KEY, $toStore); + Context::getLogger()->debug("Adding impressions into queue: ". implode(",", $toStore)); + $count = $this->cache->rightPushInList(self::IMPRESSIONS_QUEUE_KEY, $toStore); if ($count == count($impressions)) { - Di::getCache()->expireKey(self::IMPRESSIONS_QUEUE_KEY, self::IMPRESSION_KEY_DEFAULT_TTL); + $this->cache->expireKey(self::IMPRESSIONS_QUEUE_KEY, self::IMPRESSION_KEY_DEFAULT_TTL); } return ($count >= count($impressions)); } diff --git a/src/SplitIO/Component/Cache/Pool.php b/src/SplitIO/Component/Cache/Pool.php index ae850078..43bdf504 100644 --- a/src/SplitIO/Component/Cache/Pool.php +++ b/src/SplitIO/Component/Cache/Pool.php @@ -3,7 +3,7 @@ use SplitIO\Component\Cache\Storage\Adapter\PRedis as PRedisAdapter; use SplitIO\Component\Cache\Storage\Adapter\SafeRedisWrapper; -use SplitIO\Component\Common\Di; +use SplitIO\Component\Common\Context; class Pool extends CacheKeyTrait { @@ -39,7 +39,7 @@ public function __construct(array $options = array()) public function get($key) { $this->assertValidKey($key); - Di::getLogger()->debug("Fetching item ** $key ** from cache"); + Context::getLogger()->debug("Fetching item ** $key ** from cache"); return $this->adapter->get($key); } @@ -83,4 +83,9 @@ public function expireKey($key, $ttl) { return $this->adapter->expireKey($key, $ttl); } + + public function sMembers($key) + { + return $this->adapter->sMembers($key); + } } diff --git a/src/SplitIO/Component/Cache/SegmentCache.php b/src/SplitIO/Component/Cache/SegmentCache.php index 8ea79d78..1c1a838f 100644 --- a/src/SplitIO/Component/Cache/SegmentCache.php +++ b/src/SplitIO/Component/Cache/SegmentCache.php @@ -1,14 +1,25 @@ cache = $cache; + } + private static function getCacheKeyForSegmentData($segmentName) { return str_replace('{segmentName}', $segmentName, self::KEY_SEGMENT_DATA); @@ -27,7 +38,7 @@ private static function getCacheKeyForSinceParameter($segmentName) public function isInSegment($segmentName, $key) { $segmentDataKey = self::getCacheKeyForSegmentData($segmentName); - return Di::getCache()->isItemOnList($segmentDataKey, $key); + return $this->cache->isItemOnList($segmentDataKey, $key); } /** @@ -36,7 +47,7 @@ public function isInSegment($segmentName, $key) */ public function getChangeNumber($segmentName) { - $since = Di::getCache()->get(self::getCacheKeyForSinceParameter($segmentName)); + $since = $this->cache->get(self::getCacheKeyForSinceParameter($segmentName)); // empty check for nullable value return (empty($since)) ? -1 : $since; } diff --git a/src/SplitIO/Component/Cache/SplitCache.php b/src/SplitIO/Component/Cache/SplitCache.php index 522a53a1..6eae5403 100644 --- a/src/SplitIO/Component/Cache/SplitCache.php +++ b/src/SplitIO/Component/Cache/SplitCache.php @@ -1,8 +1,6 @@ cache = $cache; + } + private static function getCacheKeyForSinceParameter() { return self::KEY_TILL_CACHED_ITEM; @@ -26,6 +39,11 @@ private static function getCacheKeyForSplit($splitName) return str_replace('{splitName}', $splitName, self::KEY_SPLIT_CACHED_ITEM); } + private static function getCacheKeyForFlagSet($flagSet) + { + return str_replace('{set}', $flagSet, self::KEY_FLAG_SET_CACHED); + } + private static function getSplitNameFromCacheKey($key) { $cacheKeyPrefix = self::getCacheKeyForSplit(''); @@ -33,11 +51,11 @@ private static function getSplitNameFromCacheKey($key) } /** - * @return long + * @return int */ public function getChangeNumber() { - $since = Di::getCache()->get(self::getCacheKeyForSinceParameter()); + $since = $this->cache->get(self::getCacheKeyForSinceParameter()); // empty check for nullable value return (empty($since)) ? -1 : $since; } @@ -48,18 +66,16 @@ public function getChangeNumber() */ public function getSplit($splitName) { - $cache = Di::getCache(); - return $cache->get(self::getCacheKeyForSplit($splitName)); + return $this->cache->get(self::getCacheKeyForSplit($splitName)); } /** * @param array $splitNames - * @return string JSON representation + * @return array */ public function getSplits($splitNames) { - $cache = Di::getCache(); - $cacheItems = $cache->fetchMany(array_map([ + $cacheItems = $this->cache->fetchMany(array_map([ self::class, 'getCacheKeyForSplit' ], $splitNames)); $toReturn = array(); @@ -74,11 +90,26 @@ public function getSplits($splitNames) */ public function getSplitNames() { - $cache = Di::getCache(); - $splitKeys = $cache->getKeys(self::getCacheKeySearchPattern()); + $splitKeys = $this->cache->getKeys(self::getCacheKeySearchPattern()); return array_map([self::class, 'getSplitNameFromCacheKey'], $splitKeys); } + /** + * @param array(string) List of flag set names + * @return array(string) List of all feature flag names by flag sets + */ + public function getNamesByFlagSets($flagSets) + { + $toReturn = array(); + if (empty($flagSets)) { + return $toReturn; + } + foreach ($flagSets as $flagSet) { + $toReturn[$flagSet] = $this->cache->sMembers(self::getCacheKeyForFlagSet($flagSet)); + } + return $toReturn; + } + /** * @return array(string) List of all split JSON strings */ @@ -99,9 +130,7 @@ private static function getCacheKeyForTrafficType($trafficType) */ public function trafficTypeExists($trafficType) { - $cache = Di::getCache(); - - $count = $cache->get(self::getCacheKeyForTrafficType($trafficType)); + $count = $this->cache->get(self::getCacheKeyForTrafficType($trafficType)); // empty check for nullable value return (empty($count) || $count < 1) ? false : true; } diff --git a/src/SplitIO/Component/Cache/SplitCacheInterface.php b/src/SplitIO/Component/Cache/SplitCacheInterface.php index 1321466a..9ec14ffc 100644 --- a/src/SplitIO/Component/Cache/SplitCacheInterface.php +++ b/src/SplitIO/Component/Cache/SplitCacheInterface.php @@ -4,7 +4,7 @@ interface SplitCacheInterface { /** - * @return long + * @return int */ public function getChangeNumber(); @@ -13,4 +13,10 @@ public function getChangeNumber(); * @return string JSON representation */ public function getSplit($splitName); + + /** + * @param array(string) List of flag set names + * @return array(string) List of all feature flag names by flag sets + */ + public function getNamesByFlagSets($flagSets); } diff --git a/src/SplitIO/Component/Cache/Storage/Adapter/CacheStorageAdapterInterface.php b/src/SplitIO/Component/Cache/Storage/Adapter/CacheStorageAdapterInterface.php index fbbfb44d..5d12ea70 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/CacheStorageAdapterInterface.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/CacheStorageAdapterInterface.php @@ -37,4 +37,10 @@ public function rightPushQueue($queueName, $item); * @return boolean */ public function expireKey($key, $ttl); + + /** + * @param string $key + * @return mixed + */ + public function sMembers($key); } diff --git a/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php b/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php index d4758a47..1c6b8dca 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php @@ -2,9 +2,7 @@ namespace SplitIO\Component\Cache\Storage\Adapter; use SplitIO\Component\Cache\Storage\Exception\AdapterException; -use SplitIO\Component\Cache\Item; use SplitIO\Component\Utils as SplitIOUtils; -use SplitIO\Component\Common\Di; /** * Class PRedis @@ -16,6 +14,9 @@ class PRedis implements CacheStorageAdapterInterface /** @var \Predis\Client|null */ private $client = null; + /** @var string */ + private $prefix = ""; + /** * @param array $options * @throws AdapterException @@ -27,7 +28,11 @@ public function __construct(array $options) } $_redisConfig = $this->getRedisConfiguration($options); - $this->client = new \Predis\Client($_redisConfig['redis'], $_redisConfig['options']); + $this->client = new \Predis\Client($_redisConfig['parameters'], $_redisConfig['options']); + + if (isset($_redisConfig['options']['prefix'])) { + $this->prefix = $_redisConfig['options']['prefix']; + } } /** @@ -49,24 +54,6 @@ private function isValidConfigArray($nodes, $type) return null; } - /** - * @param array $sentinels - * @param array $options - * @return bool - * @throws AdapterException - */ - private function isValidSentinelConfig($sentinels, $options) - { - $msg = $this->isValidConfigArray($sentinels, 'sentinel'); - if (!is_null($msg)) { - throw new AdapterException($msg); - } - if (!isset($options['service'])) { - throw new AdapterException('Master name is required in replication mode for sentinel.'); - } - return true; - } - private function validateKeyHashTag($keyHashTag) { if (!is_string($keyHashTag)) { @@ -109,7 +96,7 @@ private function selectKeyHashTag($options) return $this->getDefaultKeyHashTag($options); // defaulting to keyHashTag or {SPLITIO} } $keyHashTags = $options['keyHashTags']; - $msg = $this->isValidConfigArray($keyHashTags, 'keyHashTags'); // check if is valid array + $msg = $this->isValidConfigArray($keyHashTags, 'keyHashTag'); // check if is valid array if (!is_null($msg)) { throw new AdapterException($msg); } @@ -122,83 +109,32 @@ function ($value) { if (empty($filteredArray)) { throw new AdapterException('keyHashTags size is zero after filtering valid elements.'); } - return $selected = $filteredArray[array_rand($filteredArray, 1)]; + return $filteredArray[array_rand($filteredArray, 1)]; } - /** - * @param array $clusters - * @return bool - * @throws AdapterException - */ - private function isValidClusterConfig($clusters) - { - $msg = $this->isValidConfigArray($clusters, 'clusterNode'); - if (!is_null($msg)) { - throw new AdapterException($msg); - } - return true; - } - /** * @param mixed $options * @return array - * @throws AdapterException */ private function getRedisConfiguration($options) { $redisConfigutation = array( - 'redis' => null, - 'options' => null + 'parameters' => (isset($options['parameters'])) ? $options['parameters'] : null, + 'options' => null, ); - $parameters = (isset($options['parameters'])) ? $options['parameters'] : null; - $sentinels = (isset($options['sentinels'])) ? $options['sentinels'] : null; - $clusters = (isset($options['clusterNodes'])) ? $options['clusterNodes'] : null; $_options = (isset($options['options'])) ? $options['options'] : null; - - if (isset($_options['distributedStrategy']) && isset($parameters['tls'])) { - throw new AdapterException("SSL/TLS cannot be used together with sentinel/cluster yet"); - } - if ($_options && isset($_options['prefix'])) { $_options['prefix'] = self::normalizePrefix($_options['prefix']); } - if (isset($parameters)) { - $redisConfigutation['redis'] = $parameters; - } else { - // @TODO remove this statement when replication will be deprecated - if (isset($_options['replication'])) { - Di::getLogger()->warning("'replication' option was deprecated please use 'distributedStrategy'"); - if (!isset($_options['distributedStrategy'])) { - $_options['distributedStrategy'] = $_options['replication']; - } - } - if (isset($_options['distributedStrategy'])) { - switch ($_options['distributedStrategy']) { - case 'cluster': - if ($this->isValidClusterConfig($clusters)) { - $keyHashTag = $this->selectKeyHashTag($_options); - $_options['cluster'] = 'redis'; - $redisConfigutation['redis'] = $clusters; - $prefix = isset($_options['prefix']) ? $_options['prefix'] : ''; - $_options['prefix'] = $keyHashTag . $prefix; - } - break; - case 'sentinel': - if ($this->isValidSentinelConfig($sentinels, $_options)) { - $_options['replication'] = 'sentinel'; - $redisConfigutation['redis'] = $sentinels; - } - break; - default: - throw new AdapterException("Wrong configuration of redis 'distributedStrategy'."); - } - } else { - throw new AdapterException("Wrong configuration of redis."); - } + if (isset($_options['cluster'])) { + $keyHashTag = $this->selectKeyHashTag($_options); + $prefix = isset($_options['prefix']) ? $_options['prefix'] : ''; + $_options['prefix'] = $keyHashTag . $prefix; } + $redisConfigutation['options'] = $_options; return $redisConfigutation; } @@ -248,33 +184,19 @@ public function isOnList($key, $value) return $this->client->sIsMember($key, $value); } - public function getKeys($pattern = '*') + /** + * @param string $key + * @return mixed + */ + public function sMembers($key) { - $prefix = null; - if ($this->client->getOptions()->__isset("prefix")) { - $prefix = $this->client->getOptions()->__get("prefix")->getPrefix(); - } + return $this->client->smembers($key); + } - if ($this->client->getOptions()->__isset("distributedStrategy") && - $this->client->getOptions()->__get("distributedStrategy") == "cluster") { - $keys = array(); - foreach ($this->client as $nodeClient) { - $nodeClientKeys = $nodeClient->keys($pattern); - $keys = array_merge($keys, $nodeClientKeys); - } - } else { - $keys = $this->client->keys($pattern); - } - if ($prefix) { - if (is_array($keys)) { - foreach ($keys as $index => $key) { - $keys[$index] = str_replace($prefix, '', $key); - } - } else { - $keys = str_replace($prefix, '', $keys); - } - } - return $keys; + public function getKeys($pattern = '*') + { + $keys = $this->client->keys($pattern); + return str_replace($this->prefix, '', $keys); } private static function normalizePrefix($prefix) diff --git a/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php b/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php index 9d7a9f4d..a1dc5e96 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php @@ -1,10 +1,7 @@ cacheAdapter->get($key); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred getting " . $key . " from redis."); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred getting " . $key . " from redis."); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return null; } } @@ -53,9 +50,9 @@ public function fetchMany(array $keys = array()) try { return $this->cacheAdapter->fetchMany($keys); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred getting " . json_encode($keys) . " from redis."); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred getting " . json_encode($keys) . " from redis."); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return array(); } } @@ -70,9 +67,9 @@ public function isOnList($key, $value) try { return $this->cacheAdapter->isOnList($key, $value); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred for " . $key); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred for " . $key); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return false; } } @@ -86,9 +83,9 @@ public function getKeys($pattern = '*') try { return $this->cacheAdapter->getKeys($pattern); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred getting " . $pattern); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred getting " . $pattern); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return array(); } } @@ -103,9 +100,9 @@ public function rightPushQueue($queueName, $item) try { return $this->cacheAdapter->rightPushQueue($queueName, $item); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred performing RPUSH into " . $queueName); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred performing RPUSH into " . $queueName); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return 0; } } @@ -120,10 +117,26 @@ public function expireKey($key, $ttl) try { return $this->cacheAdapter->expireKey($key, $ttl); } catch (\Exception $e) { - Di::getLogger()->critical("An error occurred setting expiration for " . $key); - Di::getLogger()->critical($e->getMessage()); - Di::getLogger()->critical($e->getTraceAsString()); + Context::getLogger()->critical("An error occurred setting expiration for " . $key); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); return false; } } + + /** + * @param string $key + * @return mixed + */ + public function sMembers($key) + { + try { + return $this->cacheAdapter->sMembers($key); + } catch (\Exception $e) { + Context::getLogger()->critical("An error occurred performing SMEMBERS for " . $key); + Context::getLogger()->critical($e->getMessage()); + Context::getLogger()->critical($e->getTraceAsString()); + return array(); + } + } } diff --git a/src/SplitIO/Component/Common/Context.php b/src/SplitIO/Component/Common/Context.php new file mode 100644 index 00000000..5c1a5618 --- /dev/null +++ b/src/SplitIO/Component/Common/Context.php @@ -0,0 +1,133 @@ +factoryTracker[$sdkKey] ?? 0; + if ($current > 0) { + self::getInstance()->getLogger()->warning(sprintf(self::SAME_SDK_KEY, $current)); + } elseif (count(self::getInstance()->factoryTracker) > 0) { + self::getInstance()->getLogger()->warning(self::MULTIPLE_INSTANCES); + } + $current += 1; + self::getInstance()->factoryTracker[$sdkKey] = $current; + return $current; + } + + /** + * @param \SplitIO\Component\Log\Logger $logger + */ + public static function setLogger(Logger $logger) + { + if (!isset(self::getInstance()->logger)) { + self::getInstance()->logger = $logger; + return; + } + self::getInstance()->logger->debug("logger was set before, ignoring new instance provided"); + } + + /** + * @return \SplitIO\Component\Log\Logger + */ + public static function getLogger() + { + if (!isset(self::getInstance()->logger)) { + throw new Exception("logger was not set yet"); + } + return self::getInstance()->logger; + } + + /** + * @param string $ip + */ + public static function setIPAddress(string $ip) + { + if (empty(self::getInstance()->ipAddress)) { + self::getInstance()->ipAddress = $ip; + return; + } + self::getInstance()->getLogger()->debug("IPAddress was set before, ignoring new instance provided"); + } + + /** + * @return string + */ + public static function getIPAddress() + { + return self::getInstance()->ipAddress; + } +} diff --git a/src/SplitIO/Component/Common/Di.php b/src/SplitIO/Component/Common/Di.php deleted file mode 100644 index e36025c3..00000000 --- a/src/SplitIO/Component/Common/Di.php +++ /dev/null @@ -1,159 +0,0 @@ -container[$key] = $instance; - } - - /** - * @param $key - * @return mixed - */ - private function getKey($key) - { - return (isset($this->container[$key])) ? $this->container[$key] : null; - } - - /** - * Set an object instance with its key - * @param $key - * @param $instance - */ - public static function set($key, $instance) - { - self::getInstance()->setKey($key, $instance); - } - - /** - * Given a key returns the object instance associated with this. - * @param $key - * @return mixed - */ - public static function get($key) - { - return self::getInstance()->getKey($key); - } - - - /** - * @param LoggerInterface $logger - */ - public static function setLogger($logger) - { - self::set(self::KEY_LOG, $logger); - } - - /** - * @return null|\Psr\Log\LoggerInterface - */ - public static function getLogger() - { - return self::get(self::KEY_LOG); - } - - /** - * @param \SplitIO\Component\Cache\Pool $cachePool - */ - public static function setCache(Pool $cachePool) - { - self::set(self::KEY_CACHE, $cachePool); - } - - /** - * @return null|\SplitIO\Component\Cache\Pool - */ - public static function getCache() - { - return self::get(self::KEY_CACHE); - } - - public static function setEvaluator(\SplitIO\Sdk\Evaluator $evaluator) - { - self::set(self::KEY_EVALUATOR, $evaluator); - } - - public static function getEvaluator() - { - return self::get(self::KEY_EVALUATOR); - } -} diff --git a/src/SplitIO/Component/Common/ServiceProvider.php b/src/SplitIO/Component/Common/ServiceProvider.php deleted file mode 100644 index 52c87cf7..00000000 --- a/src/SplitIO/Component/Common/ServiceProvider.php +++ /dev/null @@ -1,15 +0,0 @@ - 'predis', - 'options' => array( - 'options' => isset($options['predis-options']) - ? $options['predis-options'] : null, - 'parameters' => isset($options['predis-parameters']) - ? $options['predis-parameters'] : null, - 'sentinels' => isset($options['predis-sentinels']) - ? $options['predis-sentinels'] : null, - 'clusterNodes' => isset($options['predis-clusterNodes']) - ? $options['predis-clusterNodes'] : null, - 'distributedStrategy' => isset($options['predis-distributedStrategy']) - ? $options['predis-distributedStrategy'] : null, - ) - ); - - ServiceProvider::registerCache(new Pool(array('adapter' => $adapter_config))); - } -} diff --git a/src/SplitIO/Component/Initialization/LoggerFactory.php b/src/SplitIO/Component/Initialization/LoggerFactory.php index ea39e25a..3e25f6c9 100644 --- a/src/SplitIO/Component/Initialization/LoggerFactory.php +++ b/src/SplitIO/Component/Initialization/LoggerFactory.php @@ -16,7 +16,7 @@ class LoggerFactory * Builds defaultLogger * * @param $options - * @return SplitIO\Component\Log\Handler + * @return \SplitIO\Component\Log\Handler\LogHandlerInterface */ private static function buildAdapter(array $options) { @@ -42,7 +42,7 @@ private static function buildAdapter(array $options) * Builds logger * * @param $options - * @return SplitIO\Component\Log\Logger + * @return \SplitIO\Component\Log\Logger */ public static function setupLogger(array $options) { diff --git a/src/SplitIO/Component/Log/Logger.php b/src/SplitIO/Component/Log/Logger.php index d05dddb1..1dea99a7 100644 --- a/src/SplitIO/Component/Log/Logger.php +++ b/src/SplitIO/Component/Log/Logger.php @@ -1,7 +1,6 @@ getConditionType() == ConditionTypeEnum::ROLLOUT) { if ($split->getTrafficAllocation() < 100) { - $bucket = Di::get('splitter')->getBucket( + $bucket = Splitter::getBucket( $split->getAlgo(), $bucketingKey, $split->getTrafficAllocationSeed() @@ -51,8 +55,8 @@ public static function getTreatment($matchingKey, $bucketingKey, SplitGrammar $s $inRollOut = true; } } - if ($condition->match($matchingKey, $attributes, $bucketingKey)) { - $result[self::EVALUATION_RESULT_TREATMENT] = Di::get('splitter')->getTreatment( + if ($condition->match($matchingKey, $attributes, $bucketingKey, $context)) { + $result[self::EVALUATION_RESULT_TREATMENT] = Splitter::getTreatment( $bucketingKey, $split->getSeed(), $condition->getPartitions(), diff --git a/src/SplitIO/Engine/Hash/HashFactory.php b/src/SplitIO/Engine/Hash/HashFactory.php index 30edf219..c2776f8a 100644 --- a/src/SplitIO/Engine/Hash/HashFactory.php +++ b/src/SplitIO/Engine/Hash/HashFactory.php @@ -6,7 +6,7 @@ class HashFactory { /** * @param $algo - * @return \SplitIO\Engine\HashInterface + * @return \SplitIO\Engine\Hash\HashInterface */ public static function getHashAlgorithm($algo) { diff --git a/src/SplitIO/Engine/Splitter.php b/src/SplitIO/Engine/Splitter.php index ac78fe9f..d12bf967 100644 --- a/src/SplitIO/Engine/Splitter.php +++ b/src/SplitIO/Engine/Splitter.php @@ -8,12 +8,12 @@ class Splitter { /** - * @param HashAlgorithmEnum $algo + * @param \SplitIO\Engine\Hash\HashAlgorithmEnum $algo * @param string $key - * @param long $seed + * @param int $seed * @return int */ - public function getBucket($algo, $key, $seed) + public static function getBucket($algo, $key, $seed) { $hashFactory = HashFactory::getHashAlgorithm($algo); $hash = $hashFactory->getHash($key, $seed); @@ -23,12 +23,12 @@ public function getBucket($algo, $key, $seed) /** * @param string $key - * @param long $seed + * @param int $seed * @param array $partitions - * @param HashAlgorithmEnum $algo + * @param \SplitIO\Engine\Hash\HashAlgorithmEnum $algo * @return null|string */ - public function getTreatment($key, $seed, $partitions, $algo) + public static function getTreatment($key, $seed, $partitions, $algo) { $logMsg = "Splitter evaluating partitions ... \n Bucketing Key: $key \n diff --git a/src/SplitIO/Grammar/Condition.php b/src/SplitIO/Grammar/Condition.php index 852222af..317a6937 100644 --- a/src/SplitIO/Grammar/Condition.php +++ b/src/SplitIO/Grammar/Condition.php @@ -66,7 +66,7 @@ public function __construct(array $condition) * @param array|null $attributes * @return bool */ - public function match($key, array $attributes = null, $bucketingKey = null) + public function match($key, array $attributes = null, $bucketingKey = null, array $context = null) { $eval = array(); foreach ($this->matcherGroup as $matcher) { @@ -76,7 +76,7 @@ public function match($key, array $attributes = null, $bucketingKey = null) if (!$matcher->hasAttribute()) { // scenario 1: no attr in matcher // e.g. if user is in segment all then split 100:on - $_evaluation = $matcher->evaluate($key); + $_evaluation = $matcher->evaluate($key, $context); } else { // scenario 2: attribute provided but no attribute value provided. Matcher does not match // e.g. if user.age is >= 10 then split 100:on @@ -96,7 +96,7 @@ public function match($key, array $attributes = null, $bucketingKey = null) $printableAttributes = is_array($attributes) ? implode($attributes) : $attributes; SplitApp::logger()->info("Evaluating on IN_SPLIT_TREATMENT the KEY $printable"); SplitApp::logger()->info("with the following attributes: $printableAttributes"); - $_evaluation = $matcher->evalKey($key, $attributes, $bucketingKey); + $_evaluation = $matcher->evalKey($key, $attributes, $bucketingKey, $context); $eval[] = ($matcher->isNegate()) ? NotFactor::evaluate($_evaluation) : $_evaluation ; } else { //Throwing catchable exception the SDK client will return CONTROL diff --git a/src/SplitIO/Grammar/Condition/Matcher/AbstractMatcher.php b/src/SplitIO/Grammar/Condition/Matcher/AbstractMatcher.php index 53d31712..9b83388a 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/AbstractMatcher.php +++ b/src/SplitIO/Grammar/Condition/Matcher/AbstractMatcher.php @@ -22,12 +22,12 @@ protected function __construct($type, $negate = false, $attribute = null) $this->attribute = $attribute; } - public function evaluate($key) + public function evaluate($key, array $context = null) { $printable = is_array($key) ? implode($key) : $key; SplitApp::logger()->info("Evaluating on {$this->type} the KEY $printable"); - return $this->evalKey($key); + return $this->evalKey($key, $context); } public function isNegate() @@ -45,5 +45,5 @@ public function getAttribute() return $this->attribute; } - abstract protected function evalKey($key); + abstract protected function evalKey($key, array $context); } diff --git a/src/SplitIO/Grammar/Condition/Matcher/All.php b/src/SplitIO/Grammar/Condition/Matcher/All.php index 321dc455..1e41d8dc 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/All.php +++ b/src/SplitIO/Grammar/Condition/Matcher/All.php @@ -12,7 +12,7 @@ public function __construct($negate = false, $attribute = null) parent::__construct(Matcher::ALL_KEYS, $negate, $attribute); } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { SplitApp::logger()->info("Comparing: ALL_KEYS - $key"); SplitApp::logger()->info("User found: $key"); diff --git a/src/SplitIO/Grammar/Condition/Matcher/Between.php b/src/SplitIO/Grammar/Condition/Matcher/Between.php index 45fc4a11..71e5a843 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/Between.php +++ b/src/SplitIO/Grammar/Condition/Matcher/Between.php @@ -23,7 +23,7 @@ public function __construct($data, $negate = false, $attribute = null) * @param $key * @return bool */ - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_long($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/ContainsAllOfSet.php b/src/SplitIO/Grammar/Condition/Matcher/ContainsAllOfSet.php index 58be6c4c..b6556eeb 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/ContainsAllOfSet.php +++ b/src/SplitIO/Grammar/Condition/Matcher/ContainsAllOfSet.php @@ -16,7 +16,7 @@ public function __construct($data, $negate = false, $attribute = null) $this->set = Set::fromArray($data); } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/ContainsAnyOfSet.php b/src/SplitIO/Grammar/Condition/Matcher/ContainsAnyOfSet.php index c8dbe3cc..9bf97582 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/ContainsAnyOfSet.php +++ b/src/SplitIO/Grammar/Condition/Matcher/ContainsAnyOfSet.php @@ -16,7 +16,7 @@ public function __construct($data, $negate = false, $attribute = null) $this->set = Set::fromArray($data); } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/ContainsString.php b/src/SplitIO/Grammar/Condition/Matcher/ContainsString.php index 1d08a8b0..26f127dc 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/ContainsString.php +++ b/src/SplitIO/Grammar/Condition/Matcher/ContainsString.php @@ -1,7 +1,6 @@ containsStringMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($this->containsStringMatcherData) || !is_string($key) || strlen($key) == 0) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/Dependency.php b/src/SplitIO/Grammar/Condition/Matcher/Dependency.php index 2bf28cb2..02a41453 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/Dependency.php +++ b/src/SplitIO/Grammar/Condition/Matcher/Dependency.php @@ -1,10 +1,8 @@ negate; } - public function evalKey($key, $attributes = null, $bucketingKey = null) + public function evalKey($key, $attributes = null, $bucketingKey = null, $context = null) { - $evaluator = Di::getEvaluator(); + if (!isset($context['evaluator'])) { + throw new Exception('Evaluator not present in matcher context.'); + } + $evaluator = $context['evaluator']; $result = $evaluator->evaluateFeature($key, $bucketingKey, $this->splitName, $attributes); return (is_array($this->treatments) && in_array($result['treatment'], $this->treatments)); } diff --git a/src/SplitIO/Grammar/Condition/Matcher/EndsWith.php b/src/SplitIO/Grammar/Condition/Matcher/EndsWith.php index 97f0f458..b0708443 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/EndsWith.php +++ b/src/SplitIO/Grammar/Condition/Matcher/EndsWith.php @@ -1,7 +1,6 @@ endsWithMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($this->endsWithMatcherData) || !is_string($key) || strlen($key) == 0) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/EqualTo.php b/src/SplitIO/Grammar/Condition/Matcher/EqualTo.php index 8e7df8ba..7d0839e7 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/EqualTo.php +++ b/src/SplitIO/Grammar/Condition/Matcher/EqualTo.php @@ -23,7 +23,7 @@ public function __construct($data, $negate = false, $attribute = null) * @param $key * @return bool */ - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_long($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/EqualToBoolean.php b/src/SplitIO/Grammar/Condition/Matcher/EqualToBoolean.php index ec90df7b..cad8c0c3 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/EqualToBoolean.php +++ b/src/SplitIO/Grammar/Condition/Matcher/EqualToBoolean.php @@ -1,7 +1,6 @@ booleanMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (is_string($key)) { $decodedKey = json_decode(strtolower($key)); diff --git a/src/SplitIO/Grammar/Condition/Matcher/EqualToSet.php b/src/SplitIO/Grammar/Condition/Matcher/EqualToSet.php index 571868da..70fec750 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/EqualToSet.php +++ b/src/SplitIO/Grammar/Condition/Matcher/EqualToSet.php @@ -16,7 +16,7 @@ public function __construct($data, $negate = false, $attribute = null) $this->set = Set::fromArray($data); } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualTo.php b/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualTo.php index 360b7597..45ed8a2a 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualTo.php +++ b/src/SplitIO/Grammar/Condition/Matcher/GreaterThanOrEqualTo.php @@ -23,7 +23,7 @@ public function __construct($data, $negate = false, $attribute = null) * @param $key * @return bool */ - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_long($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualTo.php b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualTo.php index da9c8d60..ee7a453c 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualTo.php +++ b/src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualTo.php @@ -23,7 +23,7 @@ public function __construct($data, $negate = false, $attribute = null) * @param $key * @return bool */ - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_long($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/PartOfSet.php b/src/SplitIO/Grammar/Condition/Matcher/PartOfSet.php index d33a6413..e2cd78a5 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/PartOfSet.php +++ b/src/SplitIO/Grammar/Condition/Matcher/PartOfSet.php @@ -16,7 +16,7 @@ public function __construct($data, $negate = false, $attribute = null) $this->set = Set::fromArray($data); } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($key) || empty($key)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/Regex.php b/src/SplitIO/Grammar/Condition/Matcher/Regex.php index 8169d218..936728f1 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/Regex.php +++ b/src/SplitIO/Grammar/Condition/Matcher/Regex.php @@ -1,7 +1,6 @@ regexMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_string($this->regexMatcherData)) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/Segment.php b/src/SplitIO/Grammar/Condition/Matcher/Segment.php index 4ab2c391..b45e7801 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/Segment.php +++ b/src/SplitIO/Grammar/Condition/Matcher/Segment.php @@ -1,10 +1,9 @@ userDefinedSegmentMatcherData; - - $segmentCache = new SegmentCache(); + if (!isset($context['segmentCache'])) { + throw new Exception('Segment storage not present in matcher context.'); + } + $segmentCache = $context['segmentCache']; if ($segmentCache->isInSegment($segmentName, $key)) { return true; diff --git a/src/SplitIO/Grammar/Condition/Matcher/StartsWith.php b/src/SplitIO/Grammar/Condition/Matcher/StartsWith.php index 6f0792fc..b0494ec3 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/StartsWith.php +++ b/src/SplitIO/Grammar/Condition/Matcher/StartsWith.php @@ -1,7 +1,6 @@ startsWithMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { if (!is_array($this->startsWithMatcherData) || !is_string($key) || strlen($key) == 0) { return false; diff --git a/src/SplitIO/Grammar/Condition/Matcher/Whitelist.php b/src/SplitIO/Grammar/Condition/Matcher/Whitelist.php index 99918b3a..07e4357c 100644 --- a/src/SplitIO/Grammar/Condition/Matcher/Whitelist.php +++ b/src/SplitIO/Grammar/Condition/Matcher/Whitelist.php @@ -1,7 +1,6 @@ whitelistMatcherData = $data; } - protected function evalKey($key) + protected function evalKey($key, array $context = null) { return (is_array($this->whitelistMatcherData)) ? in_array($key, $this->whitelistMatcherData) : false; } diff --git a/src/SplitIO/Grammar/Split.php b/src/SplitIO/Grammar/Split.php index 3e203dca..56354122 100644 --- a/src/SplitIO/Grammar/Split.php +++ b/src/SplitIO/Grammar/Split.php @@ -31,6 +31,7 @@ class Split private $trafficAllocationSeed = null; private $configurations = null; + private $sets = null; public function __construct(array $split) { @@ -50,6 +51,7 @@ public function __construct(array $split) $split['trafficAllocationSeed'] : null; $this->configurations = isset($split['configurations']) && count($split['configurations']) > 0 ? $split['configurations'] : null; + $this->sets = isset($split['sets']) ? $split['sets'] : array(); SplitApp::logger()->info("Constructing Feature Flag: ".$this->name); @@ -146,7 +148,7 @@ public function getTreatments() } /** - * @return HashAlgorithmEnum + * @return \SplitIO\Engine\Hash\HashAlgorithmEnum */ public function getAlgo() { @@ -167,4 +169,12 @@ public function getConfigurations() { return $this->configurations; } + + /** + * @return array|null + */ + public function getSets() + { + return $this->sets; + } } diff --git a/src/SplitIO/Metrics.php b/src/SplitIO/Metrics.php index bbbd3b82..099496f6 100644 --- a/src/SplitIO/Metrics.php +++ b/src/SplitIO/Metrics.php @@ -5,11 +5,6 @@ class Metrics { - const MNAME_SDK_GET_TREATMENT = 'sdk.getTreatment'; - const MNAME_SDK_GET_TREATMENT_WITH_CONFIG = 'sdk.getTreatmentWithConfig'; - const MNAME_SDK_GET_TREATMENTS = 'sdk.getTreatments'; - const MNAME_SDK_GET_TREATMENTS_WITH_CONFIG = 'sdk.getTreatmentsWithConfig'; - public static function startMeasuringLatency() { return Latency::startMeasuringLatency(); diff --git a/src/SplitIO/Sdk.php b/src/SplitIO/Sdk.php index a8eb081a..f498cc31 100644 --- a/src/SplitIO/Sdk.php +++ b/src/SplitIO/Sdk.php @@ -1,14 +1,12 @@ 'predis', + 'options' => $_options, + ); - private static function setIP($ip) - { - \SplitIO\Component\Common\Di::set('ipAddress', $ip); + return new Pool(array('adapter' => $adapter_config)); } - /** - * Check factory instance - */ - private static function instanceExists() - { - $value = Di::get(Di::KEY_FACTORY_TRACKER); - if (is_null($value) || !$value) { - return false; - } - Di::getLogger()->critical("Factory Instantiation: creating multiple factories is not possible. " - . "You have already created a factory."); - return true; - } - - /** - * Register factory instance - */ - private static function registerInstance() + private static function setIP($ip) { - Di::set(Di::KEY_FACTORY_TRACKER, true); + Context::setIPAddress($ip); } } diff --git a/src/SplitIO/Sdk/Client.php b/src/SplitIO/Sdk/Client.php index bcc9463e..9e2ac24c 100644 --- a/src/SplitIO/Sdk/Client.php +++ b/src/SplitIO/Sdk/Client.php @@ -1,22 +1,18 @@ splitCache = $storages['splitCache']; + $this->segmentCache = $storages['segmentCache']; + $this->impressionCache = $storages['impressionCache']; + $this->eventCache = $storages['eventCache']; $this->labelsEnabled = isset($options['labelsEnabled']) ? $options['labelsEnabled'] : true; - $this->evaluator = new Evaluator(); - Di::setEvaluator($this->evaluator); + $this->evaluator = new Evaluator($this->splitCache, $this->segmentCache); if (isset($options['impressionListener'])) { $this->impressionListener = new \SplitIO\Sdk\ImpressionListenerWrapper($options['impressionListener']); } @@ -109,7 +129,7 @@ private function doInputValidationForTreatment($key, $featureFlagName, array $at * * @return mixed */ - private function doEvaluation($operation, $metricName, $key, $featureFlagName, $attributes) + private function doEvaluation($operation, $key, $featureFlagName, $attributes) { $default = array('treatment' => TreatmentEnum::CONTROL, 'config' => null); @@ -135,7 +155,7 @@ private function doEvaluation($operation, $metricName, $key, $featureFlagName, $ $bucketingKey ); - $this->registerData($impression, $attributes, $metricName, $result['latency']); + $this->registerData($impression, $attributes); return array( 'treatment' => $result['treatment'], 'config' => $result['config'], @@ -156,7 +176,7 @@ private function doEvaluation($operation, $metricName, $key, $featureFlagName, $ ImpressionLabel::EXCEPTION, $bucketingKey ); - $this->registerData($impression, $attributes, $metricName); + $this->registerData($impression, $attributes); } catch (\Exception $e) { SplitApp::logger()->critical( "An error occurred when attempting to log impression for " . @@ -175,7 +195,6 @@ public function getTreatment($key, $featureName, array $attributes = null) try { $result = $this->doEvaluation( 'getTreatment', - Metrics::MNAME_SDK_GET_TREATMENT, $key, $featureName, $attributes @@ -195,7 +214,6 @@ public function getTreatmentWithConfig($key, $featureFlagName, array $attributes try { return $this->doEvaluation( 'getTreatmentWithConfig', - Metrics::MNAME_SDK_GET_TREATMENT_WITH_CONFIG, $key, $featureFlagName, $attributes @@ -240,14 +258,19 @@ private function doInputValidationForTreatments($key, $featureFlagNames, array $ ); } - private function registerData($impressions, $attributes, $metricName, $latency = null) + private function registerData($impressions, $attributes) { try { - TreatmentImpression::log($impressions, $this->queueMetadata); + if (is_null($impressions) || (is_array($impressions) && 0 == count($impressions))) { + return; + } + $toStore = (is_array($impressions)) ? $impressions : array($impressions); + $this->impressionCache->logImpressions($toStore, $this->queueMetadata); if (isset($this->impressionListener)) { - $this->impressionListener->sendDataToClient($impressions, $attributes); + $this->impressionListener->sendDataToClient($toStore, $attributes); } } catch (\Exception $e) { + SplitApp::logger()->warning($e->getMessage()); SplitApp::logger()->critical( ': An exception occurred when trying to store impressions.' ); @@ -265,7 +288,7 @@ private function registerData($impressions, $attributes, $metricName, $latency = * * @return mixed */ - private function doEvaluationForTreatments($operation, $metricName, $key, $featureFlagNames, $attributes) + private function doEvaluationForTreatments($operation, $key, $featureFlagNames, $attributes) { $inputValidation = $this->doInputValidationForTreatments($key, $featureFlagNames, $attributes, $operation); if (is_null($inputValidation)) { @@ -280,35 +303,19 @@ private function doEvaluationForTreatments($operation, $metricName, $key, $featu $featureFlags = $inputValidation['featureFlagNames']; try { - $result = array(); - $impressions = array(); $evaluationResults = $this->evaluator->evaluateFeatures( $matchingKey, $bucketingKey, $featureFlags, $attributes ); - foreach ($evaluationResults['evaluations'] as $featureFlag => $evalResult) { - if (InputValidator::isSplitFound($evalResult['impression']['label'], $featureFlag, $operation)) { - // Creates impression - $impressions[] = $this->createImpression( - $matchingKey, - $featureFlag, - $evalResult['treatment'], - $evalResult['impression']['changeNumber'], - $evalResult['impression']['label'], - $bucketingKey - ); - $result[$featureFlag] = array( - 'treatment' => $evalResult['treatment'], - 'config' => $evalResult['config'], - ); - } else { - $result[$featureFlag] = array('treatment' => TreatmentEnum::CONTROL, 'config' => null); - } - } - $this->registerData($impressions, $attributes, $metricName, $evaluationResults['latency']); - return $result; + return $this->processEvaluations( + $matchingKey, + $bucketingKey, + $operation, + $attributes, + $evaluationResults['evaluations'] + ); } catch (\Exception $e) { SplitApp::logger()->critical($operation . ' method is throwing exceptions'); SplitApp::logger()->critical($e->getMessage()); @@ -329,7 +336,6 @@ function ($feature) { }, $this->doEvaluationForTreatments( 'getTreatments', - Metrics::MNAME_SDK_GET_TREATMENTS, $key, $featureFlagNames, $attributes @@ -350,7 +356,6 @@ public function getTreatmentsWithConfig($key, $featureFlagNames, array $attribut try { return $this->doEvaluationForTreatments( 'getTreatmentsWithConfig', - Metrics::MNAME_SDK_GET_TREATMENTS_WITH_CONFIG, $key, $featureFlagNames, $attributes @@ -393,7 +398,7 @@ public function isTreatment($key, $featureFlagName, $treatment) public function track($key, $trafficType, $eventType, $value = null, $properties = null) { $key = InputValidator::validateTrackKey($key); - $trafficType = InputValidator::validateTrafficType($trafficType); + $trafficType = InputValidator::validateTrafficType($this->splitCache, $trafficType); $eventType = InputValidator::validateEventType($eventType); $value = InputValidator::validateValue($value); $properties = InputValidator::validProperties($properties); @@ -406,7 +411,7 @@ public function track($key, $trafficType, $eventType, $value = null, $properties try { $eventDTO = new EventDTO($key, $trafficType, $eventType, $value, $properties); $eventQueueMessage = new EventQueueMessage($this->queueMetadata, $eventDTO); - return EventsCache::addEvent($eventQueueMessage); + return $this->eventCache->addEvent($eventQueueMessage); } catch (\Exception $exception) { // @codeCoverageIgnoreStart SplitApp::logger()->error("Error happened when trying to add events"); @@ -416,4 +421,158 @@ public function track($key, $trafficType, $eventType, $value = null, $properties return false; } + + public function getTreatmentsByFlagSets($key, $flagSets, array $attributes = null) + { + try { + return array_map( + function ($feature) { + return $feature['treatment']; + }, + $this->doEvaluationByFlagSets( + 'getTreatmentsByFlagSets', + $key, + $flagSets, + $attributes + ) + ); + } catch (\Exception $e) { + SplitApp::logger()->critical('getTreatmentsByFlagSets method is throwing exceptions'); + return array(); + } + } + + public function getTreatmentsWithConfigByFlagSets($key, $flagSets, array $attributes = null) + { + try { + return $this->doEvaluationByFlagSets( + 'getTreatmentsWithConfigByFlagSets', + $key, + $flagSets, + $attributes + ); + } catch (\Exception $e) { + SplitApp::logger()->critical('getTreatmentsWithConfigByFlagSets method is throwing exceptions'); + return array(); + } + } + + public function getTreatmentsByFlagSet($key, $flagSet, array $attributes = null) + { + try { + return array_map( + function ($feature) { + return $feature['treatment']; + }, + $this->doEvaluationByFlagSets( + 'getTreatmentsByFlagSet', + $key, + array($flagSet), + $attributes + ) + ); + } catch (\Exception $e) { + SplitApp::logger()->critical('getTreatmentsByFlagSet method is throwing exceptions'); + return array(); + } + } + + public function getTreatmentsWithConfigByFlagSet($key, $flagSet, array $attributes = null) + { + try { + return $this->doEvaluationByFlagSets( + 'getTreatmentsWithConfigByFlagSet', + $key, + array($flagSet), + $attributes + ); + } catch (\Exception $e) { + SplitApp::logger()->critical('getTreatmentsWithConfigByFlagSet method is throwing exceptions'); + return array(); + } + } + + private function doInputValidationByFlagSets($key, $flagSets, array $attributes = null, $operation) + { + $key = InputValidator::validateKey($key, $operation); + if (is_null($key) || !InputValidator::validAttributes($attributes, $operation)) { + return null; + } + + $sets = FlagSetsValidator::areValid($flagSets, $operation); + if (is_null($sets)) { + return null; + } + + return array( + 'matchingKey' => $key['matchingKey'], + 'bucketingKey' => $key['bucketingKey'], + 'flagSets' => $sets, + ); + } + + private function doEvaluationByFlagSets($operation, $key, $flagSets, $attributes) + { + $inputValidation = $this->doInputValidationByFlagSets($key, $flagSets, $attributes, $operation); + if (is_null($inputValidation)) { + return array(); + } + + $matchingKey = $inputValidation['matchingKey']; + $bucketingKey = $inputValidation['bucketingKey']; + $flagSets = $inputValidation['flagSets']; + + try { + $evaluationResults = $this->evaluator->evaluateFeaturesByFlagSets( + $matchingKey, + $bucketingKey, + $flagSets, + $attributes + ); + return $this->processEvaluations( + $matchingKey, + $bucketingKey, + $operation, + $attributes, + $evaluationResults['evaluations'] + ); + } catch (\Exception $e) { + SplitApp::logger()->critical($operation . ' method is throwing exceptions'); + SplitApp::logger()->critical($e->getMessage()); + SplitApp::logger()->critical($e->getTraceAsString()); + } + return array(); + } + + private function processEvaluations( + $matchingKey, + $bucketingKey, + $operation, + $attributes, + $evaluations + ) { + $result = array(); + $impressions = array(); + foreach ($evaluations as $featureFlagName => $evalResult) { + if (InputValidator::isSplitFound($evalResult['impression']['label'], $featureFlagName, $operation)) { + // Creates impression + $impressions[] = $this->createImpression( + $matchingKey, + $featureFlagName, + $evalResult['treatment'], + $evalResult['impression']['changeNumber'], + $evalResult['impression']['label'], + $bucketingKey + ); + $result[$featureFlagName] = array( + 'treatment' => $evalResult['treatment'], + 'config' => $evalResult['config'], + ); + } else { + $result[$featureFlagName] = array('treatment' => TreatmentEnum::CONTROL, 'config' => null); + } + } + $this->registerData($impressions, $attributes); + return $result; + } } diff --git a/src/SplitIO/Sdk/ClientInterface.php b/src/SplitIO/Sdk/ClientInterface.php index d3b31eab..813da077 100644 --- a/src/SplitIO/Sdk/ClientInterface.php +++ b/src/SplitIO/Sdk/ClientInterface.php @@ -146,6 +146,124 @@ public function getTreatments($key, $featureFlagNames, array $attributes = null) */ public function getTreatmentsWithConfig($key, $featureFlagNames, array $attributes = null); + /** + * Returns an associative array which each key will be + * the treatment result and the config for each + * feature associated with flag sets passed as parameter. + * The set of treatments for a feature can be configured + * on the Split web console and the config for + * that treatment. + *

+ * The sdk returns the default treatment of this feature if: + *

    + *
  1. The feature was killed
  2. + *
  3. The id did not match any of the conditions in the + * feature roll-out plan
  4. + *
+ * The default treatment of a feature is set on the Split web + * console. + * + *

+ * This method does not throw any exceptions. + * It also never returns null. + * + * @param $key + * @param $flagSets + * @param $attributes + * @return array + */ + public function getTreatmentsWithConfigByFlagSets($key, $flagSets, array $attributes = null); + + /** + * Returns an associative array which each key will be + * the treatment result and the config for each + * feature associated with flag sets passed as parameter. + * The set of treatments for a feature can be configured + * on the Split web console and the config for + * that treatment. + *

+ * The sdk returns the default treatment of this feature if: + *

    + *
  1. The feature was killed
  2. + *
  3. The id did not match any of the conditions in the + * feature roll-out plan
  4. + *
+ * The default treatment of a feature is set on the Split web + * console. + * + *

+ * This method does not throw any exceptions. + * It also never returns null. + * + * @param $key + * @param $flagSets + * @param $attributes + * @return array + */ + public function getTreatmentsByFlagSets($key, $flagSets, array $attributes = null); + + /** + * Returns an associative array which each key will be + * the treatment result for each feature associated with + * flag set passed as parameter. + * The set of treatments for a feature can be configured + * on the Split web console. + * This method returns the string 'control' if: + *

    + *
  1. featureNames is invalid/li> + *
+ * 'control' is a reserved treatment, to highlight these + * exceptional circumstances. + * + *

+ * The sdk returns the default treatment of this feature if: + *

    + *
  1. The feature was killed
  2. + *
  3. The id did not match any of the conditions in the + * feature roll-out plan
  4. + *
+ * The default treatment of a feature is set on the Split web + * console. + * + *

+ * This method does not throw any exceptions. + * It also never returns null. + * + * @param $key + * @param $flagSet + * @param $attributes + * @return array + */ + public function getTreatmentsByFlagSet($key, $flagSet, array $attributes = null); + + /** + * Returns an associative array which each key will be + * the treatment result and the config for each + * feature associated with flag sets passed as parameter. + * The set of treatments for a feature can be configured + * on the Split web console and the config for + * that treatment. + *

+ * The sdk returns the default treatment of this feature if: + *

    + *
  1. The feature was killed
  2. + *
  3. The id did not match any of the conditions in the + * feature roll-out plan
  4. + *
+ * The default treatment of a feature is set on the Split web + * console. + * + *

+ * This method does not throw any exceptions. + * It also never returns null. + * + * @param $key + * @param $flagSet + * @param $attributes + * @return array + */ + public function getTreatmentsWithConfigByFlagSet($key, $flagSet, array $attributes = null); + /** * A short-hand for *

@@ -168,8 +286,9 @@ public function isTreatment($key, $featureFlagName, $treatment);
      * @param $key
      * @param $trafficType
      * @param $eventType
-     * @param null $value
+     * @param $value
+     * @param $properties
      * @return boolean
      */
-    public function track($key, $trafficType, $eventType, $value = null);
+    public function track($key, $trafficType, $eventType, $value = null, $properties = null);
 }
diff --git a/src/SplitIO/Sdk/Evaluator.php b/src/SplitIO/Sdk/Evaluator.php
index 8377f9d8..6853850c 100644
--- a/src/SplitIO/Sdk/Evaluator.php
+++ b/src/SplitIO/Sdk/Evaluator.php
@@ -3,7 +3,7 @@
 namespace SplitIO\Sdk;
 
 use SplitIO\Component\Cache\SplitCache;
-use SplitIO\Component\Common\Di;
+use SplitIO\Component\Cache\SegmentCache;
 use SplitIO\Engine;
 use SplitIO\Grammar\Condition\Partition\TreatmentEnum;
 use SplitIO\Grammar\Split;
@@ -13,15 +13,22 @@
 
 class Evaluator
 {
+    /**
+     * @var \SplitIO\Component\Cache\SplitCache
+     */
+    private $splitCache;
 
-    private $splitCache = null;
+    /**
+     * @var \SplitIO\Component\Cache\SegmentCache
+     */
+    private $segmentCache;
 
-    public function __construct()
+    public function __construct(SplitCache $splitCache, SegmentCache $segmentCache)
     {
-        $this->splitCache = new SplitCache();
+        $this->splitCache = $splitCache;
+        $this->segmentCache = $segmentCache;
     }
 
-
     private function fetchSplit($featureName)
     {
         $splitCachedItem = $this->splitCache->getSplit($featureName);
@@ -47,6 +54,25 @@ private function fetchSplits($featureNames)
         return $toReturn;
     }
 
+    private function fetchFeatureFlagNamesByFlagSets($flagSets)
+    {
+        $namesByFlagSets = $this->splitCache->getNamesByFlagSets($flagSets);
+        $toReturn = array();
+
+        foreach ($namesByFlagSets as $flagSet => $flagNames) {
+            if (empty($flagNames)) {
+                SplitApp::logger()->warning("you passed $flagSet Flag Set that does not contain" .
+                'cached feature flag names, please double check what Flag Sets are in use in the' .
+                'Split user interface.');
+                continue;
+            }
+
+            array_push($toReturn, ...$flagNames);
+        }
+
+        return array_values(array_unique($toReturn));
+    }
+
     public function evaluateFeature($matchingKey, $bucketingKey, $featureName, array $attributes = null)
     {
         $timeStart = Metrics::startMeasuringLatency();
@@ -70,8 +96,21 @@ public function evaluateFeatures($matchingKey, $bucketingKey, array $featureName
         return $toReturn;
     }
 
+    public function evaluateFeaturesByFlagSets($matchingKey, $bucketingKey, array $flagSets, array $attributes = null)
+    {
+        $timeStart = Metrics::startMeasuringLatency();
+        $featureFlagNames = $this->fetchFeatureFlagNamesByFlagSets($flagSets);
+        $toReturn = $this->evaluateFeatures($matchingKey, $bucketingKey, $featureFlagNames, $attributes);
+        $toReturn['latency'] = Metrics::calculateLatency($timeStart);
+        return $toReturn;
+    }
+
     private function evalTreatment($key, $bucketingKey, $split, array $attributes = null)
     {
+        $context = array(
+            'segmentCache' => $this->segmentCache,
+            'evaluator' => $this,
+        );
         $result = array(
             'treatment' => TreatmentEnum::CONTROL,
             'impression' => array(
@@ -98,7 +137,7 @@ private function evalTreatment($key, $bucketingKey, $split, array $attributes =
                 return $result;
             }
 
-            $evaluationResult = Engine::getTreatment($key, $bucketingKey, $split, $attributes);
+            $evaluationResult = Engine::getTreatment($key, $bucketingKey, $split, $attributes, $context);
             if (!is_null($evaluationResult[Engine::EVALUATION_RESULT_TREATMENT])) {
                 $result['treatment'] = $evaluationResult[Engine::EVALUATION_RESULT_TREATMENT];
                 $result['impression']['label'] = $evaluationResult[Engine::EVALUATION_RESULT_LABEL];
diff --git a/src/SplitIO/Sdk/Factory/SplitFactory.php b/src/SplitIO/Sdk/Factory/SplitFactory.php
index 87852808..0058ee50 100644
--- a/src/SplitIO/Sdk/Factory/SplitFactory.php
+++ b/src/SplitIO/Sdk/Factory/SplitFactory.php
@@ -1,11 +1,13 @@
 options = $options;
+        $this->cache = $cache;
 
-        //Block until ready
-        $this->doBUR();
-
-        $this->client = new Client($options);
+        $eventCache = new EventsCache($cache);
+        $impressionCache = new ImpressionCache($cache);
+        $segmentCache = new SegmentCache($cache);
+        $splitCache = new SplitCache($cache);
 
-        $this->manager = new SplitManager();
-    }
-
-    private function doBUR()
-    {
-        /*
-            Deprecated
-            $ready =  (isset($this->options['ready']) && $this->options['ready'] > 0) ? $this->options['ready'] : null;
+        $this->client = new Client(array(
+            'splitCache' => $splitCache,
+            'segmentCache' => $segmentCache,
+            'impressionCache' => $impressionCache,
+            'eventCache' => $eventCache,
+        ), $options);
 
-            //Block Until Ready
-            if ($ready) {
-                if (!$this->blockUntilReady($ready)) {
-                    throw new TimeOutException("Cache data is not ready yet");
-                }
-            }
-        */
+        $this->manager = new SplitManager($splitCache);
     }
 
     /**
diff --git a/src/SplitIO/Sdk/LocalhostClient.php b/src/SplitIO/Sdk/LocalhostClient.php
index ac86afa6..139446a1 100644
--- a/src/SplitIO/Sdk/LocalhostClient.php
+++ b/src/SplitIO/Sdk/LocalhostClient.php
@@ -253,8 +253,32 @@ public function isTreatment($key, $featureFlagName, $treatment)
     /**
      * @inheritdoc
      */
-    public function track($key, $trafficType, $eventType, $value = null)
+    public function track($key, $trafficType, $eventType, $value = null, $properties = null)
     {
         return true;
     }
+
+    public function getTreatmentsWithConfigByFlagSets($key, $flagSets, array $attributes = null)
+    {
+        // no-op
+        return array();
+    }
+    
+    public function getTreatmentsByFlagSets($key, $flagSets, array $attributes = null)
+    {
+        // no-op
+        return array();
+    }
+
+    public function getTreatmentsWithConfigByFlagSet($key, $flagSet, array $attributes = null)
+    {
+        // no-op
+        return array();
+    }
+    
+    public function getTreatmentsByFlagSet($key, $flagSet, array $attributes = null)
+    {
+        // no-op
+        return array();
+    }
 }
diff --git a/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php b/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php
index 2bb3f7a5..0d138faa 100644
--- a/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php
+++ b/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php
@@ -74,6 +74,11 @@ public function splits()
         return $_splits;
     }
 
+    /**
+     * Return split
+     * @param mixed $featureName
+     * @return SplitView|null
+     */
     public function split($featureFlagName)
     {
         $featureFlagName = InputValidator::validateFeatureFlagName($featureFlagName, 'split');
@@ -90,7 +95,9 @@ public function split($featureFlagName)
                 false,
                 $this->splits[$featureFlagName]["treatments"],
                 0,
-                $configs
+                $configs,
+                null,
+                array()
             );
         }
 
diff --git a/src/SplitIO/Sdk/Manager/SplitManager.php b/src/SplitIO/Sdk/Manager/SplitManager.php
index 4a755eaf..44554aa1 100644
--- a/src/SplitIO/Sdk/Manager/SplitManager.php
+++ b/src/SplitIO/Sdk/Manager/SplitManager.php
@@ -9,10 +9,19 @@
 
 class SplitManager implements SplitManagerInterface
 {
+    /**
+     * @var \SplitIO\Component\Cache\SplitCache
+     */
+    private $splitCache;
+
+    public function __construct(SplitCache $splitCache)
+    {
+        $this->splitCache = $splitCache;
+    }
+
     public function splitNames()
     {
-        $cache = new SplitCache();
-        return $cache->getSplitNames();
+        return $this->splitCache->getSplitNames();
     }
 
     /**
@@ -20,8 +29,7 @@ public function splitNames()
      */
     public function splits()
     {
-        $cache = new SplitCache();
-        $rawSplits = $cache->getAllSplits();
+        $rawSplits = $this->splitCache->getAllSplits();
         return array_map([self::class, 'parseSplitView'], $rawSplits);
     }
 
@@ -36,8 +44,7 @@ public function split($featureFlagName)
             return null;
         }
 
-        $cache = new SplitCache();
-        $raw = $cache->getSplit($featureFlagName);
+        $raw = $this->splitCache->getSplit($featureFlagName);
         if (is_null($raw)) {
             SplitApp::logger()->warning("split: you passed " . $featureFlagName
             . " that does not exist in this environment, please double check what"
@@ -49,7 +56,7 @@ public function split($featureFlagName)
 
     /**
      * @param $splitRepresentation
-     * @return SplitView
+     * @return null|SplitView
      */
     private static function parseSplitView($splitRepresentation)
     {
@@ -58,7 +65,6 @@ private static function parseSplitView($splitRepresentation)
         }
 
         $split = new Split(json_decode($splitRepresentation, true));
-
         $configs = !is_null($split->getConfigurations()) ? $split->getConfigurations() : new StdClass;
 
         return new SplitView(
@@ -67,7 +73,9 @@ private static function parseSplitView($splitRepresentation)
             $split->killed(),
             $split->getTreatments(),
             $split->getChangeNumber(),
-            $configs
+            $configs,
+            $split->getDefaultTratment(),
+            $split->getSets()
         );
     }
 }
diff --git a/src/SplitIO/Sdk/Manager/SplitView.php b/src/SplitIO/Sdk/Manager/SplitView.php
index 493bf068..558635db 100644
--- a/src/SplitIO/Sdk/Manager/SplitView.php
+++ b/src/SplitIO/Sdk/Manager/SplitView.php
@@ -9,6 +9,8 @@ class SplitView
     private $treatments;
     private $changeNumber;
     private $configs;
+    private $defaultTreatment;
+    private $sets;
 
     /**
      * SplitView constructor.
@@ -18,15 +20,27 @@ class SplitView
      * @param $treatments
      * @param $changeNumber
      * @param $configurations
+     * @param $defaultTreatment
+     * @param $sets
      */
-    public function __construct($name, $trafficType, $killed, $treatments, $changeNumber, $configs)
-    {
+    public function __construct(
+        $name,
+        $trafficType,
+        $killed,
+        $treatments,
+        $changeNumber,
+        $configs,
+        $defaultTreatment,
+        $sets
+    ) {
         $this->name = $name;
         $this->trafficType = $trafficType;
         $this->killed = $killed;
         $this->treatments = $treatments;
         $this->changeNumber = $changeNumber;
         $this->configs = $configs;
+        $this->defaultTreatment = $defaultTreatment;
+        $this->sets = $sets;
     }
 
 
@@ -125,4 +139,36 @@ public function setConfigs($configs)
     {
         $this->configs = $configs;
     }
+
+    /**
+     * @param mixed $defaultTreatment
+     */
+    public function setDefaultTreatment($defaultTreatment)
+    {
+        $this->defaultTreatment = $defaultTreatment;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getDefaultTreatment()
+    {
+        return $this->defaultTreatment;
+    }
+
+    /**
+     * @param mixed $sets
+     */
+    public function setSets($sets)
+    {
+        $this->sets = $sets;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getSets()
+    {
+        return $this->sets;
+    }
 }
diff --git a/src/SplitIO/Sdk/Validator/FlagSetsValidator.php b/src/SplitIO/Sdk/Validator/FlagSetsValidator.php
new file mode 100644
index 00000000..69893d84
--- /dev/null
+++ b/src/SplitIO/Sdk/Validator/FlagSetsValidator.php
@@ -0,0 +1,62 @@
+error($operation . ': FlagSets must be a non-empty list.');
+            return array();
+        }
+
+        $sanitized = [];
+        foreach ($flagSets as $flagSet) {
+            $sanitizedFlagSet = self::sanitize($flagSet, $operation);
+            if (!is_null($sanitizedFlagSet)) {
+                array_push($sanitized, $sanitizedFlagSet);
+            }
+        }
+
+        return array_values(array_unique($sanitized));
+    }
+
+    private static function sanitize($flagSet, $operation)
+    {
+        if ($flagSet == null) {
+            return null;
+        }
+
+        if (!is_string($flagSet)) {
+            SplitApp::logger()->error($operation . ': FlagSet must be a string and not null. ' .
+            $flagSet . ' was discarded.');
+            return null;
+        }
+
+        $trimmed = trim($flagSet);
+        if ($trimmed !== $flagSet) {
+            SplitApp::logger()->warning($operation . ': Flag Set name "' . $flagSet .
+                '" has extra whitespace, trimming.');
+        }
+        $toLowercase = strtolower($trimmed);
+        if ($toLowercase !== $trimmed) {
+            SplitApp::logger()->warning($operation . ': Flag Set name "' . $flagSet .
+                '" should be all lowercase - converting string to lowercase.');
+        }
+        if (!preg_match(REG_EXP_FLAG_SET, $toLowercase)) {
+            SplitApp::logger()->warning($operation . ': you passed "' . $flagSet .
+                '", Flag Set must adhere to the regular expressions {' .REG_EXP_FLAG_SET .
+                '} This means a Flag Set must start with a letter or number, be in lowercase, alphanumeric and ' .
+                'have a max length of 50 characters. "' . $flagSet . '" was discarded.');
+            return null;
+        }
+
+        return $toLowercase;
+    }
+}
diff --git a/src/SplitIO/Sdk/Validator/InputValidator.php b/src/SplitIO/Sdk/Validator/InputValidator.php
index de86f1ab..9862da6c 100644
--- a/src/SplitIO/Sdk/Validator/InputValidator.php
+++ b/src/SplitIO/Sdk/Validator/InputValidator.php
@@ -5,8 +5,6 @@
 use SplitIO\Split as SplitApp;
 use SplitIO\Sdk\Key;
 use SplitIO\Component\Utils as SplitIOUtils;
-use SplitIO\Component\Cache\SplitCache;
-use SplitIO\Grammar\Condition\Partition\TreatmentEnum;
 use SplitIO\Sdk\Impressions\ImpressionLabel;
 
 const MAX_LENGTH = 250;
@@ -69,7 +67,7 @@ private static function checkIsNull($value, $name, $nameType, $operation)
     private static function checkIsEmpty($value, $name, $nameType, $operation)
     {
         $trimmed = trim($value);
-        if (empty($trimmed)) {
+        if (0 == strlen($trimmed)) {
             SplitApp::logger()->critical($operation . ": you passed an empty " . $name . ", " . $nameType .
                 " must be a non-empty string.");
             return true;
@@ -175,7 +173,7 @@ public static function validateTrackKey($key)
      * @param $trafficType
      * @return string|null
      */
-    public static function validateTrafficType($trafficType)
+    public static function validateTrafficType(\SplitIO\Component\Cache\SplitCache $splitCache, $trafficType)
     {
         if (!self::validString($trafficType, 'traffic type', 'traffic type', 'track')) {
             return null;
@@ -185,7 +183,6 @@ public static function validateTrafficType($trafficType)
             SplitApp::logger()->warning("track: '" . $trafficType . "' should be all lowercase - converting string to "
                 . "lowercase.");
         }
-        $splitCache = new SplitCache();
         if (!$splitCache->trafficTypeExists($toLowercase)) {
             SplitApp::logger()->warning("track: Traffic Type '". $toLowercase . "' does not have any corresponding "
                 . "Splits in this environment, make sure you’re tracking your events to a valid traffic type "
@@ -265,7 +262,7 @@ function ($featureFlagName) use ($operation) {
                 )
             )
         );
-        if (empty($filteredArray)) {
+        if (0 == count($filteredArray)) {
             SplitApp::logger()->critical($operation . ': featureFlagNames must be a non-empty array.');
             return null;
         }
diff --git a/src/SplitIO/Split.php b/src/SplitIO/Split.php
index 087896dd..7e17da36 100644
--- a/src/SplitIO/Split.php
+++ b/src/SplitIO/Split.php
@@ -1,24 +1,16 @@
 debug("no impressions were sent");
-                return null;
-            }
-            $impressionCache = new ImpressionCache();
-            $toStore = (is_array($impressions)) ? $impressions : array($impressions);
-            return $impressionCache->logImpressions(
-                $toStore,
-                $metadata
-            );
-        } catch (\Exception $e) {
-            Di::getLogger()->warning('Unable to write impression back to redis.');
-            Di::getLogger()->warning($e->getMessage());
-        }
-    }
-}
diff --git a/src/SplitIO/Version.php b/src/SplitIO/Version.php
index 67a6c644..2f4d6399 100644
--- a/src/SplitIO/Version.php
+++ b/src/SplitIO/Version.php
@@ -3,5 +3,5 @@
 
 class Version
 {
-    const CURRENT = '7.1.7';
+    const CURRENT = '8.0.0-rc4';
 }
diff --git a/src/SplitIO/functions.php b/src/SplitIO/functions.php
index 0ef2da0f..c06c72ea 100644
--- a/src/SplitIO/functions.php
+++ b/src/SplitIO/functions.php
@@ -1,9 +1,6 @@
 getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
 
-    public function testRedisWithNullValues()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', "Wrong configuration of redis.");
-
-        $predis = new PRedis(array());
-    }
-
     public function testRedisWithOnlyParameters()
     {
         $predis = new PRedis(array(
@@ -81,243 +71,17 @@ public function testRedisWithParametersAndPrefix()
         $predisClient->del('test-redis-assertion.this_is_a_test_key');
     }
 
-    public function testRedisWithParametersPrefixAndSentinels()
-    {
-        $predis = new PRedis(array(
-            'parameters' => array(
-                'scheme' => 'tcp',
-                'host' => 'localhost',
-                'port' => 6379,
-                'timeout' => 881,
-                'database' => 0
-            ),
-            'sentinels' => array('something', 'other'),
-            'options' => array(
-                'prefix' => 'test-redis-assertion'
-            )
-        ));
-        $predisClient = new \Predis\Client([
-            'host' => REDIS_HOST,
-            'port' => REDIS_PORT,
-        ]);
-        $predisClient->set('test-redis-assertion.this_is_a_test_key', 'this-is-a-test-value');
-
-        $value = $predis->get('this_is_a_test_key');
-        $this->assertEquals('this-is-a-test-value', $value);
-
-        $predisClient->del('test-redis-assertion.this_is_a_test_key');
-    }
-
-    public function testRedisWithEmptySentinels()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', 'At least one sentinel is required.');
-
-        $predis = new PRedis(array(
-            'sentinels' => array(),
-            'options' => array(
-                'distributedStrategy' => 'sentinel'
-            )
-        ));
-    }
-
-    public function testRedisWithSentinelsWithoutOptions()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', "Wrong configuration of redis.");
-
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                '127.0.0.1'
-            ),
-        ));
-    }
-
-    public function testRedisWithSentinelsWithoutReplicationOption()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', "Wrong configuration of redis.");
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                '127.0.0.1'
-            ),
-            'options' => array()
-        ));
-    }
-
-    public function testRedisWithSentinelsWithWrongReplicationOption()
-    {
-        $logger = $this->getMockedLogger();
-        $logger->expects($this->once())
-            ->method('warning')
-            ->with($this->equalTo("'replication' option was deprecated please use 'distributedStrategy'"));
-
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', "Wrong configuration of redis 'distributedStrategy'.");
-
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                '127.0.0.1'
-            ),
-            'options' => array(
-                'replication' => 'test'
-            )
-        ));
-    }
-
-    public function testRedisWithSentinelsWithoutServiceOption()
-    {
-        $logger = $this->getMockedLogger();
-        $logger->expects($this->once())
-            ->method('warning')
-            ->with($this->equalTo("'replication' option was deprecated please use 'distributedStrategy'"));
-
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            'Master name is required in replication mode for sentinel.'
-        );
-
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                '127.0.0.1'
-            ),
-            'options' => array(
-                'replication' => 'sentinel'
-            )
-        ));
-    }
-
-    public function testRedisWithWrongTypeOfSentinels()
-    {
-        $logger = $this->getMockedLogger();
-
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', 'sentinels must be an array.');
-
-        $predis = new PRedis(array(
-            'sentinels' => "test",
-            'options' => array(
-                'replication' => 'sentinel'
-            )
-        ));
-    }
-
-    public function testRedisSentinelWithWrongRedisDistributedStrategy()
-    {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "Wrong configuration of redis 'distributedStrategy'."
-        );
-
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                '127.0.0.1'
-            ),
-            'options' => array(
-                'distributedStrategy' => 'test'
-            )
-        ));
-    }
-
-    public function testRedisWithSentinels()
-    {
-        $logger = $this->getMockedLogger();
-        $logger->expects($this->once())
-            ->method('warning')
-            ->with($this->equalTo("'replication' option was deprecated please use 'distributedStrategy'"));
-
-        $this->expectException('\Predis\ClientException');
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
-            'options' => array(
-                'replication' => 'sentinel',
-                'service' => 'master'
-            )
-        ));
-
-        $predis->get('this_is_a_test_key');
-    }
-
-    public function testRedisWithSentinelsAndDistributedStrategy()
-    {
-        $this->expectException('\Predis\Response\ServerException');
-        $predis = new PRedis(array(
-            'sentinels' => array(
-                'tcp:/MYIP:26379?timeout=3'
-            ),
-            'options' => array(
-                'service' => 'master',
-                'distributedStrategy' => 'sentinel'
-            )
-        ));
-
-        $predis->get('this_is_a_test_key');
-    }
-
-    public function testRedisWithEmptyClusters()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', 'At least one clusterNode is required.');
-
-        $predis = new PRedis(array(
-            'clusterNodes' => array(),
-            'options' => array(
-                'distributedStrategy' => 'cluster',
-                'keyHashTag' => '{TEST}'
-            )
-        ));
-    }
-
-    public function testRedisWithClustersWithoutOptions()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', "Wrong configuration of redis.");
-
-        $predis = new PRedis(array(
-            'clusterNodes' => array(
-                '127.0.0.1'
-            ),
-        ));
-    }
-
-    public function testRedisWithWrongTypeOfClusters()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException', 'clusterNodes must be an array.');
-
-        $predis = new PRedis(array(
-            'clusterNodes' => "test",
-            'options' => array(
-                'distributedStrategy' => 'cluster',
-                'keyHashTag' => '{TEST}'
-            )
-        ));
-    }
-
-    public function testRedisClusterWithWrongRedisDistributedStrategy()
-    {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "Wrong configuration of redis 'distributedStrategy'."
-        );
-
-        $predis = new PRedis(array(
-            'clusterNodes' => array(
-                '127.0.0.1'
-            ),
-            'options' => array(
-                'distributedStrategy' => 'test'
-            )
-        ));
-    }
-
     public function testRedisWithInvalidKeyHashtagInClusters()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTag is not valid."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTag is not valid.");
 
         $predis = new PRedis(array(
             'clusterNodes' => array(
                 'tcp://MYIP:26379?timeout=3'
             ),
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTag' => '{TEST'
             )
         ));
@@ -327,17 +91,15 @@ public function testRedisWithInvalidKeyHashtagInClusters()
 
     public function testRedisWithInvalidBeginingKeyHashtagInClusters()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTag is not valid."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTag is not valid.");
 
         $predis = new PRedis(array(
             'clusterNodes' => array(
                 'tcp://MYIP:26379?timeout=3'
             ),
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'redis',
                 'keyHashTag' => 'TEST}'
             )
         ));
@@ -347,17 +109,15 @@ public function testRedisWithInvalidBeginingKeyHashtagInClusters()
 
     public function testRedisWithWrongTypeKeyHashtagInClusters()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTag must be string."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTag must be string.");
 
         $predis = new PRedis(array(
             'clusterNodes' => array(
                 'tcp://MYIP:26379?timeout=3'
             ),
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTag' => array()
             )
         ));
@@ -367,17 +127,15 @@ public function testRedisWithWrongTypeKeyHashtagInClusters()
 
     public function testRedisWithWrongLengthKeyHashtagInClusters()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTag is not valid."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTag is not valid.");
 
         $predis = new PRedis(array(
             'clusterNodes' => array(
                 'tcp://MYIP:26379?timeout=3'
             ),
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTag' => "{}"
             )
         ));
@@ -387,15 +145,13 @@ public function testRedisWithWrongLengthKeyHashtagInClusters()
 
     public function testRedisWithClusters()
     {
-        $this->expectException('\Predis\ClientException');
+        $this->expectException('\Predis\Connection\ConnectionException');
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTag' => '{TEST}'
-            )
+            ),
         ));
 
         $predis->get('this_is_a_test_key');
@@ -403,13 +159,11 @@ public function testRedisWithClusters()
 
     public function testRedisWithoutCustomKeyHashtagClusters()
     {
-        $this->expectException('\Predis\ClientException');
+        $this->expectException('\Predis\Connection\ConnectionException');
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
             )
         ));
 
@@ -418,16 +172,13 @@ public function testRedisWithoutCustomKeyHashtagClusters()
 
     public function testRedisWithClustersKeyHashTags()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTags must be array."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTags must be an array.");
+
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTags' => '{TEST}'
             )
         ));
@@ -437,16 +188,13 @@ public function testRedisWithClustersKeyHashTags()
 
     public function testRedisWithClustersKeyHashTagsInvalid()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTags size is zero after filtering valid elements."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTags size is zero after filtering valid elements.");
+
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTags' => array(1, 2)
             )
         ));
@@ -456,16 +204,13 @@ public function testRedisWithClustersKeyHashTagsInvalid()
 
     public function testRedisWithClustersKeyHashTagsInvalidHashTags()
     {
-        $this->expectException(
-            'SplitIO\Component\Cache\Storage\Exception\AdapterException',
-            "keyHashTags size is zero after filtering valid elements."
-        );
+        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
+        $this->expectExceptionMessage("keyHashTags size is zero after filtering valid elements.");
+
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTags' => array("one", "two", "three")
             )
         ));
@@ -475,13 +220,11 @@ public function testRedisWithClustersKeyHashTagsInvalidHashTags()
 
     public function testRedisWithClustersKeyHashTagsValid()
     {
-        $this->expectException('\Predis\ClientException');
+        $this->expectException('\Predis\Connection\ConnectionException');
         $predis = new PRedis(array(
-            'clusterNodes' => array(
-                'tcp://MYIP:26379?timeout=3'
-            ),
+            'parameters' => ['tcp://10.0.0.1', 'tcp://10.0.0.2', 'tcp://10.0.0.3'],
             'options' => array(
-                'distributedStrategy' => 'cluster',
+                'cluster' => 'predis',
                 'keyHashTags' => array("{one}", "{two}", "{three}")
             )
         ));
@@ -489,32 +232,6 @@ public function testRedisWithClustersKeyHashTagsValid()
         $predis->get('this_is_a_test_key');
     }
 
-    public function testRedisSSLWithClusterFails()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
-        $predis = new PRedis(array(
-            'options' => array(
-                'distributedStrategy' => 'cluster',
-            ),
-            'parameters' => array(
-                'tls' => array(),
-            ),
-        ));
-    }
-
-    public function testRedisSSLWithSentinelFails()
-    {
-        $this->expectException('SplitIO\Component\Cache\Storage\Exception\AdapterException');
-        $predis = new PRedis(array(
-            'options' => array(
-                'distributedStrategy' => 'sentinel',
-            ),
-            'parameters' => array(
-                'tls' => array(),
-            ),
-        ));
-    }
-
     public function testRedisWithWrongPrefix()
     {
         $predis = new PRedis(array(
diff --git a/tests/Suite/Attributes/SdkAttributesTest.php b/tests/Suite/Attributes/SdkAttributesTest.php
index 6b3e050c..1ce2af0f 100644
--- a/tests/Suite/Attributes/SdkAttributesTest.php
+++ b/tests/Suite/Attributes/SdkAttributesTest.php
@@ -1,20 +1,12 @@
 assertTrue(is_string(\SplitIO\version()));
 
diff --git a/tests/Suite/Attributes/files/splitChanges.json b/tests/Suite/Attributes/files/splitChanges.json
index 63dc2398..0460ef22 100644
--- a/tests/Suite/Attributes/files/splitChanges.json
+++ b/tests/Suite/Attributes/files/splitChanges.json
@@ -10,6 +10,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -57,6 +58,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "on",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -130,6 +132,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -187,6 +190,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -233,6 +237,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -273,6 +278,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -311,6 +317,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -353,6 +360,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -395,6 +403,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -437,6 +446,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -478,6 +488,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -519,6 +530,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -560,6 +572,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -601,6 +614,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -642,6 +656,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -683,6 +698,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -724,6 +740,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -765,6 +782,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -806,6 +824,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -847,6 +866,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -888,6 +908,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -929,6 +950,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -970,6 +992,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1011,6 +1034,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1052,6 +1076,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1093,6 +1118,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1134,6 +1160,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "on",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1176,6 +1203,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1244,6 +1272,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1286,6 +1315,7 @@
             "status": "ACTIVE",
             "killed": true,
             "defaultTreatment": "defTreatment",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -1328,6 +1358,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
diff --git a/tests/Suite/Component/KeysStaticMethodsTest.php b/tests/Suite/Component/KeysStaticMethodsTest.php
index 41e9f331..9cb3d715 100644
--- a/tests/Suite/Component/KeysStaticMethodsTest.php
+++ b/tests/Suite/Component/KeysStaticMethodsTest.php
@@ -2,10 +2,7 @@
 
 namespace SplitIO\Test\Suite\Component;
 
-use SplitIO\Component\Cache\ImpressionCache;
 use SplitIO\Component\Cache\SplitCache;
-use SplitIO\Component\Cache\SegmentCache;
-use SplitIO\Component\Cache\TrafficTypeCache;
 
 class KeyTest extends \PHPUnit\Framework\TestCase
 {
diff --git a/tests/Suite/Component/TrafficTypeTests.php b/tests/Suite/Component/TrafficTypeTests.php
index 69e5c781..701e3e5d 100644
--- a/tests/Suite/Component/TrafficTypeTests.php
+++ b/tests/Suite/Component/TrafficTypeTests.php
@@ -3,7 +3,7 @@
 
 use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Test\Suite\Redis\ReflectiveTools;
-use SplitIO\Component\Common\Di;
+use SplitIO\Component\Common\Context;
 
 class TrafficTypeTest extends \PHPUnit\Framework\TestCase
 {
@@ -13,11 +13,11 @@ private function getMockedLogger()
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
                 'alert', 'notice', 'write', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+            Context::setLogger($logger);
 
         return $logger;
     }
@@ -42,10 +42,11 @@ public function testTrafficTypeWarning()
 
         $this->assertEquals($keyTrafficType, 'SPLITIO.trafficType.abc');
 
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $cachePool = ReflectiveTools::cacheFromFactory($splitFactory);
         $redisClient->del($keyTrafficType);
 
-        $splitCache = new SplitCache();
+        $splitCache = new SplitCache($cachePool);
 
         $this->assertEquals($splitCache->trafficTypeExists("abc"), false);
 
diff --git a/tests/Suite/DynamicConfigurations/EvaluatorTest.php b/tests/Suite/DynamicConfigurations/EvaluatorTest.php
index 9bc75489..c1dfbfd3 100644
--- a/tests/Suite/DynamicConfigurations/EvaluatorTest.php
+++ b/tests/Suite/DynamicConfigurations/EvaluatorTest.php
@@ -1,11 +1,9 @@
 del('SPLITIO.split.mysplittest');
-
         $redisClient->set('SPLITIO.split.mysplittest', $this->split1);
 
-        $evaluator = new Evaluator($options);
+        $segmentCache = new SegmentCache($cachePool);
+        $splitCache = new SplitCache($cachePool);
+        $evaluator = new Evaluator($splitCache, $segmentCache);
+
         $result = $evaluator->evaluateFeature('test', '', 'mysplittest', null);
 
         $this->assertEquals('off', $result['treatment']);
@@ -97,15 +97,16 @@ public function testSplitWithConfigurations()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
 
         $redisClient->del('SPLITIO.split.mysplittest2');
-
         $redisClient->set('SPLITIO.split.mysplittest2', $this->split2);
 
-        $evaluator = new Evaluator($options);
+        $segmentCache = new SegmentCache($cachePool);
+        $splitCache = new SplitCache($cachePool);
+        $evaluator = new Evaluator($splitCache, $segmentCache);
         $result = $evaluator->evaluateFeature('test', '', 'mysplittest2', null);
 
         $this->assertEquals('on', $result['treatment']);
@@ -125,15 +126,16 @@ public function testSplitWithConfigurationsButKilled()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
 
         $redisClient->del('SPLITIO.split.mysplittest3');
-
         $redisClient->set('SPLITIO.split.mysplittest3', $this->split3);
 
-        $evaluator = new Evaluator($options);
+        $segmentCache = new SegmentCache($cachePool);
+        $splitCache = new SplitCache($cachePool);
+        $evaluator = new Evaluator($splitCache, $segmentCache);
         $result = $evaluator->evaluateFeature('test', '', 'mysplittest3', null);
 
         $this->assertEquals('killed', $result['treatment']);
@@ -153,15 +155,16 @@ public function testSplitWithConfigurationsButKilledWithConfigsOnDefault()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
 
         $redisClient->del('SPLITIO.split.mysplittest4');
-
         $redisClient->set('SPLITIO.split.mysplittest4', $this->split4);
 
-        $evaluator = new Evaluator($options);
+        $segmentCache = new SegmentCache($cachePool);
+        $splitCache = new SplitCache($cachePool);
+        $evaluator = new Evaluator($splitCache, $segmentCache);
         $result = $evaluator->evaluateFeature('test', '', 'mysplittest4', null);
 
         $this->assertEquals('killed', $result['treatment']);
@@ -169,4 +172,51 @@ public function testSplitWithConfigurationsButKilledWithConfigsOnDefault()
 
         $redisClient->del('SPLITIO.split.mysplittest4');
     }
+
+    public function testEvaluateFeaturesByFlagSets()
+    {
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
+
+        $redisClient->del('SPLITIO.flagSet.set_1');
+        $redisClient->del('SPLITIO.flagSet.set_2');
+        $redisClient->del('SPLITIO.split.mysplittest');
+        $redisClient->del('SPLITIO.split.mysplittest2');
+        $redisClient->del('SPLITIO.split.mysplittest4');
+        
+        $redisClient->set('SPLITIO.split.mysplittest', $this->split1);
+        $redisClient->set('SPLITIO.split.mysplittest2', $this->split2);
+        $redisClient->set('SPLITIO.split.mysplittest4', $this->split4);
+        $redisClient->sadd('SPLITIO.flagSet.set_1', 'mysplittest2');
+        $redisClient->sadd('SPLITIO.flagSet.set_2', 'mysplittest2');
+        $redisClient->sadd('SPLITIO.flagSet.set_2', 'mysplittest4');
+        $redisClient->sadd('SPLITIO.flagSet.set_5', 'mysplittest');
+
+        $segmentCache = new SegmentCache($cachePool);
+        $splitCache = new SplitCache($cachePool);
+        $evaluator = new Evaluator($splitCache, $segmentCache);
+
+        $result = $evaluator->evaluateFeaturesByFlagSets('test', '', ['set_1', 'set_2', 'set_3']);
+
+        $this->assertEquals('on', $result['evaluations']['mysplittest2']['treatment']);
+        $this->assertEquals('killed', $result['evaluations']['mysplittest4']['treatment']);
+        $this->assertFalse(array_key_exists('mysplittest', $result['evaluations']));
+        $this->assertGreaterThan(0, $result['latency']);
+
+        $redisClient->del('SPLITIO.flagSet.set_1');
+        $redisClient->del('SPLITIO.flagSet.set_2');
+        $redisClient->del('SPLITIO.split.mysplittest');
+        $redisClient->del('SPLITIO.split.mysplittest2');
+        $redisClient->del('SPLITIO.split.mysplittest4');
+    }
 }
diff --git a/tests/Suite/DynamicConfigurations/SplitTest.php b/tests/Suite/DynamicConfigurations/SplitTest.php
index 44bb0e3b..ec918375 100644
--- a/tests/Suite/DynamicConfigurations/SplitTest.php
+++ b/tests/Suite/DynamicConfigurations/SplitTest.php
@@ -1,10 +1,8 @@
 del('SPLITIO.split.mysplittest');
-
         $redisClient->set('SPLITIO.split.mysplittest', $this->split1);
 
-        $splitCache = new SplitCache();
+        $splitCache = new SplitCache($cachePool);
         $splitRepresentation = $splitCache->getSplit('mysplittest');
 
         $split = new Split(json_decode($splitRepresentation, true));
@@ -73,15 +70,15 @@ public function testSplitWithConfigurations()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
 
         $redisClient->del('SPLITIO.split.mysplittest2');
 
         $redisClient->set('SPLITIO.split.mysplittest2', $this->split2);
 
-        $splitCache = new SplitCache();
+        $splitCache = new SplitCache($cachePool);
         $splitRepresentation = $splitCache->getSplit('mysplittest2');
         $split = new Split(json_decode($splitRepresentation, true));
 
diff --git a/tests/Suite/Engine/HashTest.php b/tests/Suite/Engine/HashTest.php
index a5a73be2..edda9979 100644
--- a/tests/Suite/Engine/HashTest.php
+++ b/tests/Suite/Engine/HashTest.php
@@ -7,8 +7,7 @@
 use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Engine\Hash\HashAlgorithmEnum;
 use SplitIO\Grammar\Split;
-use SplitIO\Split as SplitApp;
-use SplitIO\Component\Common\Di;
+use SplitIO\Test\Suite\Redis\ReflectiveTools;
 
 use SplitIO\Test\Utils;
 
@@ -79,8 +78,6 @@ public function testMurmur3HashFunction()
 
     public function testAlgoField()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -119,7 +116,8 @@ public function testAlgoField()
             ),
         );
 
-        $splitCache = new SplitCache();
+        $cachePool = ReflectiveTools::cacheFromFactory($splitFactory);
+        $splitCache = new SplitCache($cachePool);
         foreach ($cases as $case) {
             $split = new Split(json_decode($splitCache->getSplit($case['key']), true));
             $this->assertEquals($split->getAlgo(), $case['algo']);
diff --git a/tests/Suite/Engine/SplitterTest.php b/tests/Suite/Engine/SplitterTest.php
index 3e48ba90..4c0eb506 100644
--- a/tests/Suite/Engine/SplitterTest.php
+++ b/tests/Suite/Engine/SplitterTest.php
@@ -2,14 +2,10 @@
 namespace SplitIO\Test\Suite\Engine;
 
 use SplitIO\Component\Initialization\LoggerFactory;
-use SplitIO\Component\Common\ServiceProvider;
-use SplitIO\Component\Log\LogLevelEnum;
 use SplitIO\Engine\Splitter;
 use SplitIO\Grammar\Condition\Partition;
 use SplitIO\Engine\Hash\HashAlgorithmEnum;
-use SplitIO\Grammar\Split;
-use SplitIO\Engine;
-use SplitIO\Component\Common\Di;
+use SplitIO\Component\Common\Context;
 
 class SplitterTest extends \PHPUnit\Framework\TestCase
 {
@@ -17,7 +13,7 @@ class SplitterTest extends \PHPUnit\Framework\TestCase
     public function testDiLog()
     {
         $logger = LoggerFactory::setupLogger(array('adapter' => 'stdout', 'level' => 'error'));
-        ServiceProvider::registerLogger($logger);
+        Context::setLogger($logger);
 
         $this->assertTrue(true);
     }
@@ -27,7 +23,6 @@ public function testDiLog()
      */
     public function testWorks()
     {
-        Di::set('splitter', new Splitter());
         $partitions = array();
         for ($i = 1; $i <= 100; $i++) {
             $partitions[$i] = new Partition(array('treatment' => "$i", 'size' => 1));
@@ -43,7 +38,7 @@ public function testWorks()
 
         for ($i = 0; $i < $n; $i++) {
             $key = uniqid('', true);
-            $treatment = Di::get('splitter')->getTreatment($key, 32126754, $partitions, HashAlgorithmEnum::LEGACY);
+            $treatment = Splitter::getTreatment($key, 32126754, $partitions, HashAlgorithmEnum::LEGACY);
             $treatments[(int)$treatment - 1]++;
         }
 
@@ -69,98 +64,7 @@ public function testSplitterErrorPartions()
         $partition = new Partition(array('treatment' => "on", 'size' => -1));
 
         $this->assertNull(
-            Di::get('splitter')->getTreatment('someValidKey', 123123545, array($partition), HashAlgorithmEnum::LEGACY)
+            Splitter::getTreatment('someValidKey', 123123545, array($partition), HashAlgorithmEnum::LEGACY)
         );
     }
-
-    public function testTrafficAllocation()
-    {
-        $rawSplit = array(
-            'name' => 'test1',
-            'algo' => 1,
-            'killed' => false,
-            'status' => 'ACTIVE',
-            'defaultTreatment' => 'default',
-            'seed' => -1222652054,
-            'orgId' => null,
-            'environment' => null,
-            'trafficTypeId' => null,
-            'trafficTypeName' => null,
-            'conditions' => array(
-                array(
-                    'conditionType' => 'WHITELIST',
-                    'matcherGroup' => array(
-                        'combiner' => 'AND',
-                        'matchers' => array(
-                            array(
-                                'matcherType' => 'ALL_KEYS',
-                                'negate' => false,
-                                'userDefinedSegmentMatcherData' => null,
-                                'whitelistMatcherData' => null
-                            )
-                        )
-                    ),
-                    'partitions' => array(
-                        array(
-                            'treatment' => 'on',
-                            'size' => 100
-                        )
-                    ),
-                    'label' => 'in segment all'
-                )
-            )
-        );
-    
-        // Test that conditionType = WHITELIST works normally
-        $split1 = new Split($rawSplit);
-        $treatment1 = Engine::getTreatment('testKey', null, $split1);
-        $this->assertEquals('on', $treatment1[Engine::EVALUATION_RESULT_TREATMENT]);
-
-        // Test that conditionType = ROLLOUT w/o trafficAllocation behaves like WHITELIST
-        $rawSplit['conditions'][0]['conditionType'] = 'ROLLOUT';
-        $split2 = new Split($rawSplit);
-        $treatment2 = Engine::getTreatment('testKey', null, $split2);
-        $this->assertEquals('on', $treatment2[Engine::EVALUATION_RESULT_TREATMENT]);
-
-        // Set a low trafficAllocation to force the bucket outside it.
-        $rawSplit['trafficAllocation'] = 1;
-        $rawSplit['trafficAllocationSeed'] = -1;
-        $split3 = new Split($rawSplit);
-        $treatment3 = Engine::getTreatment('testKey', null, $split3);
-        $this->assertEquals('default', $treatment3[Engine::EVALUATION_RESULT_TREATMENT]);
-
-        // Set bucket to 1 with low traffic allocation.
-        $splitterMocked = $this
-            ->getMockBuilder('\SplitIO\Engine\Splitter')
-            ->disableOriginalConstructor()
-            ->setMethods(array('getBucket'))
-            ->getMock();
-
-        $splitterMocked->method('getBucket')->willReturn(1);
-
-        Di::set('splitter', $splitterMocked);
-
-        $rawSplit['trafficAllocation'] = 1;
-        $rawSplit['trafficAllocationSeed'] = -1;
-        $split4 = new Split($rawSplit);
-        $treatment4 = Engine::getTreatment('testKey', null, $split4);
-        $this->assertEquals('on', $treatment4[Engine::EVALUATION_RESULT_TREATMENT]);
-
-        // Set bucket to 100 with high traffic allocation.
-        $splitterMocked2 = $this
-            ->getMockBuilder('\SplitIO\Engine\Splitter')
-            ->disableOriginalConstructor()
-            ->setMethods(array('getBucket'))
-            ->getMock();
-
-        $splitterMocked2->method('getBucket')->willReturn(100);
-
-        Di::set('splitter', $splitterMocked2);
-
-        $rawSplit['trafficAllocation'] = 99;
-        $rawSplit['trafficAllocationSeed'] = -1;
-        $split5 = new Split($rawSplit);
-        $treatment5 = Engine::getTreatment('testKey', null, $split5);
-        $this->assertEquals('default', $treatment5[Engine::EVALUATION_RESULT_TREATMENT]);
-    }
 }
diff --git a/tests/Suite/InputValidation/FactoryTrackerTest.php b/tests/Suite/InputValidation/FactoryTrackerTest.php
index 0a86a281..d867ac61 100644
--- a/tests/Suite/InputValidation/FactoryTrackerTest.php
+++ b/tests/Suite/InputValidation/FactoryTrackerTest.php
@@ -1,55 +1,66 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
-        $options = array('prefix' => TEST_PREFIX);
-
-        $sdkConfig = array(
-            'log' => array('adapter' => 'stdout'),
-            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
-        );
-
-        //Initializing the SDK instance.
-        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        return $splitFactory;
-    }
-
     private function getMockedLogger()
     {
         //Initialize mock logger
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
 
     public function testMultipleClientInstantiation()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-        $splitFactory = $this->getFactoryClient();
+        ReflectiveTools::overrideTracker();
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
         $this->assertNotNull($splitFactory->client());
 
         $logger = $this->getMockedLogger();
+        $logger->expects($this->any())
+            ->method('warning')
+            ->with($this->logicalOr(
+                $this->equalTo("Factory Instantiation: You already have 1 factory/factories with this SDK Key. "
+                    . "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing "
+                    . "it throughout your application."),
+                $this->equalTo("Factory Instantiation: You already have 2 factory/factories with this SDK Key. "
+                    . "We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing "
+                    . "it throughout your application."),
+                $this->equalTo("Factory Instantiation: You already have an instance of the Split factory. "
+                    . "Make sure you definitely want this additional instance. We recommend keeping only one instance "
+                    . "of the factory at all times (Singleton pattern) and reusing it throughout your application.")
+            )
+        );
+
+        //Initializing the SDK instance2.
+        $splitFactory2 = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $this->assertNotNull($splitFactory2->client());
+
+        //Initializing the SDK instance3.
+        $splitFactory3 = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $this->assertNotNull($splitFactory3->client());
 
-        $logger->expects($this->once())
-            ->method('critical')
-            ->with($this->equalTo("Factory Instantiation: creating multiple factories is not possible. "
-                . "You have already created a factory."));
-        
-        $splitFactory2 = $this->getFactoryClient();
-        $this->assertEquals(null, $splitFactory2);
+        //Initializing the SDK instance4.
+        $splitFactory4 = \SplitIO\Sdk::factory('other', $sdkConfig);
+        $this->assertNotNull($splitFactory4->client());
     }
 }
diff --git a/tests/Suite/InputValidation/FlagSetsValidatorTest.php b/tests/Suite/InputValidation/FlagSetsValidatorTest.php
new file mode 100644
index 00000000..fa6f5214
--- /dev/null
+++ b/tests/Suite/InputValidation/FlagSetsValidatorTest.php
@@ -0,0 +1,136 @@
+getMockBuilder('\SplitIO\Component\Log\Logger')
+            ->disableOriginalConstructor()
+            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'write', 'log'))
+            ->getMock();
+
+        ReflectiveTools::overrideLogger($logger);
+
+        return $logger;
+    }
+
+    public function testAreValidWithEmptyArray()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('error')
+            ->with($this->equalTo('test: FlagSets must be a non-empty list.'));
+
+        $result = FlagSetsValidator::areValid([], "test");
+        $this->assertEquals(0, count($result));
+    }
+
+    public function testAreValidWithWhitespaces()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('warning')
+            ->with($this->equalTo('test: Flag Set name "    set_1 " has extra whitespace, trimming.'));
+
+        $result = FlagSetsValidator::areValid(["    set_1 "], "test");
+        $this->assertEquals(1, count($result));
+    }
+
+    public function testAreValidWithUppercases()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('warning')
+            ->with($this->equalTo('test: Flag Set name "SET_1" should be all lowercase - converting string to lowercase.'));
+
+        $result = FlagSetsValidator::areValid(["SET_1"], "test");
+        $this->assertEquals(1, count($result));
+    }
+
+    public function testAreValidWithIncorrectCharacters()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('warning')
+            ->with($this->equalTo('test: you passed "set-2", Flag Set must adhere to the regular expressions {/^[a-z0-9][_a-z0-9]{0,49}$/} This means a Flag Set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. "set-2" was discarded.'));
+
+        $result = FlagSetsValidator::areValid(["set-2"], "test");
+        $this->assertEquals(0, count($result));
+    }
+
+    public function testAreValidWithFlagSetToLong()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('warning')
+            ->with($this->equalTo('test: you passed "set_123123123123123123123123123123123123123123123123", Flag Set must adhere to the regular expressions {/^[a-z0-9][_a-z0-9]{0,49}$/} This means a Flag Set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. "set_123123123123123123123123123123123123123123123123" was discarded.'));
+
+        $result = FlagSetsValidator::areValid(["set_123123123123123123123123123123123123123123123123"], "test");
+        $this->assertEquals(0, count($result));
+    }
+
+    public function testAreValidWithFlagSetDupiclated()
+    {
+        $result = FlagSetsValidator::areValid(["set_4", "set_1", "SET_1", "set_2", " set_2 ", "set_3", "set_3"], "test");
+        $this->assertEquals(4, count($result));
+        $this->assertEquals("set_4", $result[0]);
+        $this->assertEquals("set_1", $result[1]);
+        $this->assertEquals("set_2", $result[2]);
+        $this->assertEquals("set_3", $result[3]);
+    }
+
+    public function testAreValidWithIncorrectTypes()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger->expects($this->once())
+            ->method('error')
+            ->with($this->equalTo('test: FlagSet must be a string and not null. 123 was discarded.'));
+
+        $result = FlagSetsValidator::areValid([null, 123, "set_1", "SET_1"], "test");
+        $this->assertEquals(1, count($result));
+    }
+
+    public function testAreValidConsecutive()
+    {
+        $logger = $this->getMockedLogger();
+
+        $logger
+            ->expects($this->exactly(6))
+            ->method('warning')
+            ->withConsecutive(
+                ['test: Flag Set name "   A  " has extra whitespace, trimming.'],
+                ['test: Flag Set name "   A  " should be all lowercase - converting string to lowercase.'],
+                ['test: Flag Set name "@FAIL" should be all lowercase - converting string to lowercase.'],
+                ['test: you passed "@FAIL", Flag Set must adhere to the regular expressions ' .
+                    '{/^[a-z0-9][_a-z0-9]{0,49}$/} This means a Flag Set must start with a letter or number, be in lowercase, alphanumeric and ' .
+                    'have a max length of 50 characters. "@FAIL" was discarded.'],
+                ['test: Flag Set name "TEST" should be all lowercase - converting string to lowercase.'],
+                ['test: Flag Set name "  a" has extra whitespace, trimming.'],
+            );
+        $logger
+            ->expects($this->exactly(2))
+            ->method('error')
+            ->withConsecutive(
+                ['test: FlagSets must be a non-empty list.'],
+                ['test: FlagSets must be a non-empty list.']
+            );
+
+        $this->assertEquals(['a', 'test'], FlagSetsValidator::areValid(['   A  ', '@FAIL', 'TEST'], 'test'));
+        $this->assertEquals(array(), FlagSetsValidator::areValid([], 'test'));
+        $this->assertEquals(array(), FlagSetsValidator::areValid(['some' => 'some1'], 'test'));
+        $this->assertEquals(['a', 'test'], FlagSetsValidator::areValid(['a', 'test', '  a'], 'test'));
+    }
+}
diff --git a/tests/Suite/InputValidation/GetTreatmentValidationTest.php b/tests/Suite/InputValidation/GetTreatmentValidationTest.php
index 9d0158e2..2c83e812 100644
--- a/tests/Suite/InputValidation/GetTreatmentValidationTest.php
+++ b/tests/Suite/InputValidation/GetTreatmentValidationTest.php
@@ -1,7 +1,7 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -32,11 +32,11 @@ private function getMockedLogger()
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
@@ -57,6 +57,7 @@ public function testGetTreatmentWithEmptyMatchingKeyObject()
         $splitSdk = $this->getFactoryClient();
 
         $this->assertEquals('control', $splitSdk->getTreatment(new Key('', 'some_bucketing_key'), 'some_feature'));
+        $this->assertNotEquals('control', $splitSdk->getTreatment(new Key("0", 'some_bucketing_key'), 'some_feature'));
     }
 
     public function testGetTreatmentWithWrongTypeMatchingKeyObject()
diff --git a/tests/Suite/InputValidation/GetTreatmentsValidationTest.php b/tests/Suite/InputValidation/GetTreatmentsValidationTest.php
index 921f36c2..12b6222f 100644
--- a/tests/Suite/InputValidation/GetTreatmentsValidationTest.php
+++ b/tests/Suite/InputValidation/GetTreatmentsValidationTest.php
@@ -1,8 +1,8 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -32,11 +32,11 @@ private function getMockedLogger()
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
diff --git a/tests/Suite/InputValidation/ManagerValidationTest.php b/tests/Suite/InputValidation/ManagerValidationTest.php
index 7035fc1f..3729d1b2 100644
--- a/tests/Suite/InputValidation/ManagerValidationTest.php
+++ b/tests/Suite/InputValidation/ManagerValidationTest.php
@@ -1,13 +1,13 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -29,11 +29,11 @@ private function getMockedLogger()
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
diff --git a/tests/Suite/InputValidation/TrackValidationTest.php b/tests/Suite/InputValidation/TrackValidationTest.php
index e42bb603..c11ebef9 100644
--- a/tests/Suite/InputValidation/TrackValidationTest.php
+++ b/tests/Suite/InputValidation/TrackValidationTest.php
@@ -1,7 +1,7 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -32,11 +32,11 @@ private function getMockedLogger()
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
-        Di::set(Di::KEY_LOG, $logger);
+        ReflectiveTools::overrideLogger($logger);
 
         return $logger;
     }
diff --git a/tests/Suite/Matchers/MatchersTest.php b/tests/Suite/Matchers/MatchersTest.php
index f0a1c57a..b5e4146e 100644
--- a/tests/Suite/Matchers/MatchersTest.php
+++ b/tests/Suite/Matchers/MatchersTest.php
@@ -1,22 +1,19 @@
  'redis',
             'host' => "localhost",
@@ -32,7 +29,12 @@ private function setupSplitApp()
             )
         );
 
-        $splitFactory = \SplitIO\Sdk::factory('apikey', $sdkConfig);
+        $splitFactory = \SplitIO\Sdk::factory('sdkKey', $sdkConfig);
+        $cachePool = ReflectiveTools::cacheFromFactory($splitFactory);
+        $segmentCache = new SegmentCache($cachePool);
+        $this->context = array(
+            'segmentCache' => $segmentCache,
+        );
         $splitFactory->client();
     }
 
@@ -152,12 +154,12 @@ public function testInSegmentMatcher()
         );
 
         $matcher = Matcher::factory($condition);
-        $this->assertEquals($matcher->evaluate('id1'), true);
-        $this->assertEquals($matcher->evaluate('id2'), true);
-        $this->assertEquals($matcher->evaluate('id3'), true);
-        $this->assertEquals($matcher->evaluate('id4'), false);
-        $this->assertEquals($matcher->evaluate(''), false);
-        $this->assertEquals($matcher->evaluate(null), false);
+        $this->assertEquals($matcher->evaluate('id1', $this->context), true);
+        $this->assertEquals($matcher->evaluate('id2', $this->context), true);
+        $this->assertEquals($matcher->evaluate('id3', $this->context), true);
+        $this->assertEquals($matcher->evaluate('id4', $this->context), false);
+        $this->assertEquals($matcher->evaluate('', $this->context), false);
+        $this->assertEquals($matcher->evaluate(null, $this->context), false);
     }
 
     public function testWhitelistMatcher()
@@ -450,7 +452,7 @@ private function setDependencyMatcherTestMocks()
         $evaluator = $this
             ->getMockBuilder('\SplitIO\Sdk\Evaluator')
             ->disableOriginalConstructor()
-            ->setMethods(array('evaluateFeature'))
+            ->onlyMethods(array('evaluateFeature'))
             ->getMock();
 
         $evaluator->method('evaluateFeature')->willReturn(array('treatment' => 'on'));
@@ -458,21 +460,21 @@ private function setDependencyMatcherTestMocks()
             ->method('evaluateFeature')
             ->with('test_key', null, 'test_feature', array('test_attribute1' => 'test_value1'));
 
-        Di::setEvaluator($evaluator);
+        $this->context['evaluator'] = $evaluator;
     }
 
     public function testDependencyMatcherTrue()
     {
         $this->setDependencyMatcherTestMocks();
         $matcher = new Matcher\Dependency(array('split' => 'test_feature', 'treatments' => array('on')));
-        $this->assertEquals($matcher->evalKey('test_key', array('test_attribute1' => 'test_value1')), true);
+        $this->assertEquals($matcher->evalKey('test_key', array('test_attribute1' => 'test_value1'), null, $this->context), true);
     }
 
     public function testDependencyMatcherFalse()
     {
         $this->setDependencyMatcherTestMocks();
         $matcher = new Matcher\Dependency(array('split' => 'test_feature', 'treatments' => array('off')));
-        $this->assertEquals($matcher->evalKey('test_key', array('test_attribute1' => 'test_value1')), false);
+        $this->assertEquals($matcher->evalKey('test_key', array('test_attribute1' => 'test_value1'), null, $this->context), false);
     }
 
     public function testRegexMatcher()
diff --git a/tests/Suite/Redis/CacheInterfacesTest.php b/tests/Suite/Redis/CacheInterfacesTest.php
index 75f10b8e..6767b1a2 100644
--- a/tests/Suite/Redis/CacheInterfacesTest.php
+++ b/tests/Suite/Redis/CacheInterfacesTest.php
@@ -5,8 +5,7 @@
 use SplitIO\Component\Cache\Pool;
 use SplitIO\Component\Cache\SegmentCache;
 use SplitIO\Component\Cache\SplitCache;
-use SplitIO\Component\Common\Di;
-use SplitIO\Component\Cache\BlockUntilReadyCache;
+use SplitIO\Component\Common\Context;
 use SplitIO\Component\Log\Handler\Stdout;
 use SplitIO\Component\Log\Logger;
 use SplitIO\Component\Log\LogLevelEnum;
@@ -19,70 +18,65 @@
 class CacheInterfacesTest extends \PHPUnit\Framework\TestCase
 {
 
+    private $cachePool;
+
+    private function setupCachePool()
+    {
+        $parameters = array(
+            'host' => REDIS_HOST,
+            'port' => REDIS_PORT,
+        );
+        $this->cachePool = new Pool(array('adapter' => array(
+            'name' => 'predis',
+            'options' => array(
+                'options' => array('prefix' => TEST_PREFIX),
+                'parameters' => $parameters,
+            ),
+        )));
+    }
+
     public function testDiLog()
     {
         $logAdapter = new Stdout();
 
         $logger = new Logger($logAdapter, LogLevelEnum::INFO);
 
-        Di::getInstance()->setLogger($logger);
-
-        $this->assertTrue(true);
-    }
-
-    /**
-     * @depends testDiLog
-     */
-    public function testDiCache()
-    {
-        try {
-            $parameters = array(
-                'host' => REDIS_HOST,
-                'port' => REDIS_PORT,
-            );
-
-            $cachePool = new Pool(array('adapter' => array(
-                'name' => 'predis',
-                'options' => array(
-                    'options' => array('prefix' => TEST_PREFIX),
-                    'parameters' => $parameters,
-                ),
-            )));
-            Di::getInstance()->setCache($cachePool);
-        } catch (\Exception $e) {
-            $this->assertTrue(false, "Error setting cache on Di".  $e);
-        }
+        Context::getInstance()->setLogger($logger);
 
         $this->assertTrue(true);
     }
 
     /**
      * @depends testDiLog
-     * @depends testDiCache
      */
     public function testSplitCacheInterface()
     {
+        $this->setupCachePool();
         $splitChanges = file_get_contents(__DIR__."/../../files/splitChanges.json");
         $this->assertJson($splitChanges);
 
         Utils\Utils::addSplitsInCache($splitChanges);
-        $splitCache = new SplitCache();
+        $splitCache = new SplitCache($this->cachePool);
 
         $splitChanges = json_decode($splitChanges, true);
         $splits = $splitChanges['splits'];
         $split = $splits[0];
-
         $splitName = $split['name'];
+        $flagSets = array('set_a', 'set_b');
 
         $this->assertEquals(strlen(json_encode($split)), strlen($splitCache->getSplit($splitName)));
         $this->assertEquals($splitChanges['till'], $splitCache->getChangeNumber());
+        $result = $splitCache->getNamesByFlagSets($flagSets);
+        $this->assertEquals(2, count($result['set_a']));
+        $this->assertEquals(2, count($result['set_b']));
     }
 
     /**
-     * @depends testSplitCacheInterface
+     * @depends testDiLog
      */
     public function testSegmentCacheInterface()
     {
+        $this->setupCachePool();
         $segmentChanges = file_get_contents(__DIR__."/../../files/segmentEmployeesChanges.json");
         $this->assertJson($segmentChanges);
 
@@ -90,7 +84,8 @@ public function testSegmentCacheInterface()
 
         $segmentData = json_decode($segmentChanges, true);
         $segmentName = $segmentData['name'];
-        $segmentCache = new SegmentCache();
+        $segmentCache = new SegmentCache($this->cachePool);
+
         $this->assertTrue(boolval($segmentCache->isInSegment($segmentName, "fake_user_id_4")));
         $this->assertFalse(boolval($segmentCache->isInSegment($segmentName, "fake_user_id_4_")));
 
@@ -99,10 +94,10 @@ public function testSegmentCacheInterface()
 
     /**
      * @depends testDiLog
-     * @depends testDiCache
      */
     public function testEventsCache()
     {
+        $this->setupCachePool();
         $key= "some_key";
         $trafficType = "some_trafficType";
         $eventType = "some_event_type";
@@ -110,16 +105,16 @@ public function testEventsCache()
 
         $eventDTO = new EventDTO($key, $trafficType, $eventType, $value, null);
         $eventQueueMessage = new EventQueueMessage(new QueueMetadataMessage(), $eventDTO);
-
-        $this->assertTrue(EventsCache::addEvent($eventQueueMessage));
+        $eventsCache = new EventsCache($this->cachePool);
+        $this->assertTrue($eventsCache->addEvent($eventQueueMessage));
     }
 
     /**
      * @depends testDiLog
-     * @depends testDiCache
      */
     public function testEventsCacheWithProperties()
     {
+        $this->setupCachePool();
         $key= "some_key";
         $trafficType = "some_trafficType";
         $eventType = "some_event_type";
@@ -133,8 +128,8 @@ public function testEventsCacheWithProperties()
 
         $eventDTO = new EventDTO($key, $trafficType, $eventType, $value, $properties);
         $eventQueueMessage = new EventQueueMessage(new QueueMetadataMessage(), $eventDTO);
-
-        $this->assertTrue(EventsCache::addEvent($eventQueueMessage));
+        $eventsCache = new EventsCache($this->cachePool);
+        $this->assertTrue($eventsCache->addEvent($eventQueueMessage));
 
         $this->assertEquals($properties, $eventDTO->getProperties());
     }
diff --git a/tests/Suite/Redis/ReflectiveTools.php b/tests/Suite/Redis/ReflectiveTools.php
index 675cec3c..f7080370 100644
--- a/tests/Suite/Redis/ReflectiveTools.php
+++ b/tests/Suite/Redis/ReflectiveTools.php
@@ -3,9 +3,41 @@
 namespace SplitIO\Test\Suite\Redis;
 
 use ReflectionClass;
+use SplitIO\Component\Common\Context;
 
 class ReflectiveTools
 {
+    public static function cacheFromFactory(\SplitIO\Sdk\Factory\SplitFactory $factory)
+    {
+        $reflectionFactory = new ReflectionClass('\SplitIO\Sdk\Factory\SplitFactory');
+        $reflectionCache = $reflectionFactory->getProperty('cache');
+        $reflectionCache->setAccessible(true);
+        return $reflectionCache->getValue($factory);
+    }
+
+    public static function clientFromFactory(\SplitIO\Sdk\Factory\SplitFactory $factory)
+    {
+        $reflectionFactory = new ReflectionClass('\SplitIO\Sdk\Factory\SplitFactory');
+        $reflectionCache = $reflectionFactory->getProperty('cache');
+        $reflectionCache->setAccessible(true);
+        $cachePool = $reflectionCache->getValue($factory);
+
+        $reflectionPool = new ReflectionClass('\SplitIO\Component\Cache\Pool');
+        $reflectionAdapter = $reflectionPool->getProperty('adapter');
+        $reflectionAdapter->setAccessible(true);
+        $adapter = $reflectionAdapter->getValue($cachePool);
+
+        $reflectionSafeRedis = new ReflectionClass('SplitIO\Component\Cache\Storage\Adapter\SafeRedisWrapper');
+        $reflectionCacheAdapter= $reflectionSafeRedis->getProperty('cacheAdapter');
+        $reflectionCacheAdapter->setAccessible(true);
+        $adapter = $reflectionCacheAdapter->getValue($adapter);
+
+        $reflectionPRedis = new ReflectionClass('SplitIO\Component\Cache\Storage\Adapter\PRedis');
+        $reflectionClient= $reflectionPRedis->getProperty('client');
+        $reflectionClient->setAccessible(true);
+        return $reflectionClient->getValue($adapter);
+    }
+
     public static function clientFromCachePool(\SplitIO\Component\Cache\Pool $cachePool)
     {
         $reflectionPool = new ReflectionClass('\SplitIO\Component\Cache\Pool');
@@ -23,4 +55,41 @@ public static function clientFromCachePool(\SplitIO\Component\Cache\Pool $cacheP
         $reflectionClient->setAccessible(true);
         return $reflectionClient->getValue($adapter);
     }
+
+    public static function overrideLogger($logger)
+    {
+        $di = Context::getInstance();
+        $reflection = new ReflectionClass('SplitIO\Component\Common\Context');
+        $property = $reflection->getProperty('logger');
+        $property->setAccessible(true);
+        $property->setValue($di, $logger);
+    }
+
+    public static function resetIPAddress()
+    {
+        $di = Context::getInstance();
+        $reflection = new ReflectionClass('SplitIO\Component\Common\Context');
+        $property = $reflection->getProperty('ipAddress');
+        $property->setAccessible(true);
+        $property->setValue($di, "");
+    }
+
+    public static function overrideTracker()
+    {
+        $di = Context::getInstance();
+        $reflection = new ReflectionClass('SplitIO\Component\Common\Context');
+        $property = $reflection->getProperty('factoryTracker');
+        $property->setAccessible(true);
+        $property->setValue($di, array());
+    }
+
+    public static function resetContext()
+    {
+        $context = Context::getInstance();
+        $reflection = new ReflectionClass($context);
+        $instance = $reflection->getProperty('instance');
+        $instance->setAccessible(true);
+        $instance->setValue(null, null);
+        $instance->setAccessible(false);
+    }
 }
diff --git a/tests/Suite/Redis/SafeRedisWrapperTest.php b/tests/Suite/Redis/SafeRedisWrapperTest.php
index 473a7aa6..b60b9c8d 100644
--- a/tests/Suite/Redis/SafeRedisWrapperTest.php
+++ b/tests/Suite/Redis/SafeRedisWrapperTest.php
@@ -1,9 +1,6 @@
 getMockBuilder('\Predis\Client')
             ->disableOriginalConstructor()
-            ->setMethods($cachePoolMethods)
+            ->addMethods($cachePoolMethods)
+            ->onlyMethods(array('getOptions'))
             ->getMock();
 
         foreach ($cachePoolMethods as $method) {
             $predisMock->method($method)
                 ->will($this->throwException(new \Exception()));
         }
+        $predisMock->method('getOptions')
+            ->will($this->throwException(new \Exception()));
 
         $predisMock->method('getOptions')
             ->willReturn(array());
@@ -46,5 +46,6 @@ public function testAllMethodsException()
         $this->assertEquals(array(), $safeRedisWrapper->getKeys("some"));
         $this->assertEquals(0, $safeRedisWrapper->rightPushQueue("some", "another"));
         $this->assertEquals(false, $safeRedisWrapper->expireKey("some", 12345));
+        $this->assertEquals(array(), $safeRedisWrapper->sMembers("key"));
     }
 }
diff --git a/tests/Suite/Sdk/ImpressionWrapperTest.php b/tests/Suite/Sdk/ImpressionWrapperTest.php
index 10d45d83..c4e636fb 100644
--- a/tests/Suite/Sdk/ImpressionWrapperTest.php
+++ b/tests/Suite/Sdk/ImpressionWrapperTest.php
@@ -3,12 +3,10 @@
 
 use SplitIO\Sdk\ImpressionListenerWrapper;
 use SplitIO\Sdk\Impressions\Impression;
-use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Grammar\Condition\Partition\TreatmentEnum;
 use SplitIO\Test\Suite\Sdk\Helpers\ListenerClient;
 use SplitIO\Test\Suite\Sdk\Helpers\ListenerClientWithException;
-use SplitIO\Test\Suite\Sdk\Helpers\ListenerClientWrong;
-use SplitIO\Component\Common\Di;
+use SplitIO\Test\Suite\Redis\ReflectiveTools;
 
 use SplitIO\Test\Utils;
 
@@ -42,7 +40,6 @@ public function testSendDataToClient()
 
     private function getFactoryClient($sdkConfig)
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array();
 
@@ -121,6 +118,7 @@ public function testClient()
 
         $impressionClient = new ListenerClient();
 
+        ReflectiveTools::resetIPAddress();
         $sdkConfig = array(
             'log' => array('adapter' => 'stdout'),
             'impressionListener' => $impressionClient,
@@ -162,6 +160,7 @@ public function testClientWithEmptyIpAddress()
 
         $impressionClient3 = new ListenerClient();
 
+        ReflectiveTools::resetIPAddress();
         $sdkConfig = array(
             'log' => array('adapter' => 'stdout'),
             'impressionListener' => $impressionClient3,
@@ -194,6 +193,7 @@ public function testClientWithEmptyStringIpAddress()
 
         $impressionClient4 = new ListenerClient();
 
+        ReflectiveTools::resetIPAddress();
         $sdkConfig = array(
             'log' => array('adapter' => 'stdout'),
             'impressionListener' => $impressionClient4,
@@ -226,6 +226,7 @@ public function testClientErasingServer()
 
         $impressionClient4 = new ListenerClient();
 
+        ReflectiveTools::resetIPAddress();
         $sdkConfig = array(
             'log' => array('adapter' => 'stdout'),
             'impressionListener' => $impressionClient4,
diff --git a/tests/Suite/Sdk/ImpressionsTest.php b/tests/Suite/Sdk/ImpressionsTest.php
index 0ba34dc1..23eaec50 100644
--- a/tests/Suite/Sdk/ImpressionsTest.php
+++ b/tests/Suite/Sdk/ImpressionsTest.php
@@ -1,8 +1,6 @@
  'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -24,14 +21,15 @@ public function testImpressionsAreAdded()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
 
         $redisClient->del(ImpressionCache::IMPRESSIONS_QUEUE_KEY);
         $queueMetadata = new QueueMetadataMessage();
 
-        TreatmentImpression::log(new Impression(
+        $impressionCache = new ImpressionCache($cachePool);
+        $impressionCache->logImpressions(array(new Impression(
             'someMatchingKey',
             'someFeature',
             'on',
@@ -39,7 +37,7 @@ public function testImpressionsAreAdded()
             123456,
             321654,
             'someBucketingKey'
-        ), $queueMetadata);
+        )), $queueMetadata);
 
         // Assert that the TTL is within a 10-second range (between it was set and retrieved).
         $ttl = $redisClient->ttl(ImpressionCache::IMPRESSIONS_QUEUE_KEY);
@@ -63,7 +61,6 @@ public function testImpressionsAreAdded()
 
     public function testExpirationOnlyOccursOnce()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
 
@@ -74,13 +71,15 @@ public function testExpirationOnlyOccursOnce()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
-
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $redisClient = ReflectiveTools::clientFromFactory($factory);
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        
         $redisClient->del(ImpressionCache::IMPRESSIONS_QUEUE_KEY);
         $queueMetadata = new QueueMetadataMessage(false);
 
-        TreatmentImpression::log(new Impression(
+        $impressionCache = new ImpressionCache($cachePool);
+        $impressionCache->logImpressions(array(new Impression(
             'someMatchingKey',
             'someFeature',
             'on',
@@ -88,11 +87,11 @@ public function testExpirationOnlyOccursOnce()
             123456,
             321654,
             'someBucketingKey'
-        ), $queueMetadata);
+        )), $queueMetadata);
 
         sleep(3);
 
-        TreatmentImpression::log(new Impression(
+        $impressionCache->logImpressions(array(new Impression(
             'someMatchingKey',
             'someFeature',
             'on',
@@ -100,7 +99,7 @@ public function testExpirationOnlyOccursOnce()
             123456,
             321654,
             'someBucketingKey'
-        ), $queueMetadata);
+        )), $queueMetadata);
 
         $ttl = $redisClient->ttl(ImpressionCache::IMPRESSIONS_QUEUE_KEY);
         // $ttl should be lower than or equalt the default impressions TTL minus 3 seconds,
diff --git a/tests/Suite/Sdk/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php
index 12c3dcfa..ddd27fed 100644
--- a/tests/Suite/Sdk/SdkClientTest.php
+++ b/tests/Suite/Sdk/SdkClientTest.php
@@ -2,24 +2,22 @@
 namespace SplitIO\Test\Suite\Sdk;
 
 use \stdClass;
-use SplitIO\Component\Common\Di;
+use ReflectionClass;
 use SplitIO\Test\Suite\Redis\ReflectiveTools;
+use SplitIO\Component\Cache\SegmentCache;
+use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Component\Cache\ImpressionCache;
-use SplitIO\Component\Cache\Storage\Adapter;
+use SplitIO\Component\Cache\EventsCache;
 use SplitIO\Component\Cache\Storage\Adapter\PRedis;
 use SplitIO\Component\Cache\Pool;
-use SplitIO\Component\Cache\SegmentCache;
-use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Sdk\Client;
-
-use SplitIO\Test\Suite\Sdk\Helpers\CustomLogger;
 use SplitIO\Test\Utils;
 
 class SdkClientTest extends \PHPUnit\Framework\TestCase
 {
     public function testLocalClient()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         $options['splitFile'] = dirname(dirname(__DIR__)).'/files/.splits';
         $splitFactory = \SplitIO\Sdk::factory('localhost', $options);
         $splitSdk = $splitFactory->client();
@@ -36,7 +34,7 @@ public function testLocalClient()
 
     public function testLocalClientYAML()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         $options['splitFile'] = dirname(dirname(__DIR__)).'/files/splits.yml';
         $splitFactory = \SplitIO\Sdk::factory('localhost', $options);
         $splitSdk = $splitFactory->client();
@@ -207,9 +205,43 @@ private function validateLastImpression(
         $this->assertEquals($parsed['m']['n'], $machineName);
     }
 
+    public function testSplitManager()
+    {
+        ReflectiveTools::resetContext();
+        $parameters = array(
+            'scheme' => 'redis',
+            'host' => REDIS_HOST,
+            'port' => REDIS_PORT,
+            'timeout' => 881,
+        );
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitManager = $splitFactory->manager();
+
+        //Assertions
+        $split_view = $splitManager->split("flagsets_feature");
+        $this->assertEquals("flagsets_feature", $split_view->getName());
+        $this->assertEquals('off', $split_view->getDefaultTreatment());
+        $this->assertEquals('["set_a","set_b","set_c"]', json_encode($split_view->getSets()));
+
+        $split_views = $splitManager->splits();
+        $this->assertEquals('off', $split_views["flagsets_feature"]->getDefaultTreatment());
+        $this->assertEquals('["set_a","set_b","set_c"]', json_encode($split_views["flagsets_feature"]->getSets()));
+    }
+
     public function testClient()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         //Testing version string
         $this->assertTrue(is_string(\SplitIO\version()));
 
@@ -236,7 +268,7 @@ public function testClient()
         Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
         Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
 
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
 
         //Assertions
         $this->assertEquals('on', $splitSdk->getTreatment('user1', 'sample_feature'));
@@ -435,18 +467,15 @@ public function testClient()
         $this->assertEquals('{"size":15,"test":20}', $configs['on']);
     }
 
-    /**
-     * @depends testClient
-     */
     public function testCustomLog()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         // create a log channel
         $log = $this
             ->getMockBuilder('Psr\Log\LoggerInterface')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
@@ -476,7 +505,7 @@ public function testCustomLog()
 
     public function testInvalidCacheAdapter()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         $this->expectException('\SplitIO\Exception\Exception');
 
         $sdkConfig = array(
@@ -490,13 +519,12 @@ public function testInvalidCacheAdapter()
 
     public function testCacheExceptionReturnsControl()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-
+        ReflectiveTools::resetContext();
         $log = $this
             ->getMockBuilder('Psr\Log\LoggerInterface')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
@@ -516,7 +544,7 @@ public function testCacheExceptionReturnsControl()
         $cachePool = $this
             ->getMockBuilder('\SplitIO\Component\Cache\Pool')
             ->disableOriginalConstructor()
-            ->setMethods($cachePoolMethods)
+            ->onlyMethods($cachePoolMethods)
             ->getMock();
 
         foreach ($cachePoolMethods as $method) {
@@ -524,15 +552,13 @@ public function testCacheExceptionReturnsControl()
                 ->will($this->throwException(new \Exception()));
         }
 
-        Di::setCache($cachePool);
-
         $treatment = $splitSdk->getTreatment('key1', 'feature1');
         $this->assertEquals($treatment, 'control');
     }
 
     public function testGetTreatmentsWithDistinctFeatures()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
+        ReflectiveTools::resetContext();
         //Testing version string
         $this->assertTrue(is_string(\SplitIO\version()));
 
@@ -563,14 +589,13 @@ public function testGetTreatmentsWithDistinctFeatures()
         $this->assertEquals('control', $treatmentResult['invalid_feature']);
 
         //Check impressions generated
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
         $this->validateLastImpression($redisClient, 'sample_feature', 'user1', 'on', 'NA', 'NA');
     }
 
     public function testGetTreatmentsWithRepeteadedFeatures()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-
+        ReflectiveTools::resetContext();
         //Testing version string
         $this->assertTrue(is_string(\SplitIO\version()));
 
@@ -602,15 +627,13 @@ public function testGetTreatmentsWithRepeteadedFeatures()
         $this->assertEquals('control', $treatmentResult['invalid_feature']);
 
         // Check impressions
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
         $this->validateLastImpression($redisClient, 'sample_feature', 'user1', 'on', 'ip-1-2-3-4', '1.2.3.4');
     }
 
     public function testGetTreatmentsWithRepeteadedAndNullFeatures()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-        Di::set('ipAddress', null); // unset ipaddress from previous test
-
+        ReflectiveTools::resetIPAddress();
         //Testing version string
         $this->assertTrue(is_string(\SplitIO\version()));
 
@@ -619,7 +642,8 @@ public function testGetTreatmentsWithRepeteadedAndNullFeatures()
 
         $sdkConfig = array(
             'log' => array('adapter' => 'stdout'),
-            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options),
+            'ipAddress' => ''
         );
 
         //Initializing the SDK instance.
@@ -641,36 +665,34 @@ public function testGetTreatmentsWithRepeteadedAndNullFeatures()
         $this->assertEquals('control', $treatmentResult['invalid_feature']);
 
         //Check impressions
-        $redisClient = ReflectiveTools::clientFromCachePool(Di::getCache());
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
         $this->validateLastImpression($redisClient, 'sample_feature', 'user1', 'on');
     }
 
     public function testGetTreatmentsFetchesSplitsInOneCall()
     {
+        ReflectiveTools::resetIPAddress();
         // Set redis-library client mock
         $predisMock = $this
             ->getMockBuilder('\Predis\Client')
             ->disableOriginalConstructor()
-            ->setMethods(array('get', 'mget', 'rpush', 'incr'))
+            ->addMethods(array('get', 'mget', 'rpush', 'incr'))
             ->getMock();
 
         // Create an adapter and inject mock via reflection
         $adapter = new PRedis(array('parameters' => array()));
-        $adapterReflection = new \ReflectionClass($adapter);
+        $adapterReflection = new ReflectionClass($adapter);
         $clientProperty = $adapterReflection->getProperty('client');
         $clientProperty->setAccessible(true);
         $clientProperty->setValue($adapter, $predisMock);
 
         // Create a pool and inject the adapter via reflection
         $pool = new Pool(array('adapter' => array('name' => 'predis', 'options' => array('parameters' => array()))));
-        $poolReflection = new \ReflectionClass($pool);
+        $poolReflection = new ReflectionClass($pool);
         $adapterProperty = $poolReflection->getProperty('adapter');
         $adapterProperty->setAccessible(true);
         $adapterProperty->setValue($pool, $adapter);
 
-        // use the newly created pool as cache
-        Di::setCache($pool);
-
         // Assert that MGET is called with the 3 splits.
         $predisMock->expects($this->once())
             ->method('mget')
@@ -680,10 +702,265 @@ public function testGetTreatmentsFetchesSplitsInOneCall()
         $predisMock->expects($this->never())
             ->method('get');
 
-        $client = new Client();
+        $eventCache = new EventsCache($pool);
+        $impressionCache = new ImpressionCache($pool);
+        $segmentCache = new SegmentCache($pool);
+        $splitCache = new SplitCache($pool);
+        
+        $client = new Client(array(
+            'splitCache' => $splitCache,
+            'segmentCache' => $segmentCache,
+            'impressionCache' => $impressionCache,
+            'eventCache' => $eventCache,
+        ));
+
         $client->getTreatments('key1', array('split1', 'split2', 'split3'));
     }
 
+    public function testMultipleInstantiationNotOverrideIP()
+    {
+        ReflectiveTools::resetIPAddress();
+        //Testing version string
+        $this->assertTrue(is_string(\SplitIO\version()));
+
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options),
+            'ipAddress' => '1.2.3.4'
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk = $splitFactory->client();
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
+
+        $treatmentResult = $splitSdk->getTreatments('user1', array('sample_feature', 'invalid_feature',
+        'sample_feature', 'sample_feature'), null);
+
+        //Assertions
+        $this->assertEquals(2, count(array_keys($treatmentResult)));
+
+        $this->assertEquals('on', $treatmentResult['sample_feature']);
+        $this->assertEquals('control', $treatmentResult['invalid_feature']);
+
+        // Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $this->validateLastImpression($redisClient, 'sample_feature', 'user1', 'on', 'ip-1-2-3-4', '1.2.3.4');
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options),
+            'ipAddress' => '1.2.3.4.5'
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory2 = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk2 = $splitFactory2->client();
+
+        $treatmentResult2 = $splitSdk2->getTreatments('user1', array('sample_feature', 'invalid_feature',
+        'sample_feature', 'sample_feature'), null);
+
+        //Assertions
+        $this->assertEquals(2, count(array_keys($treatmentResult2)));
+
+        $this->assertEquals('on', $treatmentResult2['sample_feature']);
+        $this->assertEquals('control', $treatmentResult2['invalid_feature']);
+
+        // Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory2);
+        $this->validateLastImpression($redisClient, 'sample_feature', 'user1', 'on', 'ip-1-2-3-4', '1.2.3.4');
+    }
+
+    public function testGetTreatmentsWithConfigByFlagSets()
+    {
+        ReflectiveTools::resetContext();
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk = $splitFactory->client();
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
+
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSets('user1', array('set_a', null, 'invalid-set', 'set_a', null, 'set_b'), null);
+
+        //Assertions
+        $this->assertEquals(2, count(array_keys($treatmentResult)));
+
+        $this->assertEquals('on', $treatmentResult['flagsets_feature']["treatment"]);
+        $this->assertEquals("{\"size\":15,\"test\":20}", $treatmentResult['flagsets_feature']["config"]);
+        $this->assertEquals('off', $treatmentResult['boolean_test']["treatment"]);
+        $this->assertNull($treatmentResult['boolean_test']["config"]);
+
+        //Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $this->validateLastImpression($redisClient, 'boolean_test', 'user1', 'off');
+        $this->validateLastImpression($redisClient, 'flagsets_feature', 'user1', 'on');
+
+        // With Incorrect Values
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSets('user1', array(null, 123, ""=>"", "fake_name", "###", ["set"], ["set"=>"set"]), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // Empty array
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSets('user1', array(), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // null
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSets('user1', null, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+    }
+
+    public function testGetTreatmentsByFlagSets()
+    {
+        ReflectiveTools::resetContext();
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk = $splitFactory->client();
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
+
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSets('user1', array('set_a', null, 'invalid-set', 'set_a', null, 'set_b'), null);
+
+        //Assertions
+        $this->assertEquals(2, count(array_keys($treatmentResult)));
+
+        $this->assertEquals('on', $treatmentResult['flagsets_feature']);
+        $this->assertEquals('off', $treatmentResult['boolean_test']);
+
+        //Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $this->validateLastImpression($redisClient, 'boolean_test', 'user1', 'off');
+        $this->validateLastImpression($redisClient, 'flagsets_feature', 'user1', 'on');
+
+        // With Incorrect Values
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSets('user1', array(null, 123, "set"=>"set", "fake_name", "###", ["set"], ["set"=>"set"]), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // Empty array
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSets('user1', array(), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // null
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSets('user1', null, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+    }
+
+    public function testGetTreatmentsWithConfigByFlagSet()
+    {
+        ReflectiveTools::resetContext();
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk = $splitFactory->client();
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
+
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSet('user1', 'set_a', null);
+
+        //Assertions
+        $this->assertEquals(1, count(array_keys($treatmentResult)));
+
+        $this->assertEquals('on', $treatmentResult['flagsets_feature']["treatment"]);
+        $this->assertEquals("{\"size\":15,\"test\":20}", $treatmentResult['flagsets_feature']["config"]);
+
+        //Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $this->validateLastImpression($redisClient, 'flagsets_feature', 'user1', 'on');
+
+        // With Incorrect Values
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSet('user1', 123, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // Empty array
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSet('user1', array(), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // null
+        $treatmentResult = $splitSdk->getTreatmentsWithConfigByFlagSet('user1', null, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+    }
+
+    public function testGetTreatmentsByFlagSet()
+    {
+        ReflectiveTools::resetContext();
+        $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
+        $options = array('prefix' => TEST_PREFIX);
+
+        $sdkConfig = array(
+            'log' => array('adapter' => 'stdout'),
+            'cache' => array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options)
+        );
+
+        //Initializing the SDK instance.
+        $splitFactory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $splitSdk = $splitFactory->client();
+
+        //Populating the cache.
+        Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentEmployeesChanges.json"));
+        Utils\Utils::addSegmentsInCache(file_get_contents(__DIR__."/files/segmentHumanBeignsChanges.json"));
+
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSet('user1', 'set_a', null);
+
+        //Assertions
+        $this->assertEquals(1, count(array_keys($treatmentResult)));
+
+        $this->assertEquals('on', $treatmentResult['flagsets_feature']);
+
+        //Check impressions
+        $redisClient = ReflectiveTools::clientFromFactory($splitFactory);
+        $this->validateLastImpression($redisClient, 'flagsets_feature', 'user1', 'on');
+
+        // With Incorrect Values
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSet('user1', 123, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // Empty array
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSet('user1', array(), null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+
+        // null
+        $treatmentResult = $splitSdk->getTreatmentsByFlagSet('user1', null, null);
+        $this->assertEquals(0, count(array_keys($treatmentResult)));
+    }
+    
     public static function tearDownAfterClass(): void
     {
         Utils\Utils::cleanCache();
diff --git a/tests/Suite/Sdk/SdkReadOnlyTest.php b/tests/Suite/Sdk/SdkReadOnlyTest.php
index 732635c1..5020c9af 100644
--- a/tests/Suite/Sdk/SdkReadOnlyTest.php
+++ b/tests/Suite/Sdk/SdkReadOnlyTest.php
@@ -2,14 +2,8 @@
 
 namespace SplitIO\Test\Suite\Sdk;
 
-use SplitIO\Component\Cache\SplitCache;
 use SplitIO\Component\Cache\Storage\Adapter\PRedis;
-use SplitIO\Component\Common\Di;
-use SplitIO\Test\Suite\Redis\PRedisReadOnlyMock;
-use SplitIO\TreatmentImpression;
-use SplitIO\Grammar\Condition\Partition\TreatmentEnum;
-use SplitIO\Sdk\Impressions\Impression;
-use SplitIO\Sdk\QueueMetadataMessage;
+use SplitIO\Component\Common\Context;
 
 use SplitIO\Test\Utils;
 
@@ -17,8 +11,6 @@ class SdkReadOnlyTest extends \PHPUnit\Framework\TestCase
 {
     public function testClient()
     {
-        Di::set(Di::KEY_FACTORY_TRACKER, false);
-
         $parameters = array('scheme' => 'redis', 'host' => REDIS_HOST, 'port' => REDIS_PORT, 'timeout' => 881);
         $options = array('prefix' => TEST_PREFIX);
         $sdkConfig = array(
@@ -35,14 +27,13 @@ public function testClient()
 
         //Instantiate PRedis Mocked Cache
         $predis = new PRedis(array('adapter' => 'predis', 'parameters' => $parameters, 'options' => $options));
-        Di::set(Di::KEY_CACHE, new PRedisReadOnlyMock($predis));
 
         //Initialize mock logger
         $logger = $this
             ->getMockBuilder('\SplitIO\Component\Log\Logger')
             ->disableOriginalConstructor()
-            ->setMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
-                'alert', 'notice', 'write', 'log'))
+            ->onlyMethods(array('warning', 'debug', 'error', 'info', 'critical', 'emergency',
+                'alert', 'notice', 'log'))
             ->getMock();
 
         $logger->expects($this->any())
@@ -54,13 +45,15 @@ public function testClient()
                 $this->equalTo('The SPLIT definition for \'mockedPRedisInvalid\' has not been found')
             ));
 
-        Di::set(Di::KEY_LOG, $logger);
+        Context::setLogger($logger);
 
         $this->assertEquals('on', $splitSdk->getTreatment('valid', 'mockedPRedis'));
         $this->assertEquals('off', $splitSdk->getTreatment('invalid', 'mockedPRedis'));
         $this->assertEquals('control', $splitSdk->getTreatment('valid', 'mockedPRedisInvalid'));
     }
 
+    /*
+    @TODO FIND A WAY TO IMPLEMENT
     public function testException()
     {
         Di::set(Di::KEY_FACTORY_TRACKER, false);
@@ -73,7 +66,7 @@ public function testException()
         );
 
         //Initializing the SDK instance.
-        \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
+        $factory = \SplitIO\Sdk::factory('asdqwe123456', $sdkConfig);
 
         //Populating the cache.
         Utils\Utils::addSplitsInCache(file_get_contents(__DIR__."/files/splitReadOnly.json"));
@@ -115,9 +108,18 @@ public function testException()
             'something'
         );
 
-        TreatmentImpression::log($impression, new QueueMetadataMessage());
+        $cachePool = ReflectiveTools::cacheFromFactory($factory);
+        $logger = $this
+            ->getMockBuilder('\SplitIO\Component\Log\Logger')
+            ->disableOriginalConstructor()
+            ->setMethods(array('warning', 'debug'))
+            ->getMock();
+        $impressionCache = new ImpressionCache($cachePool);
+        $impressionCache->logImpressions(array($impression), new QueueMetadataMessage());
     }
 
+    */
+
     public static function tearDownAfterClass(): void
     {
         Utils\Utils::cleanCache();
diff --git a/tests/Suite/Sdk/files/splitChanges.json b/tests/Suite/Sdk/files/splitChanges.json
index 84548e58..59e8a287 100644
--- a/tests/Suite/Sdk/files/splitChanges.json
+++ b/tests/Suite/Sdk/files/splitChanges.json
@@ -10,6 +10,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -69,6 +70,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -128,6 +130,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -164,6 +167,7 @@
             "status": "ACTIVE",
             "killed": true,
             "defaultTreatment": "defTreatment",
+            "sets": [],
             "configurations": {
                 "off": "{\"size\":15,\"test\":20}",
                 "defTreatment": "{\"size\":15,\"defTreatment\":true}"
@@ -204,6 +208,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "configurations": {
                 "on": "{\"size\":15,\"test\":20}"
             },
@@ -266,6 +271,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -305,6 +311,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -341,6 +348,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": ["set_b", "set_c"],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -366,6 +374,70 @@
                     ]
                 }
             ]
+        },
+        {
+            "orgId": null,
+            "environment": null,
+            "trafficTypeId": null,
+            "trafficTypeName": null,
+            "name": "flagsets_feature",
+            "seed": -1222652054,
+            "status": "ACTIVE",
+            "killed": false,
+            "defaultTreatment": "off",
+            "sets": ["set_a", "set_b", "set_c"],
+            "configurations": {
+                "on": "{\"size\":15,\"test\":20}",
+                "of": "{\"size\":15,\"defTreatment\":true}"
+            },
+            "conditions": [
+                {
+                    "matcherGroup": {
+                        "combiner": "AND",
+                        "matchers": [
+                            {
+                                "matcherType": "WHITELIST",
+                                "negate": false,
+                                "userDefinedSegmentMatcherData": null,
+                                "whitelistMatcherData": {
+                                    "whitelist": [
+                                        "whitelisted_user"
+                                    ]
+                                }
+                            }
+                        ]
+                    },
+                    "partitions": [
+                        {
+                            "treatment": "on",
+                            "size": 100
+                        }
+                    ]
+                },
+                {
+                    "matcherGroup": {
+                        "combiner": "AND",
+                        "matchers": [
+                            {
+                                "matcherType": "ALL_KEYS",
+                                "negate": false,
+                                "userDefinedSegmentMatcherData": null,
+                                "whitelistMatcherData": null
+                            }
+                        ]
+                    },
+                    "partitions": [
+                        {
+                            "treatment": "on",
+                            "size": 100
+                        },
+                        {
+                            "treatment": "off",
+                            "size": 0
+                        }
+                    ]
+                }
+            ]
         }
     ],
     "since": -1,
diff --git a/tests/Suite/Sdk/files/splitReadOnly.json b/tests/Suite/Sdk/files/splitReadOnly.json
index 748dd155..9f540ead 100644
--- a/tests/Suite/Sdk/files/splitReadOnly.json
+++ b/tests/Suite/Sdk/files/splitReadOnly.json
@@ -10,6 +10,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
diff --git a/tests/Suite/Sdk/files/splitil.json b/tests/Suite/Sdk/files/splitil.json
index 0753555f..2b7f689b 100644
--- a/tests/Suite/Sdk/files/splitil.json
+++ b/tests/Suite/Sdk/files/splitil.json
@@ -10,6 +10,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
diff --git a/tests/Suite/TrafficAllocation/TrafficAllocationTest.php b/tests/Suite/TrafficAllocation/TrafficAllocationTest.php
new file mode 100644
index 00000000..9aaac8c3
--- /dev/null
+++ b/tests/Suite/TrafficAllocation/TrafficAllocationTest.php
@@ -0,0 +1,73 @@
+shouldReceive([
+			'getBucket' => 1,
+			'getTreatment' => 'on',
+		]);
+
+        $logger = LoggerFactory::setupLogger(array('adapter' => 'stdout', 'level' => 'error'));
+        Context::setLogger($logger);
+
+		$rawSplit = array(
+		    'name' => 'test1',
+		    'algo' => 1,
+		    'killed' => false,
+		    'status' => 'ACTIVE',
+		    'defaultTreatment' => 'default',
+		    'seed' => -1222652054,
+		    'orgId' => null,
+		    'environment' => null,
+		    'trafficTypeId' => null,
+		    'trafficTypeName' => null,
+		    'conditions' => array(
+			array(
+			    'conditionType' => 'WHITELIST',
+			    'matcherGroup' => array(
+				'combiner' => 'AND',
+				'matchers' => array(
+				    array(
+					'matcherType' => 'ALL_KEYS',
+					'negate' => false,
+					'userDefinedSegmentMatcherData' => null,
+					'whitelistMatcherData' => null
+				    )
+				)
+			    ),
+			    'partitions' => array(
+				array(
+				    'treatment' => 'on',
+				    'size' => 100
+				)
+			    ),
+			    'label' => 'in segment all'
+			)
+		    )
+		);
+	    
+		$rawSplit['trafficAllocation'] = 1;
+		$rawSplit['trafficAllocationSeed'] = -1;
+		$split4 = new Split($rawSplit);
+		$treatment4 = \SplitIO\Engine::getTreatment('testKey', null, $split4);
+		$this->assertEquals('on', $treatment4[\SplitIO\Engine::EVALUATION_RESULT_TREATMENT]);
+
+		$rawSplit['trafficAllocation'] = 1;
+		$rawSplit['trafficAllocationSeed'] = -1;
+		$split4 = new Split($rawSplit);
+		$treatment4 = \SplitIO\Engine::getTreatment('testKey', null, $split4);
+		$this->assertEquals('on', $treatment4[\SplitIO\Engine::EVALUATION_RESULT_TREATMENT]);
+	}
+};
\ No newline at end of file
diff --git a/tests/Utils/Utils.php b/tests/Utils/Utils.php
index e2be6ec3..df602407 100644
--- a/tests/Utils/Utils.php
+++ b/tests/Utils/Utils.php
@@ -7,6 +7,7 @@ public static function addSplitsInCache($splitChanges)
     {
         $splitKey = "SPLITIO.split.";
         $tillKey = "SPLITIO.splits.till";
+        $flagSetKey = "SPLITIO.flagSet.";
 
         $predis = new \Predis\Client([
             'host' => REDIS_HOST,
@@ -23,6 +24,11 @@ public static function addSplitsInCache($splitChanges)
         foreach ($splits as $split) {
             $splitName = $split['name'];
             $predis->set($splitKey . $splitName, json_encode($split));
+
+            $sets = $split['sets'];
+            foreach ($sets as $set) {
+                $predis->sadd($flagSetKey . $set, $splitName);
+            }
         }
         $till = -1;
         if (isset($splitChanges['till'])) {
diff --git a/tests/files/algoSplits.json b/tests/files/algoSplits.json
index 67dac317..37e78bba 100644
--- a/tests/files/algoSplits.json
+++ b/tests/files/algoSplits.json
@@ -11,6 +11,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -71,6 +72,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -108,6 +110,7 @@
             "status": "ACTIVE",
             "killed": true,
             "defaultTreatment": "defTreatment",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
@@ -144,6 +147,7 @@
             "status": "ACTIVE",
             "killed": false,
             "defaultTreatment": "off",
+            "sets": [],
             "conditions": [
                 {
                     "matcherGroup": {
diff --git a/tests/files/splitChanges.json b/tests/files/splitChanges.json
index 35b44a35..30ab4981 100644
--- a/tests/files/splitChanges.json
+++ b/tests/files/splitChanges.json
@@ -9,6 +9,72 @@
       "seed": 301711069,
       "status": "ACTIVE",
       "killed": false,
+      "sets": ["set_a", "set_b", "set_c"],
+      "configurations": {
+        "on": "{\"size\":15,\"test\":20}"
+      },
+      "conditions": [
+        {
+          "matcherGroup": {
+            "combiner": "AND",
+            "matchers": [
+              {
+                "matcherType": "WHITELIST",
+                "negate": false,
+                "userDefinedSegmentMatcherData": null,
+                "whitelistMatcherData": {
+                  "whitelist": [
+                    "fake_user_id_6",
+                    "fake_user_id_7999"
+                  ]
+                }
+              }
+            ]
+          },
+          "partitions": [
+            {
+              "treatment": "on",
+              "size": 100
+            }
+          ]
+        },
+        {
+          "matcherGroup": {
+            "combiner": "AND",
+            "matchers": [
+              {
+                "matcherType": "IN_SEGMENT",
+                "negate": false,
+                "userDefinedSegmentMatcherData": {
+                  "segmentName": "employees"
+                },
+                "whitelistMatcherData": null
+              }
+            ]
+          },
+          "partitions": [
+            {
+              "treatment": "on",
+              "size": 80
+            },
+            {
+              "treatment": "control",
+              "size": 20
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "orgId": "bf083ab0-b402-11e5-b7d5-024293b5d101",
+      "environment": "bf9d9ce0-b402-11e5-b7d5-024293b5d101",
+      "name": "sample_feature_2",
+      "trafficTypeId": "u",
+      "trafficTypeName": "User",
+      "seed": 301711069,
+      "status": "ACTIVE",
+      "killed": false,
+      "sets": ["set_a", "set_b", "set_c"],
       "configurations": {
         "on": "{\"size\":15,\"test\":20}"
       },