-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
37 changed files
with
1,639 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaunchDarkly; | ||
|
||
/** | ||
* A status enum which represents the result of a Big Segment query involved in | ||
* a flag evaluation. | ||
*/ | ||
enum BigSegmentEvaluationStatus: string | ||
{ | ||
/** | ||
* Indicates that the Big Segment query involved in the flag evaluation was | ||
* successful, and that the segment state is considered up to date. | ||
*/ | ||
case HEALTHY = 'HEALTHY'; | ||
|
||
/** | ||
* Indicates that the Big Segment query involved in the flag evaluation was | ||
* successful, but that the segment state may not be up to date. | ||
*/ | ||
case STALE = 'STALE'; | ||
|
||
/** | ||
* Indicates that Big Segments could not be queried for the flag evaluation | ||
* because the SDK configuration did not include a Big Segment store. | ||
*/ | ||
case NOT_CONFIGURED = 'NOT_CONFIGURED'; | ||
|
||
/** | ||
* Indicates that the Big Segment query involved in the flag evaluation | ||
* failed, for instance due to a database error. | ||
*/ | ||
case STORE_ERROR = 'STORE_ERROR'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaunchDarkly\Impl\BigSegments; | ||
|
||
use LaunchDarkly\BigSegmentEvaluationStatus; | ||
|
||
class MembershipResult | ||
{ | ||
/** | ||
* @param array<array<string, bool>>|null $membership | ||
*/ | ||
public function __construct( | ||
public readonly ?array $membership, | ||
public readonly BigSegmentEvaluationStatus $status | ||
) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaunchDarkly\Impl\BigSegments; | ||
|
||
use DateTimeImmutable; | ||
use Exception; | ||
use LaunchDarkly\BigSegmentEvaluationStatus; | ||
use LaunchDarkly\Impl; | ||
use LaunchDarkly\Subsystems; | ||
use LaunchDarkly\Types; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class StoreManager | ||
{ | ||
private Types\BigSegmentConfig $config; | ||
private ?Subsystems\BigSegmentStore $store; | ||
private Impl\BigSegments\StoreStatusProvider $statusProvider; | ||
private ?Types\BigSegmentStoreStatus $lastStatus; | ||
private ?DateTimeImmutable $lastStatusPollTime; | ||
|
||
public function __construct(Types\BigSegmentConfig $config, private readonly LoggerInterface $logger) | ||
{ | ||
$this->config = $config; | ||
$this->store = $config->store; | ||
$this->statusProvider = new Impl\BigSegments\StoreStatusProvider( | ||
fn () => $this->pollAndUpdateStatus(), | ||
$logger | ||
); | ||
$this->lastStatus = null; | ||
$this->lastStatusPollTime = null; | ||
} | ||
|
||
public function getStatusProvider(): Subsystems\BigSegmentStatusProvider | ||
{ | ||
return $this->statusProvider; | ||
} | ||
|
||
public function getContextMembership(string $contextKey): ?Impl\BigSegments\MembershipResult | ||
{ | ||
if ($this->store === null) { | ||
return null; | ||
} | ||
|
||
$cachedItem = $this->config->cache?->getItem($contextKey); | ||
/** @var ?array */ | ||
$membership = $cachedItem?->get(); | ||
|
||
if ($membership === null) { | ||
try { | ||
$membership = $this->store->getMembership(StoreManager::hashForContextKey($contextKey)); | ||
if ($this->config->cache !== null) { | ||
$cachedItem = $this->config | ||
->cache | ||
->getItem($contextKey) | ||
->set($membership) | ||
/** @psalm-suppress PossiblyNullArgument */ | ||
->expiresAfter($this->config->contextCacheTime); | ||
Check failure on line 59 in src/LaunchDarkly/Impl/BigSegments/StoreManager.php GitHub Actions / linux-build (8.1, true)PossiblyNullArgument
Check failure on line 59 in src/LaunchDarkly/Impl/BigSegments/StoreManager.php GitHub Actions / linux-build (8.1, false)PossiblyNullArgument
Check failure on line 59 in src/LaunchDarkly/Impl/BigSegments/StoreManager.php GitHub Actions / linux-build (8.2, true)PossiblyNullArgument
Check failure on line 59 in src/LaunchDarkly/Impl/BigSegments/StoreManager.php GitHub Actions / linux-build (8.2, false)PossiblyNullArgument
|
||
|
||
if (!$this->config->cache->save($cachedItem)) { | ||
$this->logger->warning("Failed to save Big Segment membership to cache", ['contextKey' => $contextKey]); | ||
} | ||
} | ||
} catch (Exception $e) { | ||
$this->logger->warning("Failed to retrieve Big Segment membership", ['contextKey' => $contextKey, 'exception' => $e->getMessage()]); | ||
return new Impl\BigSegments\MembershipResult(null, BigSegmentEvaluationStatus::STORE_ERROR); | ||
} | ||
} | ||
|
||
$nextPollingTime = ($this->lastStatusPollTime?->getTimestamp() ?? 0) + $this->config->statusPollInterval; | ||
|
||
$status = $this->lastStatus; | ||
if ($this->lastStatusPollTime === null || $nextPollingTime < time()) { | ||
$status = $this->pollAndUpdateStatus(); | ||
} | ||
|
||
if ($status === null || !$status->isAvailable()) { | ||
return new Impl\BigSegments\MembershipResult($membership, BigSegmentEvaluationStatus::STORE_ERROR); | ||
} | ||
|
||
return new Impl\BigSegments\MembershipResult($membership, $status->isStale() ? BigSegmentEvaluationStatus::STALE : BigSegmentEvaluationStatus::HEALTHY); | ||
} | ||
|
||
private function pollAndUpdateStatus(): Types\BigSegmentStoreStatus | ||
{ | ||
$newStatus = new Types\BigSegmentStoreStatus(false, false); | ||
if ($this->store !== null) { | ||
try { | ||
$metadata = $this->store->getMetadata(); | ||
$newStatus = new Types\BigSegmentStoreStatus( | ||
available: true, | ||
stale: $metadata->isStale($this->config->staleAfter) | ||
); | ||
} catch (Exception $e) { | ||
$this->logger->warning("Failed to retrieve Big Segment metadata", ['exception' => $e->getMessage()]); | ||
} | ||
} | ||
|
||
$this->lastStatus = $newStatus; | ||
$this->statusProvider->updateStatus($newStatus); | ||
$this->lastStatusPollTime = new DateTimeImmutable(); | ||
|
||
return $newStatus; | ||
} | ||
|
||
private static function hashForContextKey(string $contextKey): string | ||
{ | ||
return base64_encode(hash('sha256', $contextKey, true)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace LaunchDarkly\Impl\BigSegments; | ||
|
||
use Exception; | ||
use LaunchDarkly\Subsystems; | ||
use LaunchDarkly\Types; | ||
use Psr\Log\LoggerInterface; | ||
use SplObjectStorage; | ||
|
||
class StoreStatusProvider implements Subsystems\BigSegmentStatusProvider | ||
{ | ||
private SplObjectStorage $listeners; | ||
/** | ||
* @var callable(): Types\BigSegmentStoreStatus | ||
*/ | ||
private $statusFn; | ||
private ?Types\BigSegmentStoreStatus $lastStatus; | ||
private LoggerInterface $logger; | ||
|
||
/** | ||
* @param callable(): Types\BigSegmentStoreStatus $statusFn | ||
*/ | ||
public function __construct(callable $statusFn, LoggerInterface $logger) | ||
{ | ||
$this->listeners = new SplObjectStorage(); | ||
$this->statusFn = $statusFn; | ||
$this->lastStatus = null; | ||
$this->logger = $logger; | ||
} | ||
|
||
public function attach(Subsystems\BigSegmentStatusListener $listener): void | ||
{ | ||
$this->listeners->attach($listener); | ||
} | ||
|
||
public function detach(Subsystems\BigSegmentStatusListener $listener): void | ||
{ | ||
$this->listeners->detach($listener); | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
public function updateStatus(Types\BigSegmentStoreStatus $status): void | ||
{ | ||
if ($this->lastStatus != $status) { | ||
$old = $this->lastStatus; | ||
$this->lastStatus = $status; | ||
|
||
$this->notify(old: $old, new: $status); | ||
} | ||
} | ||
|
||
private function notify(?Types\BigSegmentStoreStatus $old, Types\BigSegmentStoreStatus $new): void | ||
{ | ||
/** @var Subsystems\BigSegmentStatusListener $listener */ | ||
foreach ($this->listeners as $listener) { | ||
try { | ||
$listener->statusChanged($old, $new); | ||
} catch (Exception $e) { | ||
$this->logger->warning('A big segment status listener threw an exception', ['exception' => $e->getMessage()]); | ||
} | ||
} | ||
} | ||
|
||
public function lastStatus(): ?Types\BigSegmentStoreStatus | ||
{ | ||
return $this->lastStatus; | ||
} | ||
|
||
public function status(): Types\BigSegmentStoreStatus | ||
{ | ||
return ($this->statusFn)(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.