Skip to content

Commit

Permalink
Feature Toggle Refactor (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaEstes authored Oct 20, 2023
1 parent 48f7021 commit ff70411
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 29 deletions.
39 changes: 39 additions & 0 deletions docs/components/feature-toggle/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,42 @@ title: Feature Toggle - Overview
```shell
composer require sonsofphp/feature-toggle
```

## Usage

```php
use SonsOfPHP\Component\FeatureToggle\Feature;
use SonsOfPHP\Component\FeatureToggle\Provider\InMemoryFeatureToggleProvider;
use SonsOfPHP\Component\FeatureToggle\Toggle\AlwaysEnabledToggle;

// Using a feature toggle provider
$provider = new InMemoryFeatureToggleProvider();
$provider->addFeature(new Feature('feature.example', new AlwaysEnabledToggle()));

$feature = $provider->getFeatureToggleByKey('feature.example');

// Checking if the feature is enabled
$isEnabled = $feature->isEnabled();
```

## Advanced Usage

```php
// ...
use SonsOfPHP\Component\FeatureToggle\Context;

// Different ways to build the context, the context
// is used by the Toggle
$context = new Context([
'user' => $user,
]);
$context->set('user', $user);
$context['user'] = $user;

$isEnabled = $feature->isEnabled($context);
```

## Create your own Toggle

Take a look at the `AlwaysEnabledToggle` and `AlwaysDisabledToggle`. This is how
more complex Toggles can be created. These two are as simple as you can get.
44 changes: 37 additions & 7 deletions packages/feature-toggle/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,56 @@
*/
final class Context implements ContextInterface
{
private array $data = [];
public function __construct(private array $data = []) {}

public function get(string $key)
public function offsetSet($offset, $value): void
{
if ($this->has($key)) {
return $this->data[$key];
if (null === $offset) {
throw new \Exception('Requires a key');
}

return null;
$this->data[$offset] = $value;
}

public function offsetExists($offset): bool
{
return \array_key_exists($offset, $this->data);
}

public function offsetUnset($offset): void
{
unset($this->data[$offset]);
}

public function offsetGet($offset): mixed
{
return $this->data[$offset] ?? null;
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->data);
}

public function jsonSerialize(): mixed
{
return $this->data;
}

public function get(string $key): mixed
{
return $this->offsetGet($key);
}

public function set(string $key, $value): ContextInterface
{
$this->data[$key] = $value;
$this->offsetSet($key, $value);

return $this;
}

public function has(string $key): bool
{
return \array_key_exists($key, $this->data);
return $this->offsetExists($key);
}
}
2 changes: 1 addition & 1 deletion packages/feature-toggle/ContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* @author Joshua Estes <[email protected]>
*/
interface ContextInterface
interface ContextInterface extends \ArrayAccess, \IteratorAggregate, \JsonSerializable
{
public function get(string $key);

Expand Down
2 changes: 1 addition & 1 deletion packages/feature-toggle/Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function getKey(): string
return $this->key;
}

public function isEnabled(ContextInterface $context): bool
public function isEnabled(?ContextInterface $context = null): bool
{
return $this->toggle->isEnabled($context);
}
Expand Down
7 changes: 1 addition & 6 deletions packages/feature-toggle/FeatureInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,12 @@
*
* @author Joshua Estes <[email protected]>
*/
interface FeatureInterface
interface FeatureInterface extends ToggleInterface
{
/**
* Returns the key that is used for the feature toggle. The key should be
* unique enough that you can say "is_granted('feature_toggle_key')" and not
* get them all mixed up.
*/
public function getKey(): string;

/**
* Returns true or false if the toggle is enabled.
*/
public function isEnabled(ContextInterface $context): bool;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,5 @@
*/
interface FeatureToggleProviderInterface
{
/**
* @return FeatureInterface[]
*/
public function getFeatures(): iterable;
public function getFeatureToggleByKey(string $key): ?FeatureInterface;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class InMemoryFeatureToggleProvider implements FeatureToggleProviderInterf
{
private array $features = [];

public function __construct($features = [])
public function __construct(array $features = [])
{
foreach ($features as $feature) {
$this->addFeature($feature);
Expand All @@ -22,11 +22,15 @@ public function __construct($features = [])

public function addFeature(FeatureInterface $feature): void
{
$this->features[] = $feature;
$this->features[$feature->getKey()] = $feature;
}

public function getFeatures(): iterable
public function getFeatureToggleByKey(string $key): ?FeatureInterface
{
yield from $this->features;
if (isset($this->features[$key])) {
return $this->features[$key];
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use SonsOfPHP\Component\FeatureToggle\Provider\FeatureToggleProviderInterface;
use SonsOfPHP\Component\FeatureToggle\Provider\InMemoryFeatureToggleProvider;
use SonsOfPHP\Component\FeatureToggle\ToggleInterface;
use PHPUnit\Framework\MockObject;

/**
* @coversDefaultClass \SonsOfPHP\Component\FeatureToggle\Provider\InMemoryFeatureToggleProvider
Expand All @@ -17,7 +18,7 @@
*/
final class InMemoryFeatureToggleProviderTest extends TestCase
{
private $toggle;
private MockObject|ToggleInterface $toggle;

protected function setUp(): void
{
Expand All @@ -37,7 +38,7 @@ public function testItHasTheCorrectInterface(): void
/**
* @covers ::__construct
* @covers ::addFeature
* @covers ::getFeatures
* @covers ::getFeatureToggleByKey
*/
public function testAddingFeatures(): void
{
Expand All @@ -48,6 +49,8 @@ public function testAddingFeatures(): void

$provider = new InMemoryFeatureToggleProvider($features);

$this->assertCount(2, iterator_to_array($provider->getFeatures()));
$this->assertNotNull($provider->getFeatureToggleByKey('test.one'));
$this->assertNotNull($provider->getFeatureToggleByKey('test.two'));
$this->assertNull($provider->getFeatureToggleByKey('test.three'));
}
}
2 changes: 1 addition & 1 deletion packages/feature-toggle/Toggle/AlwaysDisabledToggle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
class AlwaysDisabledToggle implements ToggleInterface
{
public function isEnabled(ContextInterface $context): bool
public function isEnabled(?ContextInterface $context = null): bool
{
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/feature-toggle/Toggle/AlwaysEnabledToggle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
class AlwaysEnabledToggle implements ToggleInterface
{
public function isEnabled(ContextInterface $context): bool
public function isEnabled(?ContextInterface $context = null): bool
{
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/feature-toggle/ToggleInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ interface ToggleInterface
/**
* Returns true if this strategy is enabled.
*/
public function isEnabled(ContextInterface $context): bool;
public function isEnabled(?ContextInterface $context = null): bool;
}

0 comments on commit ff70411

Please sign in to comment.