Skip to content

Commit

Permalink
Merge 0058b62 into 6e321f6
Browse files Browse the repository at this point in the history
  • Loading branch information
RikudouSage authored May 6, 2022
2 parents 6e321f6 + 0058b62 commit 6a225da
Show file tree
Hide file tree
Showing 18 changed files with 942 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/.github export-ignore
/doc export-ignore
/examples export-ignore
/tests export-ignore
/.gitattributes export-ignore
Expand All @@ -7,6 +8,7 @@
/.php-cs-fixer.dist.php export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml export-ignore
/README.md export-ignore
/rector.72.php export-ignore
/rector.73.php export-ignore
/rector.74.php export-ignore
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ or

`composer require unleash/client symfony/http-client nyholm/psr7 symfony/cache`

If you want to make use of events you also need to install `symfony/event-dispatcher`.
See [event documentation here](doc/events.md).

## Usage

The basic usage is getting the `Unleash` object and checking for a feature:
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"phpunit/phpunit": "^9.5",
"symfony/http-client": "^5.0 | ^6.0",
"nyholm/psr7": "^1.0",
"symfony/cache": "^5.0 | ^6.0"
"symfony/cache": "^5.0 | ^6.0",
"symfony/event-dispatcher": "^5.0 | ^6.0"
},
"autoload-dev": {
"psr-4": {
Expand All @@ -48,7 +49,8 @@
"guzzlehttp/guzzle": "A http client implementation (PSR-17 and PSR-18)",
"symfony/http-client": "A http client implementation (PSR-17 and PSR-18)",
"nyholm/psr7": "Needed when you use symfony/http-client",
"unleash/symfony-client-bundle": "The Symfony bundle for this library"
"unleash/symfony-client-bundle": "The Symfony bundle for this library",
"symfony/event-dispatcher": "Needed when you want to react to events from Unleash"
},
"scripts": {
"fixer": "php-cs-fixer fix --verbose --allow-risky=yes",
Expand Down
223 changes: 223 additions & 0 deletions doc/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Event system in Unleash PHP SDK

This SDK supports events using [`symfony/event-dispatcher`](https://packagist.org/packages/symfony/event-dispatcher).

## Installation

The event dispatcher is not a mandatory component of this SDK, so you need to install it by running:

`composer require symfony/event-dispatcher`

## Usage

You can add event subscribers to the builder object:

```php
<?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Unleash\Client\Event\FeatureToggleDisabledEvent;
use Unleash\Client\Event\FeatureToggleMissingStrategyHandlerEvent;
use Unleash\Client\Event\FeatureToggleNotFoundEvent;
use Unleash\Client\Event\FeatureVariantBeforeFallbackReturnedEvent;
use Unleash\Client\UnleashBuilder;
use Unleash\Client\Event\UnleashEvents;

class MyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
UnleashEvents::FEATURE_TOGGLE_DISABLED => 'onFeatureDisabled',
UnleashEvents::FEATURE_TOGGLE_MISSING_STRATEGY_HANDLER => 'onNoStrategyHandler',
UnleashEvents::FEATURE_TOGGLE_NOT_FOUND => 'onFeatureNotFound',
];
}

public function onFeatureDisabled(FeatureToggleDisabledEvent $event)
{
// todo
}

public function onNoStrategyHandler(FeatureToggleMissingStrategyHandlerEvent $event)
{
// todo
}

public function onFeatureNotFound(FeatureToggleNotFoundEvent $event)
{
// todo
}
}

$unleash = UnleashBuilder::create()
->withAppName('My App')
->withAppUrl('http://localhost:4242')
->withInstanceId('test')
->withEventSubscriber(new MyEventSubscriber())
->build();

$unleash->isEnabled('test');
```

The relevant methods will be called in the above example when their respective event occurs.

### List of events:

- `\Unleash\Client\Event\UnleashEvents::FEATURE_TOGGLE_NOT_FOUND` - when a feature with the name isn't found on the
unleash server (or in the bootstrap if it's used). Event object: `Unleash\Client\Event\FeatureToggleNotFoundEvent`
- `\Unleash\Client\Event\UnleashEvents::FEATURE_TOGGLE_DISABLED` - when a feature is found but it's disabled.
Event object: Unleash\Client\Event\FeatureToggleDisabledEvent
- `\Unleash\Client\Event\UnleashEvents::FEATURE_TOGGLE_MISSING_STRATEGY_HANDLER` - when there is no suitable strategy handler
implemented for any of the feature's strategies. Event object: `Unleash\Client\Event\FeatureToggleMissingStrategyHandlerEvent`

## FEATURE_TOGGLE_NOT_FOUND event

Example:

```php
<?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Unleash\Client\Event\FeatureToggleNotFoundEvent;
use Unleash\Client\Event\UnleashEvents;
use Unleash\Client\DTO\DefaultFeature;
use Unleash\Client\UnleashBuilder;

final class MyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
UnleashEvents::FEATURE_TOGGLE_NOT_FOUND => 'onNotFound',
];
}

public function onNotFound(FeatureToggleNotFoundEvent $event): void
{
// methods:
$event->getFeatureName(); // string
$event->getContext(); // instance of Context
}
}

$unleash = UnleashBuilder::create()
->withEventSubscriber(new MyEventSubscriber())
->build();
```

## FEATURE_TOGGLE_DISABLED event

Example:

```php
<?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Unleash\Client\Event\FeatureToggleDisabledEvent;
use Unleash\Client\Event\UnleashEvents;
use Unleash\Client\DTO\DefaultFeature;
use Unleash\Client\DTO\DefaultStrategy;

final class MyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
UnleashEvents::FEATURE_TOGGLE_DISABLED => 'onFeatureDisabled',
];
}

public function onFeatureDisabled(FeatureToggleDisabledEvent $event): void
{
// methods:
$event->getContext(); // instance of Context
$event->getFeature(); // instance of Feature
}
}
```

## FEATURE_TOGGLE_MISSING_STRATEGY_HANDLER event

Triggered when no strategy handler can be found for any of the strategies.

Example:

```php
<?php

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Unleash\Client\Event\UnleashEvents;
use Unleash\Client\UnleashBuilder;
use Unleash\Client\Event\FeatureToggleMissingStrategyHandlerEvent;
use Unleash\Client\Strategy\DefaultStrategyHandler;

final class MyEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
UnleashEvents::FEATURE_TOGGLE_MISSING_STRATEGY_HANDLER => 'onMissingStrategyHandler',
];
}

public function onMissingStrategyHandler(FeatureToggleMissingStrategyHandlerEvent $event): void
{
// methods:
$event->getContext(); // instance of Context
$event->getFeature(); // instance of Feature
// get strategies
$event->getFeature()->getStrategies(); // iterable of Strategy instances
}
}
```

## Customizing event dispatcher

If you already use event dispatcher in your app, you can provide it to the builder:

```php
<?php

use Symfony\Component\EventDispatcher\EventDispatcher;
use Unleash\Client\UnleashBuilder;

$eventDispatcher = new EventDispatcher();

// do something with event dispatcher

$unleash = UnleashBuilder::create()
->withEventDispatcher($eventDispatcher)
// add other unleash configurations
->build();
```

All event subscribers/listeners registered directly in the event dispatcher work as usual:

```php
<?php

use Symfony\Component\EventDispatcher\EventDispatcher;
use Unleash\Client\UnleashBuilder;
use Unleash\Client\Event\UnleashEvents;
use Unleash\Client\Event\FeatureToggleDisabledEvent;

$eventDispatcher = new EventDispatcher();

$eventDispatcher->addSubscriber(new MyEventSubscriber());
$eventDispatcher->addListener(UnleashEvents::FEATURE_TOGGLE_DISABLED, function (FeatureToggleDisabledEvent $event) {
// todo
});


$unleash = UnleashBuilder::create()
->withEventDispatcher($eventDispatcher)
// add other unleash configurations
->build();
```

> Tip for PhpStorm users: Use the [Symfony plugin](https://plugins.jetbrains.com/plugin/7219-symfony-support)
> for help with autocompletion of events, afterwards it looks like this:
![Symfony plugin events autocompletion](symfony-plugin-events.gif)

Binary file added doc/symfony-plugin-events.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
parameters:
ignoreErrors:
- '#@throws with type .+ is not subtype of Throwable#'
- '#@throws with type .+ is not subtype of Throwable#'
treatPhpDocTypesAsCertain: false
17 changes: 17 additions & 0 deletions src/Configuration/UnleashConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Unleash\Client\ContextProvider\DefaultUnleashContextProvider;
use Unleash\Client\ContextProvider\SettableUnleashContextProvider;
use Unleash\Client\ContextProvider\UnleashContextProvider;
use Unleash\Client\Helper\EventDispatcher;

final class UnleashConfiguration
{
Expand All @@ -38,8 +39,11 @@ public function __construct(
// todo remove nullability in next major version
private ?BootstrapProvider $bootstrapProvider = null,
private bool $fetchingEnabled = true,
// todo remove nullability in next major version
private ?EventDispatcher $eventDispatcher = null,
) {
$this->contextProvider ??= new DefaultUnleashContextProvider();
$this->eventDispatcher ??= new EventDispatcher(null);
if ($defaultContext !== null) {
$this->setDefaultContext($defaultContext);
}
Expand Down Expand Up @@ -245,4 +249,17 @@ public function setFetchingEnabled(bool $fetchingEnabled): self

return $this;
}

public function getEventDispatcher(): EventDispatcher
{
return $this->eventDispatcher ?? new EventDispatcher(null);
}

public function setEventDispatcher(?EventDispatcher $eventDispatcher): self
{
$eventDispatcher ??= new EventDispatcher(null);
$this->eventDispatcher = $eventDispatcher;

return $this;
}
}
28 changes: 27 additions & 1 deletion src/DefaultUnleash.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
use Unleash\Client\Configuration\UnleashConfiguration;
use Unleash\Client\DTO\Strategy;
use Unleash\Client\DTO\Variant;
use Unleash\Client\Event\FeatureToggleDisabledEvent;
use Unleash\Client\Event\FeatureToggleMissingStrategyHandlerEvent;
use Unleash\Client\Event\FeatureToggleNotFoundEvent;
use Unleash\Client\Event\UnleashEvents;
use Unleash\Client\Metrics\MetricsHandler;
use Unleash\Client\Repository\UnleashRepository;
use Unleash\Client\Strategy\StrategyHandler;
Expand Down Expand Up @@ -36,10 +40,22 @@ public function isEnabled(string $featureName, ?Context $context = null, bool $d

$feature = $this->repository->findFeature($featureName);
if ($feature === null) {
$event = new FeatureToggleNotFoundEvent($context, $featureName);
$this->configuration->getEventDispatcher()->dispatch(
$event,
UnleashEvents::FEATURE_TOGGLE_NOT_FOUND,
);

return $default;
}

if (!$feature->isEnabled()) {
$event = new FeatureToggleDisabledEvent($feature, $context);
$this->configuration->getEventDispatcher()->dispatch(
$event,
UnleashEvents::FEATURE_TOGGLE_DISABLED,
);

$this->metricsHandler->handleMetrics($feature, false);

return false;
Expand All @@ -55,11 +71,13 @@ public function isEnabled(string $featureName, ?Context $context = null, bool $d
return true;
}

$handlersFound = false;
foreach ($strategies as $strategy) {
$handlers = $this->findStrategyHandlers($strategy);
if (!count($handlers)) {
continue;
}
$handlersFound = true;
foreach ($handlers as $handler) {
if ($handler->isEnabled($strategy, $context)) {
$this->metricsHandler->handleMetrics($feature, true);
Expand All @@ -69,6 +87,14 @@ public function isEnabled(string $featureName, ?Context $context = null, bool $d
}
}

if (!$handlersFound) {
$event = new FeatureToggleMissingStrategyHandlerEvent($context, $feature);
$this->configuration->getEventDispatcher()->dispatch(
$event,
UnleashEvents::FEATURE_TOGGLE_MISSING_STRATEGY_HANDLER,
);
}

$this->metricsHandler->handleMetrics($feature, false);

return false;
Expand All @@ -89,7 +115,7 @@ public function getVariant(string $featureName, ?Context $context = null, ?Varia
$this->metricsHandler->handleMetrics($feature, true, $variant);
}

return $variant ?? $fallbackVariant;
return $variant ?? $fallbackVariant;
}

public function register(): bool
Expand Down
Loading

0 comments on commit 6a225da

Please sign in to comment.