Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Toggle Refactor #24

Merged
merged 2 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Loading