Skip to content

Commit

Permalink
feat: support for named clients and provider context at client level …
Browse files Browse the repository at this point in the history
…and more fixes
  • Loading branch information
tcarrio committed Apr 30, 2024
1 parent 0c3651d commit 657412e
Show file tree
Hide file tree
Showing 22 changed files with 228 additions and 207 deletions.
90 changes: 57 additions & 33 deletions src/OpenFeatureAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use League\Event\EventDispatcher;
use OpenFeature\implementation\events\Event;
use OpenFeature\implementation\flags\NoOpClient;
use OpenFeature\implementation\provider\NoOpProvider;
use OpenFeature\implementation\provider\ProviderAwareTrait;
use OpenFeature\interfaces\common\LoggerAwareTrait;
use OpenFeature\interfaces\common\Metadata;
use OpenFeature\interfaces\events\EventDetails;
Expand All @@ -17,6 +17,7 @@
use OpenFeature\interfaces\flags\EvaluationContext;
use OpenFeature\interfaces\hooks\Hook;
use OpenFeature\interfaces\provider\Provider;
use OpenFeature\interfaces\provider\ProviderAware;
use Psr\Log\LoggerAwareInterface;
use Throwable;

Expand All @@ -26,14 +27,23 @@
final class OpenFeatureAPI implements API, LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* -----------------
* Requirement 1.1.2
* -----------------
* The API MUST provide a function to set the global provider singleton, which
* accepts an API-conformant provider implementation.
*/
use ProviderAwareTrait;

private static ?OpenFeatureAPI $instance = null;

private Provider $provider;
private EventDispatcher $dispatcher;

/** @var Array<string,OpenFeatureClient> $clientMap */
private array $clientMap;
/** @var (Client & ProviderAware) | null $defaultClient */
private $defaultClient;
/** @var Array<string,Client & ProviderAware> $clientMap */
private array $clientMap = [];

/** @var Hook[] $hooks */
private array $hooks = [];
Expand Down Expand Up @@ -67,37 +77,23 @@ public static function getInstance(): API
*/
private function __construct()
{
$this->provider = new NoOpProvider();
$this->dispatcher = new EventDispatcher();
}

public function getProvider(): Provider
{
return $this->provider;
}

/**
* -----------------
* Requirement 1.1.2
* -----------------
* The API MUST provide a function to set the global provider singleton, which
* accepts an API-conformant provider implementation.
*/
public function setProvider(Provider $provider, string ...$clientNames): void
{
$this->provider = $provider;
}

/**
* -----------------
* Requirement 1.1.3
* -----------------
* The API MUST provide a function to bind a given provider to one or more client names.
* If the client-name already has a bound provider, it is overwritten with the new mapping.
*/
public function setClientProvider(string $clientName, Provider $provider): void
public function setClientProvider(string $clientDomain, Provider $provider): void
{
$this->clientMap[$clientName] = $provider;
if (!isset($this->clientMap[$clientDomain])) {
return;
}

$this->clientMap[$clientDomain]->setProvider($provider);
}

/**
Expand All @@ -117,25 +113,36 @@ public function getProviderMetadata(): Metadata
* Requirement 1.1.6
* -----------------
* The API MUST provide a function for creating a client which accepts the following options:
* name (optional): A logical string identifier for the client.
* domain (optional): A logical string identifier for the client.
*/
public function getClient(?string $name = null): Client
public function getClient(?string $domain = null): Client
{
try {
$name = $name ?? self::class;
$isDefaultClient = is_null($domain);
$domain = $domain ?? self::class;

if (key_exists($name, $this->clientMap)) {
return $this->clientMap[$name];
if ($isDefaultClient) {
$currentClient = $this->defaultClient ?? null;
} else {
$currentClient = key_exists($domain, $this->clientMap) ? $this->clientMap[$domain] : null;
}

if (!is_null($currentClient)) {
return $currentClient;
}

try {
$client = new OpenFeatureClient($this, $name);
$client = new OpenFeatureClient($this, $domain);
$client->setLogger($this->getLogger());
} catch (Throwable $err) {
$client = new NoOpClient();
}

$this->clientMap[$name] = $client;
if ($isDefaultClient) {
$this->defaultClient = $client;
} else {
$this->clientMap[$domain] = $client;
}

return $client;
} catch (Throwable $err) {
Expand Down Expand Up @@ -207,15 +214,32 @@ public function dispose(): void
*/
public function dispatch(ProviderEvent $providerEvent, EventDetails $eventDetails): void
{
$this->dispatcher->dispatch(new Event($providerEvent->getValue(), $eventDetails));
$this->dispatcher->dispatch(new Event($providerEvent->value, $eventDetails));
}

public function addHandler(ProviderEvent $providerEvent, callable $handler): void
{
$this->dispatcher->subscribeTo($providerEvent->getValue(), $handler);
$this->dispatcher->subscribeTo($providerEvent->value, $handler);
}

public function removeHandler(ProviderEvent $providerEvent, callable $handler): void
{
}

/**
* TESTING UTILITY
*/
protected function resetClients(): void
{
$this->defaultClient = null;
$this->clientMap = [];
}

/**
* TESTING UTILITY
*/
protected function resetProviders(): void
{
$this->provider = null;
}
}
29 changes: 15 additions & 14 deletions src/OpenFeatureClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use OpenFeature\implementation\hooks\HookContextFactory;
use OpenFeature\implementation\hooks\HookExecutor;
use OpenFeature\implementation\hooks\HookHints;
use OpenFeature\implementation\provider\ProviderAwareTrait;
use OpenFeature\implementation\provider\ResolutionError;
use OpenFeature\interfaces\common\LoggerAwareTrait;
use OpenFeature\interfaces\common\Metadata as MetadataInterface;
Expand All @@ -28,6 +29,7 @@
use OpenFeature\interfaces\hooks\HooksAwareTrait;
use OpenFeature\interfaces\provider\ErrorCode;
use OpenFeature\interfaces\provider\Provider;
use OpenFeature\interfaces\provider\ProviderAware;
use OpenFeature\interfaces\provider\Reason;
use OpenFeature\interfaces\provider\ResolutionDetails;
use OpenFeature\interfaces\provider\ThrowableWithResolutionError;
Expand All @@ -37,33 +39,27 @@
use function array_reverse;
use function sprintf;

class OpenFeatureClient implements Client, LoggerAwareInterface
class OpenFeatureClient implements Client, LoggerAwareInterface, ProviderAware
{
use HooksAwareTrait;
use LoggerAwareTrait;
use ProviderAwareTrait;

private ?EvaluationContextInterface $evaluationContext = null;

/**
* Client for evaluating the flag. There may be multiples of these floating around.
*
* @param API $api Backing global singleton
* @param string $name Name of the client (used by observability tools).
* @param string $version Version of the client (used by observability tools).
* @param string $domain Domain of the client (used by observability tools).
*/
public function __construct(
private readonly API $api,
private readonly string $name,
private readonly string $version,
private readonly string $domain,
) {
$this->hooks = [];
}

public function getVersion(): string
{
return $this->version;
}

/**
* Return an optional client-level evaluation context.
*/
Expand Down Expand Up @@ -104,14 +100,14 @@ public function addHooks(Hook ...$hooks): void
* Requirement 1.2.2
* -----------------
* The client interface MUST define a metadata member or accessor, containing
* an immutable name field or accessor of type string, which corresponds to
* the name value supplied during client creation.
* an immutable domain field or accessor of type string, which corresponds to
* the domain value supplied during client creation.
*
* Returns the metadata for the current resource
*/
public function getMetadata(): MetadataInterface
{
return new Metadata($this->name);
return new Metadata($this->domain);
}

/**
Expand Down Expand Up @@ -291,7 +287,7 @@ private function evaluateFlag(
?EvaluationOptionsInterface $options = null,
): EvaluationDetailsInterface {
$api = $this->api;
$provider = $api->getProvider();
$provider = $this->determineProvider();
/** @var EvaluationOptionsInterface $options */
$options = $options ?? new EvaluationOptions();
$hookHints = $options->getHookHints() ?? new HookHints();
Expand Down Expand Up @@ -420,4 +416,9 @@ private function createProviderEvaluation(
return $provider->resolveObjectValue($key, $defaultValue, $context);
}
}

private function determineProvider(): Provider
{
return $this->provider ?? $this->api->getProvider();
}
}
19 changes: 16 additions & 3 deletions src/implementation/common/Metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,30 @@

class Metadata implements MetadataInterface
{
public function __construct(private string $name)
public function __construct(private string $domain)
{
}

public function getDomain(): string
{
return $this->domain;
}

public function setDomain(string $domain): void
{
$this->domain = $domain;
}

/**
* @deprecated Use getDomain
*/
public function getName(): string
{
return $this->name;
return $this->domain;
}

public function setName(string $name): void
{
$this->name = $name;
$this->domain = $name;
}
}
8 changes: 2 additions & 6 deletions src/implementation/flags/FlagMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

namespace OpenFeature\implementation\flags;

use DateTime;
use OpenFeature\implementation\common\ArrayHelper;
use OpenFeature\interfaces\flags\FlagMetadata as FlagMetadataInterface;

class FlagMetadata implements FlagMetadataInterface
{
/**
* @param Array<array-key, bool|string|int|float|DateTime|mixed[]|null> $attributesMap
* @param Array<array-key, bool|string|int|float> $metadata
*/
public function __construct(protected array $metadata = [])
{
Expand All @@ -27,16 +26,13 @@ public function keys(): array
return ArrayHelper::getStringKeys($this->metadata);
}

/**
* @return bool|string|int|float|null
*/
public function get(string $key): bool | string | int | float | null
{
return $this->metadata[$key] ?? null;
}

/**
* @return Array<array-key, bool|string|int|float|DateTime|mixed[]|null>
* @return Array<array-key, bool|string|int|float>
*/
public function toArray(): array
{
Expand Down
6 changes: 5 additions & 1 deletion src/implementation/flags/NoOpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
namespace OpenFeature\implementation\flags;

use OpenFeature\implementation\common\Metadata;
use OpenFeature\implementation\provider\ProviderAwareTrait;
use OpenFeature\interfaces\flags\Client;
use OpenFeature\interfaces\flags\EvaluationContext as EvaluationContextInterface;
use OpenFeature\interfaces\flags\EvaluationDetails;
use OpenFeature\interfaces\flags\EvaluationOptions;
use OpenFeature\interfaces\provider\ProviderAware;

class NoOpClient implements Client
class NoOpClient implements Client, ProviderAware
{
use ProviderAwareTrait;

private const CLIENT_NAME = 'no-op-client';

public function getBooleanValue(string $flagKey, bool $defaultValue, ?EvaluationContextInterface $context = null, ?EvaluationOptions $options = null): ?bool
Expand Down
27 changes: 4 additions & 23 deletions src/implementation/provider/AbstractProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ abstract class AbstractProvider implements HooksAware, Provider

protected static string $NAME = 'AbstractProvider';

protected ?ProviderState $status;

/** @var Hook[] $hooks */
private array $hooks = [];
protected ProviderState $status = ProviderState::NOT_READY;

public function getMetadata(): MetadataInterface
{
Expand All @@ -49,35 +46,19 @@ abstract public function resolveObjectValue(string $flagKey, array $defaultValue
*/
public function dispose(): void
{
$this->status = ProviderState::NOT_READY();
$this->status = ProviderState::NOT_READY;
}

/**
* The default is that this is not required, and must be overridden by the implementing provider
*/
public function initialize(EvaluationContext $evaluationContext): void
{
$this->status = ProviderState::READY();
$this->status = ProviderState::READY;
}

public function getStatus(): ProviderState
{
return $this->status ?? ProviderState::NOT_READY();
}

/**
* @return Hook[]
*/
public function getHooks(): array
{
return $this->hooks;
}

/**
* @param Hook[] $hooks
*/
public function setHooks(array $hooks): void
{
$this->hooks = $hooks;
return $this->status;
}
}
Loading

0 comments on commit 657412e

Please sign in to comment.