...
 
Commits (5)
<?php
namespace Minds\UnleashClient;
require('vendor/autoload.php');
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Unleash;
use Minds\UnleashClient\Config;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Logger;
use Minds\UnleashClient\Unleash;
function main() : void
{
......
<?php
require('vendor/autoload.php');
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\Logger;
function main()
{
$logger = new Logger();
$logger->debug('Unleash normalizer stats');
$normalizedValue = new NormalizedValue();
$stats = array_fill(1, 100, 0);
$min = 1;
$max = 999999;
$groupId = 'default';
for ($i = $min; $i <= $max; $i++) {
$id = $normalizedValue->build("$i", $groupId, 100, 1);
$stats[(int) $id]++;
}
var_export($stats);
}
main();
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\StrategyAlgorithms\ApplicationHostnameStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class ApplicationHostnameStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
public function let(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->beConstructedWith($logger);
}
public function it_is_initializable()
{
$this->shouldHaveType(ApplicationHostnameStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'hostNames' => 'foo.bar, phpspec.test, minds.com'
]);
$context->getHostName()
->shouldBeCalled()
->willReturn('phpspec.test');
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'hostNames' => 'foo.bar, phpspec.test, minds.com'
]);
$context->getHostName()
->shouldBeCalled()
->willReturn('notawhitelisteddomain.com');
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\StrategyAlgorithms\DefaultStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class DefaultStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
public function let(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->beConstructedWith($logger);
}
public function it_is_initializable()
{
$this->shouldHaveType(DefaultStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\StrategyAlgorithms\FlexibleRolloutStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class FlexibleRolloutStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
/** @var NormalizedValue */
protected $normalizedValue;
public function let(
LoggerInterface $logger,
NormalizedValue $normalizedValue
) {
$this->logger = $logger;
$this->normalizedValue = $normalizedValue;
$this->beConstructedWith($logger, $normalizedValue);
}
public function it_is_initializable()
{
$this->shouldHaveType(FlexibleRolloutStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled_with_user_id_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'userId',
'groupId' => 'test'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1000');
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('1000', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled_with_user_id_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'userId',
'groupId' => 'test'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1000');
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('1000', 'test')
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
public function it_should_check_it_is_enabled_with_session_id_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'sessionId',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->shouldBeCalled()
->willReturn('phpspec~123123');
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('phpspec~123123', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled_with_session_id_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'sessionId',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->shouldBeCalled()
->willReturn('phpspec~123123');
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('phpspec~123123', 'test')
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
public function it_should_check_it_is_enabled_with_random_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'random',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->random(999999, 1)
->shouldBeCalled()
->willReturn(99);
$this->normalizedValue->build('99', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled_with_random_stickiness(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'random',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->shouldBeCalled()
->willReturn(99);
$this->normalizedValue->build('99', 'test')
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
public function it_should_check_it_is_enabled_with_default_stickiness_using_user_id(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'default',
'groupId' => 'test'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1000');
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('1000', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_enabled_with_default_stickiness_using_session_id(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'default',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->shouldBeCalled()
->willReturn('phpspec~123123');
$this->normalizedValue->random(999999, 1)
->willReturn(99);
$this->normalizedValue->build('phpspec~123123', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_enabled_with_default_stickiness_using_random(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'rollout' => 20,
'stickiness' => 'default',
'groupId' => 'test'
]);
$context->getUserId()
->willReturn(null);
$context->getSessionId()
->willReturn(null);
$this->normalizedValue->random(999999, 1)
->shouldBeCalled()
->willReturn(99);
$this->normalizedValue->build('99', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\StrategyAlgorithms\GradualRolloutRandomStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class GradualRolloutRandomStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
/** @var NormalizedValue */
protected $normalizedValue;
public function let(
LoggerInterface $logger,
NormalizedValue $normalizedValue
) {
$this->logger = $logger;
$this->normalizedValue = $normalizedValue;
$this->beConstructedWith($logger, $normalizedValue);
}
public function it_is_initializable()
{
$this->shouldHaveType(GradualRolloutRandomStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20
]);
$this->normalizedValue->random()
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20
]);
$this->normalizedValue->random()
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\StrategyAlgorithms\GradualRolloutSessionIdStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class GradualRolloutSessionIdStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
/** @var NormalizedValue */
protected $normalizedValue;
public function let(
LoggerInterface $logger,
NormalizedValue $normalizedValue
) {
$this->logger = $logger;
$this->normalizedValue = $normalizedValue;
$this->beConstructedWith($logger, $normalizedValue);
}
public function it_is_initializable()
{
$this->shouldHaveType(GradualRolloutSessionIdStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20,
'groupId' => 'test'
]);
$context->getSessionId()
->shouldBeCalled()
->willReturn('phpspec~123');
$this->normalizedValue->build('phpspec~123', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_no_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20,
'groupId' => 'test'
]);
$context->getSessionId()
->shouldBeCalled()
->willReturn('phpspec~123');
$this->normalizedValue->build('phpspec~123', 'test')
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\Helpers\NormalizedValue;
use Minds\UnleashClient\StrategyAlgorithms\GradualRolloutUserIdStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class GradualRolloutUserIdStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
/** @var NormalizedValue */
protected $normalizedValue;
public function let(
LoggerInterface $logger,
NormalizedValue $normalizedValue
) {
$this->logger = $logger;
$this->normalizedValue = $normalizedValue;
$this->beConstructedWith($logger, $normalizedValue);
}
public function it_is_initializable()
{
$this->shouldHaveType(GradualRolloutUserIdStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20,
'groupId' => 'test'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1000');
$this->normalizedValue->build('1000', 'test')
->shouldBeCalled()
->willReturn(10);
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_no_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'percentage' => 20,
'groupId' => 'test'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1000');
$this->normalizedValue->build('1000', 'test')
->shouldBeCalled()
->willReturn(90);
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\StrategyAlgorithms\RemoteAddressStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class RemoteAddressStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
public function let(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->beConstructedWith($logger);
}
public function it_is_initializable()
{
$this->shouldHaveType(RemoteAddressStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'IPs' => '10.0.0.1, 127.0.0.1, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
]);
$context->getRemoteAddress()
->shouldBeCalled()
->willReturn('127.0.0.1');
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_enabled_with_ipv6_casing(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'IPs' => '10.0.0.1, 127.0.0.1, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
]);
$context->getRemoteAddress()
->shouldBeCalled()
->willReturn('2001:0DB8:85A3:0000:0000:8A2E:0370:7334');
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'IPs' => '10.0.0.1, 127.0.0.1, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
]);
$context->getRemoteAddress()
->shouldBeCalled()
->willReturn('192.168.0.200');
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
<?php
namespace spec\Minds\UnleashClient\StrategyAlgorithms;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Entities\Strategy;
use Minds\UnleashClient\StrategyAlgorithms\UserWithIdStrategyAlgorithm;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Log\LoggerInterface;
class UserWithIdStrategyAlgorithmSpec extends ObjectBehavior
{
/** @var LoggerInterface */
protected $logger;
public function let(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->beConstructedWith($logger);
}
public function it_is_initializable()
{
$this->shouldHaveType(UserWithIdStrategyAlgorithm::class);
}
public function it_should_check_it_is_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'userIds' => '1000,1005, 1010'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('1005');
$this
->isEnabled($strategy, $context)
->shouldReturn(true);
}
public function it_should_check_it_is_not_enabled(
Strategy $strategy,
Context $context
) {
$strategy->getParameters()
->shouldBeCalled()
->willReturn([
'userIds' => '1000,1005, 1010'
]);
$context->getUserId()
->shouldBeCalled()
->willReturn('7777');
$this
->isEnabled($strategy, $context)
->shouldReturn(false);
}
}
......@@ -13,18 +13,31 @@ class NormalizedValue
{
/**
* Normalizes a value using Murmur3 algorithm hash and a normalizer modulus.
* Returns a value from 1 to 100 if ID is truthy, if not it returns 0.
* Returns a value from $min (default 1) to $normalizer (default 100) if
* ID is truthy, if not it returns $min - 1 (default 0).
* @param string $id
* @param string $groupId
* @param int $normalizer
* @param int $min
* @return int
*/
public function build(string $id, string $groupId, int $normalizer = 100): int
public function build(string $id, string $groupId, int $normalizer = 100, int $min = 1): int
{
if (!$id) {
return 0;
return $min - 1;
}
return (Murmur::hash3_int("{$id}:{$groupId}") % $normalizer) + 1;
return (Murmur::hash3_int("{$id}:{$groupId}") % $normalizer) + $min;
}
/**
* Returns a random value from $min (default 1) to $normalizer (default 100).
* @param int $normalizer
* @param int $min
* @return int
*/
public function random($normalizer = 100, $min = 1): int
{
return mt_rand($min, $normalizer);
}
}
......@@ -45,6 +45,7 @@ class FlexibleRolloutStrategyAlgorithm implements StrategyAlgorithm
$groupId = trim($parameters['groupId'] ?? '');
$userId = trim($context->getUserId() ?? '');
$sessionId = trim($context->getSessionId() ?? '');
$randomId = sprintf("%s", $this->normalizedValue->random(999999, 1));
switch ($stickiness) {
case 'userId':
......@@ -56,11 +57,11 @@ class FlexibleRolloutStrategyAlgorithm implements StrategyAlgorithm
break;
case 'random':
$stickinessId = sprintf("%s", mt_rand(1, 100));
$stickinessId = $randomId;
break;
default:
$stickinessId = $userId ?: $sessionId ?: sprintf("%s", mt_rand(1, 100));
$stickinessId = $userId ?: $sessionId ?: $randomId;
break;
}
......
......@@ -9,6 +9,7 @@ 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;
......@@ -17,13 +18,18 @@ class GradualRolloutRandomStrategyAlgorithm implements StrategyAlgorithm
/** @var LoggerInterface|Logger */
protected $logger;
/** @var NormalizedValue */
protected $normalizedValue;
/**
* @inheritDoc
*/
public function __construct(
LoggerInterface $logger = null
LoggerInterface $logger = null,
NormalizedValue $normalizedValue = null
) {
$this->logger = $logger ?: new Logger();
$this->normalizedValue = $normalizedValue ?: new NormalizedValue();
}
/**
......@@ -34,7 +40,7 @@ class GradualRolloutRandomStrategyAlgorithm implements StrategyAlgorithm
$parameters = $strategy->getParameters();
$percentage = intval($parameters['percentage'] ?? 0);
$random = mt_rand(1, 100);
$random = $this->normalizedValue->random();
$this->logger->debug(static::class, [
$random,
......