Skip to content

Commit

Permalink
#13 Support new "match all" chain strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
migo315 committed Oct 4, 2019
1 parent 5bb3319 commit 76cba0a
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 4 deletions.
46 changes: 46 additions & 0 deletions docs/activator/chain.md
Expand Up @@ -24,3 +24,49 @@ class MyClass
}
}
```

The `ChainActivator` use the "first match" strategy. At least one activator must return `true` for activating the feature.
You can change the strategy to "match all". Then **all** activators must return `true` for activating the feature.

Pass your needed strategy via constructor argument ...

```php
// MyClass.php
class MyClass
{
public function doSomething()
{
$activator = new ChainActivator(ChainActivator::STRATEGY_ALL_MATCH);
// ...
}
}
```

... or by context:

```php
// MyClass.php
class MyClass
{
public function doSomething()
{
$activator = new ChainActivator();
$activator->add(new ArrayActivator([
'feature_foo'
]));
$activator->add(new YourCustomActivator());

$manager = new FeatureManager($activator);

$context = new Context();
$context->add('chain_strategy', ChainActivator::STRATEGY_ALL_MATCH);

// The array activator returns false, so it will request YourCustomActivator.
if ($manager->isActive('feature_def', $context)) {
// do something
}
}
}
```

The strategy defined in context will override the strategy defined in constructor.
54 changes: 50 additions & 4 deletions src/Activator/ChainActivator.php
Expand Up @@ -12,13 +12,45 @@
*/
class ChainActivator implements FeatureActivatorInterface
{
/**
* At least one activator must return true to activating the feature (default)
*/
const STRATEGY_FIRST_MATCH = 1;

/**
* All activators must return true to activating the feature
*/
const STRATEGY_ALL_MATCH = 2;

/**
* The reserved name for strategy override via context
*/
const CONTEXT_STRATEGY_NAME = 'chain_strategy';

/**
* Ordered array of feature activators
*
* @var FeatureActivatorInterface[]
*/
private $bag = [];

/**
* The used strategy
*
* @var int
*/
private $strategy;

/**
* ChainActivator constructor.
*
* @param int $strategy
*/
public function __construct($strategy = self::STRATEGY_FIRST_MATCH)
{
$this->strategy = $strategy;
}

/**
* Add activator
*
Expand Down Expand Up @@ -54,12 +86,26 @@ public function getName()
*/
public function isActive($name, Context $context)
{
foreach ($this->bag as $activator) {
if ($activator->isActive($name, $context) === true) {
return true;
$strategy = $context->get(self::CONTEXT_STRATEGY_NAME, $this->strategy);

if ($strategy === self::STRATEGY_ALL_MATCH) {
$result = true;
foreach ($this->bag as $activator) {
if ($activator->isActive($name, $context) === false) {
$result = false;
break;
}
}
} else {
$result = false;
foreach ($this->bag as $activator) {
if ($activator->isActive($name, $context) === true) {
$result = true;
break;
}
}
}

return false;
return $result;
}
}
106 changes: 106 additions & 0 deletions tests/Activator/ChainActivatorTest.php
Expand Up @@ -5,6 +5,7 @@
use Flagception\Activator\ArrayActivator;
use Flagception\Activator\ChainActivator;
use Flagception\Activator\FeatureActivatorInterface;
use Flagception\Exception\AlreadyDefinedException;
use Flagception\Model\Context;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -117,4 +118,109 @@ public function testAddAndGet()
static::assertSame($fakeActivator1, $decorator->getActivators()[0]);
static::assertSame($fakeActivator2, $decorator->getActivators()[1]);
}

/**
* Test match all strategy
*
* @return void
*/
public function testMatchAllStrategy()
{
$activator = new ChainActivator(ChainActivator::STRATEGY_ALL_MATCH);
$activator->add($firstActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($secondActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($thirdActivator = $this->createMock(FeatureActivatorInterface::class));

$firstActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$secondActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$thirdActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

static::assertTrue($activator->isActive('feature_abc', new Context()));
}

/**
* Test match all strategy break as soon as possible
*
* @return void
*/
public function testMatchAllStrategyBreakChain()
{
$activator = new ChainActivator(ChainActivator::STRATEGY_ALL_MATCH);
$activator->add($firstActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($secondActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($thirdActivator = $this->createMock(FeatureActivatorInterface::class));

$firstActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$secondActivator
->expects(static::once())
->method('isActive')
->willReturn(false);

$thirdActivator
->expects(static::never())
->method('isActive');

static::assertFalse($activator->isActive('feature_abc', new Context()));
}

/**
* Test match all strategy
*
* @return void
*
* @throws AlreadyDefinedException
*/
public function testContextOverrideStrategy()
{
$activator = new ChainActivator(ChainActivator::STRATEGY_FIRST_MATCH);
$activator->add($firstActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($secondActivator = $this->createMock(FeatureActivatorInterface::class));
$activator->add($thirdActivator = $this->createMock(FeatureActivatorInterface::class));

$firstActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$secondActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$thirdActivator
->expects(static::once())
->method('isActive')
->willReturn(true);

$context = new Context();
$context->add('chain_strategy', ChainActivator::STRATEGY_ALL_MATCH);
static::assertTrue($activator->isActive('feature_abc', $context));
}

/**
* Test public constants
*
* @return void
*/
public function testConstants()
{
static::assertEquals(1, ChainActivator::STRATEGY_FIRST_MATCH);
static::assertEquals(2, ChainActivator::STRATEGY_ALL_MATCH);
static::assertEquals('chain_strategy', ChainActivator::CONTEXT_STRATEGY_NAME);
}
}

0 comments on commit 76cba0a

Please sign in to comment.