Commit ede7119d authored by Emiliano Balbuena's avatar Emiliano Balbuena

(feat): Strategy Algorithms and resolver

1 merge request!2WIP: Clean up and strategies
Pipeline #105679728 passed with stage
in 1 minute and 6 seconds
<?php
/**
* StrategyAlgorithmFactory
*
* @author edgebal
*/
namespace Minds\UnleashClient\Factories;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Minds\UnleashClient\StrategyAlgorithms\StrategyAlgorithm;
use Psr\Log\LoggerInterface;
class StrategyAlgorithmFactory
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* StrategyAlgorithmFactory constructor.
* @param LoggerInterface|null $logger
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* Builds a new strategy algorithm instance
* @param Strategy $strategy
* @return StrategyAlgorithm|null
*/
public function build(Strategy $strategy): ?StrategyAlgorithm
{
$className = sprintf("\\Minds\\UnleashClient\\StrategyAlgorithms\\%sStrategyAlgorithm", ucfirst($strategy->getName()));
if (!class_exists($className)) {
$this->logger->warning("{$className} does not exist");
return null;
}
$strategyAlgorithm = new $className($this->logger);
if (!($strategyAlgorithm instanceof StrategyAlgorithm)) {
$this->logger->warning(sprintf("%s is not an %s instance", $strategyAlgorithm, StrategyAlgorithm::class));
return null;
}
$this->logger->debug("Created a new {$className} instance");
return $strategyAlgorithm;
}
}
......@@ -9,9 +9,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class ApplicationHostnameStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -26,6 +40,11 @@ class ApplicationHostnameStrategyAlgorithm implements StrategyAlgorithm
$hostNames = array_map([$this, 'normalizeHostName'], explode(',', $hostNamesList));
$this->logger->debug(static::class, [
$context->getHostName(),
$hostNames
]);
return in_array(strtolower($context->getHostName()), $hostNames, true);
}
......
......@@ -9,14 +9,30 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class DefaultStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
public function isEnabled(Strategy $strategy, Context $context): bool
{
$this->logger->debug(static::class, [ true ]);
return true;
}
}
......@@ -10,9 +10,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class FlexibleRolloutStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -20,7 +34,7 @@ class FlexibleRolloutStrategyAlgorithm implements StrategyAlgorithm
{
$parameters = $strategy->getParameters();
$percentage = $parameters['rollout'] ?? 0;
$percentage = intval($parameters['rollout'] ?? 0);
$stickiness = $parameters['stickiness'] ?? 'default';
$groupId = trim($parameters['groupId'] ?? '');
$userId = trim($context->getUserId() ?? '');
......@@ -50,6 +64,12 @@ class FlexibleRolloutStrategyAlgorithm implements StrategyAlgorithm
$stickinessValue = NormalizedValue::build($stickinessId, $groupId);
$this->logger->debug(static::class, [
$stickiness,
$stickinessValue,
$percentage
]);
return $percentage > 0 &&
$stickinessValue <= $percentage;
}
......
......@@ -9,9 +9,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class GradualRolloutRandomStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -19,9 +33,14 @@ class GradualRolloutRandomStrategyAlgorithm implements StrategyAlgorithm
{
$parameters = $strategy->getParameters();
$percentage = $parameters['percentage'] ?? 0;
$percentage = intval($parameters['percentage'] ?? 0);
$random = mt_rand(1, 100);
$this->logger->debug(static::class, [
$random,
$percentage
]);
return $percentage > 0 &&
$random <= $percentage;
}
......
......@@ -10,9 +10,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class GradualRolloutSessionIdStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -20,7 +34,7 @@ class GradualRolloutSessionIdStrategyAlgorithm implements StrategyAlgorithm
{
$parameters = $strategy->getParameters();
$percentage = $parameters['percentage'] ?? 0;
$percentage = intval($parameters['percentage'] ?? 0);
$groupId = trim($parameters['groupId'] ?? '');
$sessionId = trim($context->getSessionId() ?? '');
......@@ -30,6 +44,11 @@ class GradualRolloutSessionIdStrategyAlgorithm implements StrategyAlgorithm
$sessionIdValue = NormalizedValue::build($sessionId, $groupId);
$this->logger->debug(static::class, [
$sessionIdValue,
$percentage
]);
return $percentage > 0 &&
$sessionIdValue <= $percentage;
}
......
......@@ -10,9 +10,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class GradualRolloutUserIdStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -20,7 +34,7 @@ class GradualRolloutUserIdStrategyAlgorithm implements StrategyAlgorithm
{
$parameters = $strategy->getParameters();
$percentage = $parameters['percentage'] ?? 0;
$percentage = intval($parameters['percentage'] ?? 0);
$groupId = trim($parameters['groupId'] ?? '');
$userId = trim($context->getUserId() ?? '');
......@@ -30,6 +44,11 @@ class GradualRolloutUserIdStrategyAlgorithm implements StrategyAlgorithm
$userIdValue = NormalizedValue::build($userId, $groupId);
$this->logger->debug(static::class, [
$userIdValue,
$percentage
]);
return $percentage > 0 &&
$userIdValue <= $percentage;
}
......
......@@ -9,9 +9,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class RemoteAddressStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -26,6 +40,11 @@ class RemoteAddressStrategyAlgorithm implements StrategyAlgorithm
$ipAddresses = array_map([$this, 'normalizeIpAddress'], explode(',', $ipAddressesList));
$this->logger->debug(static::class, [
$context->getRemoteAddress(),
$ipAddresses
]);
return in_array(strtolower($context->getRemoteAddress()), $ipAddresses, true);
}
......
......@@ -9,9 +9,16 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Psr\Log\LoggerInterface;
interface StrategyAlgorithm
{
/**
* StrategyAlgorithm constructor.
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger);
/**
* Resolves a strategy using the context
* @param Strategy $strategy
......
......@@ -9,9 +9,23 @@ namespace Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Logger;
use Psr\Log\LoggerInterface;
class UserWithIdStrategyAlgorithm implements StrategyAlgorithm
{
/** @var LoggerInterface|Logger */
protected $logger;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
) {
$this->logger = $logger ?: new Logger();
}
/**
* @inheritDoc
*/
......@@ -26,6 +40,11 @@ class UserWithIdStrategyAlgorithm implements StrategyAlgorithm
$userIds = array_map([$this, 'normalizeUserId'], explode(',', $userIdsList));
$this->logger->debug(static::class, [
$context->getUserId(),
$userIds
]);
return in_array($context->getUserId(), $userIds, true);
}
......
<?php
/**
* StrategyResolver
*
* @author edgebal
*/
namespace Minds\UnleashClient;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Factories\StrategyAlgorithmFactory;
use Minds\UnleashClient\StrategyAlgorithms\StrategyAlgorithm;
use Psr\Log\LoggerInterface;
class StrategyResolver
{
/** @var LoggerInterface|Logger */
protected $logger;
/** @var StrategyAlgorithmFactory */
protected $strategyAlgorithmFactory;
/**
* StrategyResolver constructor.
* @param LoggerInterface|null $logger
* @param StrategyAlgorithm|null $strategyAlgorithmFactory
*/
public function __construct(
LoggerInterface $logger = null,
StrategyAlgorithm $strategyAlgorithmFactory = null
) {
$this->logger = $logger ?: new Logger();
$this->strategyAlgorithmFactory = $strategyAlgorithmFactory ?: new StrategyAlgorithmFactory($this->logger);
}
/**
* Instantiates a new strategy algorithm based on the passed strategy and run it against
* the context
* @param array $strategies
* @param Context $context
* @return bool
*/
public function isEnabled(array $strategies, Context $context): bool
{
foreach ($strategies as $strategy) {
$strategyAlgorithm = $this->strategyAlgorithmFactory
->build($strategy);
if ($strategyAlgorithm->isEnabled($strategy, $context)) {
return true;
}
}
return false;
}
}
......@@ -17,7 +17,7 @@ class Unleash
/** @var Config */
protected $config;
/** @var LoggerInterface */
/** @var Logger|LoggerInterface */
protected $logger;
/** @var Client */
......@@ -26,6 +26,9 @@ class Unleash
/** @var Cache\SimpleCache|CacheInterface */
protected $cache;
/** @var StrategyResolver */
protected $strategyResolver;
/** @var bool */
protected $isClientRegistered = false;
......@@ -38,18 +41,21 @@ class Unleash
* @param LoggerInterface|null $logger
* @param Client|null $client
* @param CacheInterface|null $cache
* @param StrategyResolver|null $strategyResolver
*/
public function __construct(
Config $config = null,
LoggerInterface $logger = null,
Client $client = null,
CacheInterface $cache = null
CacheInterface $cache = null,
StrategyResolver $strategyResolver = null
) {
$this->config = $config ?: new Config();
$this->logger = $logger ?: new Logger();
$this->client = $client ?: new Client($this->config, $this->logger);
$this->cache = $cache ?: new Cache\SimpleCache($this->logger);
$this->strategyResolver = $strategyResolver ?: new StrategyResolver($this->logger);
}
/**
......@@ -80,14 +86,19 @@ class Unleash
$this->fetch();
}
/** @var Entities\Feature|null */
/** @var Entities\Feature|null $feature */
$feature = $this->cache->get($this->buildCacheKey($featureName), null);
if ($feature === null) {
return $default;
}
return $feature->isEnabled();
return
$feature->isEnabled() &&
$this->strategyResolver->isEnabled(
$feature->getStrategies(),
$this->context
);
} catch (\Exception $e) {
$this->logger->error("Error checking feature flag {$featureName}");
$this->logger->error($e);
......
Please register or to comment