From f158cf017fdaa04e5cb13533776b66b496e63ba2 Mon Sep 17 00:00:00 2001 From: Matias Melograno Date: Thu, 1 Feb 2024 17:13:36 -0300 Subject: [PATCH] merged with latest release --- CHANGES.txt | 10 + src/SplitIO/Component/Cache/Pool.php | 5 + src/SplitIO/Component/Cache/SplitCache.php | 23 ++ .../Component/Cache/SplitCacheInterface.php | 6 + .../Adapter/CacheStorageAdapterInterface.php | 6 + .../Cache/Storage/Adapter/PRedis.php | 9 + .../Storage/Adapter/SafeRedisWrapper.php | 16 ++ src/SplitIO/Grammar/Split.php | 10 + src/SplitIO/Metrics.php | 5 - src/SplitIO/Sdk/Client.php | 201 +++++++++++++--- src/SplitIO/Sdk/ClientInterface.php | 118 +++++++++ src/SplitIO/Sdk/Evaluator.php | 29 ++- src/SplitIO/Sdk/LocalhostClient.php | 24 ++ .../Sdk/Manager/LocalhostSplitManager.php | 4 +- src/SplitIO/Sdk/Manager/SplitManager.php | 5 +- src/SplitIO/Sdk/Manager/SplitView.php | 50 +++- .../Sdk/Validator/FlagSetsValidator.php | 62 +++++ src/SplitIO/Sdk/Validator/InputValidator.php | 4 +- src/SplitIO/Version.php | 2 +- .../Suite/Attributes/files/splitChanges.json | 31 +++ .../DynamicConfigurations/EvaluatorTest.php | 47 ++++ .../InputValidation/FlagSetsValidatorTest.php | 136 +++++++++++ .../GetTreatmentValidationTest.php | 1 + tests/Suite/Redis/CacheInterfacesTest.php | 5 +- tests/Suite/Redis/SafeRedisWrapperTest.php | 6 +- tests/Suite/Sdk/SdkClientTest.php | 223 +++++++++++++++++- tests/Suite/Sdk/files/splitChanges.json | 72 ++++++ tests/Suite/Sdk/files/splitReadOnly.json | 1 + tests/Suite/Sdk/files/splitil.json | 1 + tests/Utils/Utils.php | 6 + tests/files/algoSplits.json | 4 + tests/files/splitChanges.json | 66 ++++++ 32 files changed, 1131 insertions(+), 57 deletions(-) create mode 100644 src/SplitIO/Sdk/Validator/FlagSetsValidator.php create mode 100644 tests/Suite/InputValidation/FlagSetsValidatorTest.php diff --git a/CHANGES.txt b/CHANGES.txt index 972a8855..f3338d24 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,6 +2,16 @@ - 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/src/SplitIO/Component/Cache/Pool.php b/src/SplitIO/Component/Cache/Pool.php index 88e22744..43bdf504 100644 --- a/src/SplitIO/Component/Cache/Pool.php +++ b/src/SplitIO/Component/Cache/Pool.php @@ -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/SplitCache.php b/src/SplitIO/Component/Cache/SplitCache.php index 138a5171..6eae5403 100644 --- a/src/SplitIO/Component/Cache/SplitCache.php +++ b/src/SplitIO/Component/Cache/SplitCache.php @@ -9,6 +9,8 @@ class SplitCache implements SplitCacheInterface const KEY_TRAFFIC_TYPE_CACHED = 'SPLITIO.trafficType.{trafficTypeName}'; + const KEY_FLAG_SET_CACHED = 'SPLITIO.flagSet.{set}'; + /** * @var \SplitIO\Component\Cache\Pool */ @@ -37,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(''); @@ -87,6 +94,22 @@ public function getSplitNames() 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 */ diff --git a/src/SplitIO/Component/Cache/SplitCacheInterface.php b/src/SplitIO/Component/Cache/SplitCacheInterface.php index a667e48c..9ec14ffc 100644 --- a/src/SplitIO/Component/Cache/SplitCacheInterface.php +++ b/src/SplitIO/Component/Cache/SplitCacheInterface.php @@ -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 e5a4a680..1c6b8dca 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/PRedis.php @@ -184,6 +184,15 @@ public function isOnList($key, $value) return $this->client->sIsMember($key, $value); } + /** + * @param string $key + * @return mixed + */ + public function sMembers($key) + { + return $this->client->smembers($key); + } + public function getKeys($pattern = '*') { $keys = $this->client->keys($pattern); diff --git a/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php b/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php index 5f26405e..a1dc5e96 100644 --- a/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php +++ b/src/SplitIO/Component/Cache/Storage/Adapter/SafeRedisWrapper.php @@ -123,4 +123,20 @@ public function expireKey($key, $ttl) 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/Grammar/Split.php b/src/SplitIO/Grammar/Split.php index 78dfc6ba..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); @@ -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/Client.php b/src/SplitIO/Sdk/Client.php index bb65c354..9e2ac24c 100644 --- a/src/SplitIO/Sdk/Client.php +++ b/src/SplitIO/Sdk/Client.php @@ -1,7 +1,6 @@ TreatmentEnum::CONTROL, 'config' => null); @@ -156,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'], @@ -177,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 " . @@ -196,7 +195,6 @@ public function getTreatment($key, $featureName, array $attributes = null) try { $result = $this->doEvaluation( 'getTreatment', - Metrics::MNAME_SDK_GET_TREATMENT, $key, $featureName, $attributes @@ -216,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 @@ -261,7 +258,7 @@ private function doInputValidationForTreatments($key, $featureFlagNames, array $ ); } - private function registerData($impressions, $attributes, $metricName, $latency = null) + private function registerData($impressions, $attributes) { try { if (is_null($impressions) || (is_array($impressions) && 0 == count($impressions))) { @@ -291,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)) { @@ -306,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()); @@ -355,7 +336,6 @@ function ($feature) { }, $this->doEvaluationForTreatments( 'getTreatments', - Metrics::MNAME_SDK_GET_TREATMENTS, $key, $featureFlagNames, $attributes @@ -376,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 @@ -442,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 b070116e..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 *

diff --git a/src/SplitIO/Sdk/Evaluator.php b/src/SplitIO/Sdk/Evaluator.php
index ac958320..6853850c 100644
--- a/src/SplitIO/Sdk/Evaluator.php
+++ b/src/SplitIO/Sdk/Evaluator.php
@@ -29,7 +29,6 @@ public function __construct(SplitCache $splitCache, SegmentCache $segmentCache)
         $this->segmentCache = $segmentCache;
     }
 
-
     private function fetchSplit($featureName)
     {
         $splitCachedItem = $this->splitCache->getSplit($featureName);
@@ -55,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();
@@ -78,6 +96,15 @@ 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(
diff --git a/src/SplitIO/Sdk/LocalhostClient.php b/src/SplitIO/Sdk/LocalhostClient.php
index 2af44546..139446a1 100644
--- a/src/SplitIO/Sdk/LocalhostClient.php
+++ b/src/SplitIO/Sdk/LocalhostClient.php
@@ -257,4 +257,28 @@ public function track($key, $trafficType, $eventType, $value = null, $properties
     {
         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 0e0e4b83..0d138faa 100644
--- a/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php
+++ b/src/SplitIO/Sdk/Manager/LocalhostSplitManager.php
@@ -95,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 51613df1..44554aa1 100644
--- a/src/SplitIO/Sdk/Manager/SplitManager.php
+++ b/src/SplitIO/Sdk/Manager/SplitManager.php
@@ -65,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(
@@ -74,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 27f2f07e..9862da6c 100644
--- a/src/SplitIO/Sdk/Validator/InputValidator.php
+++ b/src/SplitIO/Sdk/Validator/InputValidator.php
@@ -67,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;
@@ -262,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/Version.php b/src/SplitIO/Version.php
index 97f8446b..2f4d6399 100644
--- a/src/SplitIO/Version.php
+++ b/src/SplitIO/Version.php
@@ -3,5 +3,5 @@
 
 class Version
 {
-    const CURRENT = '8.0.0-rc3';
+    const CURRENT = '8.0.0-rc4';
 }
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/DynamicConfigurations/EvaluatorTest.php b/tests/Suite/DynamicConfigurations/EvaluatorTest.php
index b57db811..c1dfbfd3 100644
--- a/tests/Suite/DynamicConfigurations/EvaluatorTest.php
+++ b/tests/Suite/DynamicConfigurations/EvaluatorTest.php
@@ -172,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/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 a4f3ba8f..2c83e812 100644
--- a/tests/Suite/InputValidation/GetTreatmentValidationTest.php
+++ b/tests/Suite/InputValidation/GetTreatmentValidationTest.php
@@ -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/Redis/CacheInterfacesTest.php b/tests/Suite/Redis/CacheInterfacesTest.php
index 8e94bb65..6767b1a2 100644
--- a/tests/Suite/Redis/CacheInterfacesTest.php
+++ b/tests/Suite/Redis/CacheInterfacesTest.php
@@ -61,11 +61,14 @@ public function testSplitCacheInterface()
         $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']));
     }
 
     /**
diff --git a/tests/Suite/Redis/SafeRedisWrapperTest.php b/tests/Suite/Redis/SafeRedisWrapperTest.php
index 320f2999..b60b9c8d 100644
--- a/tests/Suite/Redis/SafeRedisWrapperTest.php
+++ b/tests/Suite/Redis/SafeRedisWrapperTest.php
@@ -1,9 +1,6 @@
 getMockBuilder('\Predis\Client')
             ->disableOriginalConstructor()
@@ -49,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/SdkClientTest.php b/tests/Suite/Sdk/SdkClientTest.php
index 765fcc99..ddd27fed 100644
--- a/tests/Suite/Sdk/SdkClientTest.php
+++ b/tests/Suite/Sdk/SdkClientTest.php
@@ -4,15 +4,13 @@
 use \stdClass;
 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\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
@@ -207,6 +205,40 @@ 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()
     {
         ReflectiveTools::resetContext();
@@ -746,6 +778,189 @@ public function testMultipleInstantiationNotOverrideIP()
         $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/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/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}"
       },