Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
614d2a2
DTO traits for create and packout
typotter Apr 9, 2025
cf0292b
Config wire types and trait tests
typotter Apr 9, 2025
9562182
ignore tmp cache directory
typotter Apr 9, 2025
e7aaf09
Configuration Class
typotter Apr 9, 2025
4860aac
move UFCParser logic to Flag
typotter Apr 9, 2025
f2d7479
Config class tests
typotter Apr 9, 2025
3ca697c
refactor reloadIfExpired out of getFlag
typotter Apr 9, 2025
16e7e66
Tests for Configuration class
typotter Apr 9, 2025
ba24b15
DTO updates
typotter Apr 9, 2025
f187884
new config store
typotter Apr 9, 2025
42ffe2b
new config store
typotter Apr 9, 2025
f29a3a0
Tests for the new config store
typotter Apr 9, 2025
cdc93ad
rip out changes
typotter Apr 9, 2025
d2dc794
fixed more tests
typotter Apr 9, 2025
7de70f9
more tests
typotter Apr 10, 2025
ad85819
even more tests. feat complete possibly
typotter Apr 10, 2025
40ac12d
last test
typotter Apr 10, 2025
9969444
finish configuration store swap out
typotter Apr 10, 2025
d513987
rename create trait
typotter Apr 11, 2025
be8f682
polish
typotter Apr 11, 2025
898ccc1
polish
typotter Apr 14, 2025
bffb17b
fix timing for cache test.
typotter Apr 14, 2025
674c5ad
lint
typotter Apr 14, 2025
04b2bcc
don't evaluate bandit for default value
typotter Apr 15, 2025
bde87bb
log cache errors
typotter Apr 15, 2025
497e3f4
Merge branch 'main' into tp/opt-bandit-load
typotter Apr 15, 2025
86403db
feat: load bandits when needed and other changes
typotter Apr 15, 2025
503308a
chore: rename fromJson
typotter Apr 15, 2025
6c42945
comments
typotter Apr 16, 2025
e3c6e4e
iterate
typotter Apr 16, 2025
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
34 changes: 17 additions & 17 deletions src/Config/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@

use Eppo\DTO\Bandit\Bandit;
use Eppo\DTO\BanditParametersResponse;
use Eppo\DTO\BanditReference;
use Eppo\DTO\ConfigurationWire\ConfigResponse;
use Eppo\DTO\ConfigurationWire\ConfigurationWire;
use Eppo\DTO\Flag;
use Eppo\DTO\FlagConfigResponse;

class Configuration
{
private array $parsedFlags = [];
private readonly FlagConfigResponse $flags;
private readonly BanditParametersResponse $bandits;

Expand All @@ -23,8 +21,8 @@ private function __construct(
) {
$flagJson = json_decode($this->flagsConfig->response, true);
$banditsJson = json_decode($this->banditsConfig?->response ?? "", true);
$this->flags = FlagConfigResponse::fromJson($flagJson ?? []);
$this->bandits = BanditParametersResponse::fromJson($banditsJson ?? []);
$this->flags = FlagConfigResponse::fromArray($flagJson ?? []);
$this->bandits = BanditParametersResponse::fromArray($banditsJson ?? []);
}

public static function fromUfcResponses(ConfigResponse $flagsConfig, ?ConfigResponse $banditsConfig): Configuration
Expand All @@ -39,10 +37,9 @@ public static function fromConfigurationWire(ConfigurationWire $configurationWir

public static function fromFlags(array $flags, ?array $bandits = null)
{
$fcr = FlagConfigResponse::fromJson(["flags" => $flags]);
$flagsConfig = new ConfigResponse(response: json_encode($fcr));
$flagsConfig = new ConfigResponse(response: json_encode(["flags" => $flags]));
$banditsConfig = $bandits ? new ConfigResponse(
response: json_encode(BanditParametersResponse::fromJson(["bandits" => $bandits]))
response: json_encode(BanditParametersResponse::fromArray(["bandits" => $bandits]))
) : null;
return new self($flagsConfig, $banditsConfig);
}
Expand All @@ -54,27 +51,20 @@ public static function emptyConfig(): self

public function getFlag(string $key): ?Flag
{
if (!isset($this->parsedFlags[$key])) {
$flagObj = $this->flags->flags[$key] ?? null;
if ($flagObj !== null) {
$this->parsedFlags[$key] = Flag::fromJson($flagObj);
}
}
return $this->parsedFlags[$key] ?? null;
return $this->flags->flags[$key] ?? null;
}

public function getBandit(string $banditKey): ?Bandit
{
if (!isset($this->bandits->bandits[$banditKey])) {
return null;
}
return Bandit::fromJson($this->bandits?->bandits[$banditKey]) ?? null;
return Bandit::fromArray($this->bandits?->bandits[$banditKey]) ?? null;
}

public function getBanditByVariation(string $flagKey, string $variation): ?string
{
foreach ($this->flags->banditReferences as $banditKey => $banditReferenceObj) {
$banditReference = BanditReference::fromJson($banditReferenceObj);
foreach ($this->flags->banditReferences as $banditKey => $banditReference) {
foreach ($banditReference->flagVariations as $flagVariation) {
if ($flagVariation->flagKey === $flagKey && $flagVariation->variationKey === $variation) {
return $banditKey;
Expand All @@ -101,4 +91,14 @@ public function getFlagETag(): ?string
{
return $this->flagsConfig?->eTag ?? null;
}

public function getBanditModels(): array
{
$models = [];
foreach ($this->bandits->bandits as $key => $banditArr) {
$bandit = Bandit::fromArray($banditArr);
$models[$key] = $bandit->modelVersion;
}
return $models;
}
}
31 changes: 27 additions & 4 deletions src/Config/ConfigurationLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function reloadConfigurationIfExpired(): void
*/
public function fetchAndStoreConfiguration(?string $flagETag): void
{
$currentConfig = $this->configurationStore->getConfiguration();
$response = $this->apiRequestWrapper->getUFC($flagETag);
if ($response->isModified) {
$configResponse = new ConfigResponse(
Expand All @@ -48,12 +49,34 @@ public function fetchAndStoreConfiguration(?string $flagETag): void
syslog(LOG_WARNING, "[Eppo SDK] Empty or invalid response from the configuration server.");
return;
}
$fcr = FlagConfigResponse::fromJson($responseData);
$fcr = FlagConfigResponse::fromArray($responseData);
$banditResponse = null;
// TODO: Also check current bandit models loaded for optimized bandit loading.
if (count($fcr->banditReferences) > 0) {
$bandits = $this->apiRequestWrapper->getBandits();
$banditResponse = new ConfigResponse($bandits->body, date('c'), $bandits->eTag);
$canReuseBandits = true;
$currentBandits = $currentConfig->getBanditModels();

foreach ($fcr->banditReferences as $banditKey => $banditReference) {
if (
!array_key_exists(
$banditKey,
$currentBandits
) || $banditReference->modelVersion !== $currentBandits[$banditKey]
) {
$canReuseBandits = false;
break;
}
}

if ($canReuseBandits) {
$banditResponse = $currentConfig->toConfigurationWire()->bandits;
} else {
$banditResource = $this->apiRequestWrapper->getBandits();
if (!$banditResource?->body) {
syslog(E_ERROR, "[Eppo SDK] Empty or invalid bandit response from the configuration server.");
} else {
$banditResponse = new ConfigResponse($banditResource->body, date('c'), $banditResource->eTag);
}
}
}

$configuration = Configuration::fromUfcResponses($configResponse, $banditResponse);
Expand Down
20 changes: 10 additions & 10 deletions src/DTO/Bandit/ActionCoefficients.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,29 @@ public function __construct(
* @return array
* @throws InvalidArgumentException
*/
public static function arrayFromJson(array $coefficients): array
public static function parseArray(array $coefficients): array
{
$res = [];
foreach ($coefficients as $key => $coefficient) {
$res[$key] = ActionCoefficients::fromJson($coefficient);
$res[$key] = ActionCoefficients::fromArray($coefficient);
}
return $res;
}

/**
* @param array $json
* @param array $arr
* @return ActionCoefficients
* @throws InvalidArgumentException
*/
public static function fromJson(array $json): ActionCoefficients
public static function fromArray(array $arr): ActionCoefficients
{
return new ActionCoefficients(
$json['actionKey'],
$json['intercept'],
NumericAttributeCoefficient::arrayFromJson($json['subjectNumericCoefficients']),
CategoricalAttributeCoefficient::arrayFromJson($json['subjectCategoricalCoefficients']),
NumericAttributeCoefficient::arrayFromJson($json['actionNumericCoefficients']),
CategoricalAttributeCoefficient::arrayFromJson($json['actionCategoricalCoefficients'])
$arr['actionKey'],
$arr['intercept'],
NumericAttributeCoefficient::parseArray($arr['subjectNumericCoefficients']),
CategoricalAttributeCoefficient::parseArray($arr['subjectCategoricalCoefficients']),
NumericAttributeCoefficient::parseArray($arr['actionNumericCoefficients']),
CategoricalAttributeCoefficient::parseArray($arr['actionCategoricalCoefficients'])
);
}
}
22 changes: 11 additions & 11 deletions src/DTO/Bandit/Bandit.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,32 @@ public function __construct(
}

/**
* @param array $json
* @param array $arr
* @return Bandit
*/
public static function fromJson(array $json): Bandit
public static function fromArray(array $arr): Bandit
{
try {
if (!isset($json['updatedAt'])) {
if (!isset($arr['updatedAt'])) {
$updatedAt = new DateTime();
} elseif (is_array($json['updatedAt'])) {// serialized datetime
$updatedAt = new DateTime($json['updatedAt']['date']);
} elseif (is_array($arr['updatedAt'])) {// serialized datetime
$updatedAt = new DateTime($arr['updatedAt']['date']);
} else {
$updatedAt = new DateTime($json['updatedAt']);
$updatedAt = new DateTime($arr['updatedAt']);
}
} catch (\Exception $e) {
syslog(
LOG_WARNING,
"[Eppo SDK] invalid timestamp for bandit model ${json['updatedAt']}: " . $e->getMessage()
"[Eppo SDK] invalid timestamp for bandit model ${arr['updatedAt']}: " . $e->getMessage()
);
$updatedAt = new DateTime();
} finally {
return new Bandit(
$json['banditKey'],
$json['modelName'],
$arr['banditKey'],
$arr['modelName'],
$updatedAt,
$json['modelVersion'],
BanditModelData::fromJson($json['modelData'])
$arr['modelVersion'],
BanditModelData::fromArray($arr['modelData'])
);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/DTO/Bandit/BanditModelData.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ public function __construct(
) {
}

public static function fromJson($json): BanditModelData
public static function fromArray($arr): BanditModelData
{
return new BanditModelData(
$json['gamma'],
ActionCoefficients::arrayFromJson($json['coefficients']),
$json['defaultActionScore'],
$json['actionProbabilityFloor']
$arr['gamma'],
ActionCoefficients::parseArray($arr['coefficients']),
$arr['defaultActionScore'],
$arr['actionProbabilityFloor']
);
}
}
4 changes: 2 additions & 2 deletions src/DTO/Bandit/BanditResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public function __toString(): string
return $this->action ?? $this->variation;
}

public static function fromJson($json): self
public static function fromArray($arr): self
{
return new self($json['Variation'], $json['Action'] ?? null);
return new self($arr['Variation'], $arr['Action'] ?? null);
}
}
10 changes: 5 additions & 5 deletions src/DTO/Bandit/CategoricalAttributeCoefficient.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ public function __construct(
}

/**
* @param array $json
* @param array $arr
* @return CategoricalAttributeCoefficient
*/
public static function fromJson(array $json): CategoricalAttributeCoefficient
public static function fromArray(array $arr): CategoricalAttributeCoefficient
{
return new self($json['attributeKey'], $json['missingValueCoefficient'], $json['valueCoefficients']);
return new self($arr['attributeKey'], $arr['missingValueCoefficient'], $arr['valueCoefficients']);
}

/**
* @param array $categoricalCoefficients
* @return CategoricalAttributeCoefficient[]
*/
public static function arrayFromJson(array $categoricalCoefficients): array
public static function parseArray(array $categoricalCoefficients): array
{
return array_map(fn($item) => self::fromJson($item), $categoricalCoefficients);
return array_map(fn($item) => self::fromArray($item), $categoricalCoefficients);
}
}
10 changes: 5 additions & 5 deletions src/DTO/Bandit/NumericAttributeCoefficient.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,20 @@ public function __construct(
}

/**
* @param array $json
* @param array $arr
* @return NumericAttributeCoefficient
*/
public static function fromJson(array $json): NumericAttributeCoefficient
public static function fromArray(array $arr): NumericAttributeCoefficient
{
return new self($json['attributeKey'], $json['coefficient'], $json['missingValueCoefficient']);
return new self($arr['attributeKey'], $arr['coefficient'], $arr['missingValueCoefficient']);
}

/**
* @param array $numericCoefficients
* @return NumericAttributeCoefficient[]
*/
public static function arrayFromJson(array $numericCoefficients): array
public static function parseArray(array $numericCoefficients): array
{
return array_map(fn($item) => self::fromJson($item), $numericCoefficients);
return array_map(fn($item) => self::fromArray($item), $numericCoefficients);
}
}
12 changes: 6 additions & 6 deletions src/DTO/BanditFlagVariation.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public function __construct(
) {
}

public static function fromJson($json): BanditFlagVariation
public static function fromArray($arr): BanditFlagVariation
{
return new self(
$json['key'],
$json['flagKey'],
$json['allocationKey'],
$json['variationKey'],
$json['variationValue']
$arr['key'],
$arr['flagKey'],
$arr['allocationKey'],
$arr['variationKey'],
$arr['variationValue']
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/DTO/BanditParametersResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace Eppo\DTO;

use Eppo\Traits\StaticFromJson;
use Eppo\Traits\StaticFromArray;
use Eppo\Traits\ToArray;

class BanditParametersResponse
{
use StaticFromJson;
use StaticFromArray;
use ToArray;

public array $bandits;
Expand Down
10 changes: 5 additions & 5 deletions src/DTO/BanditReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ public function __construct(
) {
}

public static function fromJson(mixed $json): BanditReference
public static function fromArray(mixed $arr): BanditReference
{
$flagVariations = [];
if (isset($json['flagVariations']) && is_array($json['flagVariations'])) {
foreach ($json['flagVariations'] as $variation) {
$flagVariations[] = BanditFlagVariation::fromJson($variation);
if (isset($arr['flagVariations']) && is_array($arr['flagVariations'])) {
foreach ($arr['flagVariations'] as $variation) {
$flagVariations[] = BanditFlagVariation::fromArray($variation);
}
}

return new BanditReference(
modelVersion: $json['modelVersion'],
modelVersion: $arr['modelVersion'],
flagVariations: $flagVariations
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/DTO/ConfigurationWire/ConfigResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Eppo\DTO\ConfigurationWire;

use Eppo\Traits\StaticFromJson;
use Eppo\Traits\StaticFromArray;
use Eppo\Traits\ToArray;

class ConfigResponse
{
use ToArray;
use StaticFromJson;
use StaticFromArray;

public function __construct(
public string $response = "",
Expand Down
8 changes: 4 additions & 4 deletions src/DTO/ConfigurationWire/ConfigurationWire.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public static function fromArray(array $arr): self
$dto = new self();
$dto->version = $arr['version'] ?? 1;
if (isset($arr['config'])) {
$dto->config = ConfigResponse::fromJson($arr['config']);
$dto->config = ConfigResponse::fromArray($arr['config']);
}
if (isset($arr['bandits'])) {
$dto->bandits = ConfigResponse::fromJson($arr['bandits']);
$dto->bandits = ConfigResponse::fromArray($arr['bandits']);
}
return $dto;
}
Expand All @@ -46,9 +46,9 @@ public static function fromResponses(ConfigResponse $flags, ?ConfigResponse $ban
return $dto;
}

public static function fromJsonString(string $jsonEncodedString): self
public static function fromJson(string $jsonEncodedObject): self
{
return ConfigurationWire::fromArray(json_decode($jsonEncodedString, associative: true));
return ConfigurationWire::fromArray(json_decode($jsonEncodedObject, associative: true));
}

public function toJsonString(): string
Expand Down
Loading