From a99970fa62eb326f8d6701b46b881d95b7523155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ku=C5=BAnik?= Date: Thu, 21 Aug 2025 09:18:07 +0200 Subject: [PATCH 1/5] refactor: Add named constructors for options array handling --- featurevisor | 2 +- src/DatafileReader.php | 64 ++++++++++++++++--- src/{Instance.php => Featurevisor.php} | 86 +++++++++++++++----------- src/HooksManager.php | 51 +++++++++++++-- src/functions.php | 4 +- tests/ConditionsTest.php | 10 +-- tests/DatafileReaderTest.php | 4 +- tests/EventsTest.php | 12 ++-- 8 files changed, 163 insertions(+), 70 deletions(-) rename src/{Instance.php => Featurevisor.php} (87%) diff --git a/featurevisor b/featurevisor index ee0a555..0c491a6 100755 --- a/featurevisor +++ b/featurevisor @@ -255,7 +255,7 @@ function testSegment(array $assertion, array $segment, string $level): array { 'segments' => [] ]; - $datafileReader = new DatafileReader([ + $datafileReader = DatafileReader::createFromOptions([ 'datafile' => $datafile, 'logger' => createLogger([ 'level' => $level, diff --git a/src/DatafileReader.php b/src/DatafileReader.php index efbb01f..8332e45 100644 --- a/src/DatafileReader.php +++ b/src/DatafileReader.php @@ -3,9 +3,17 @@ namespace Featurevisor; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; class DatafileReader { + private const EMPTY_CONTENT = [ + 'schemaVersion' => '2', + 'revision' => 'unknown', + 'segments' => [], + 'features' => [] + ]; + private string $schemaVersion; private string $revision; private array $segments; @@ -13,15 +21,57 @@ class DatafileReader private LoggerInterface $logger; private array $regexCache; - public function __construct(array $options) + public static function createEmpty(LoggerInterface $logger): self + { + return self::createFromOptions([ + 'datafile' => self::EMPTY_CONTENT, + 'logger' => $logger, + ]); + } + + public static function createFromMixed($datafile, LoggerInterface $logger): self + { + return is_string($datafile) + ? self::createFromJson($datafile, $logger) + : self::createFromOptions([ + 'datafile' => $datafile, + 'logger' => $logger, + ]); + } + + /** + * @throws \JsonException + */ + public static function createFromJson(string $json, LoggerInterface $logger): self + { + $decodedDatafile = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + + return self::createFromOptions([ + 'datafile' => $decodedDatafile, + 'logger' => $logger + ]); + } + + public static function createFromOptions(array $data): self + { + if (array_key_exists('datafile', $data) === false ) { + throw new \InvalidArgumentException('Missing datafile key in data array'); + } + + return new self( + $data['datafile'], + $data['logger'] ?? null + ); + } + + public function __construct(array $datafileContent, ?LoggerInterface $logger = null) { - $datafile = $options['datafile']; - $this->logger = $options['logger']; + $this->logger = $logger ?? new NullLogger(); - $this->schemaVersion = $datafile['schemaVersion']; - $this->revision = $datafile['revision']; - $this->segments = $datafile['segments']; - $this->features = $datafile['features']; + $this->schemaVersion = $datafileContent['schemaVersion']; + $this->revision = $datafileContent['revision']; + $this->segments = $datafileContent['segments']; + $this->features = $datafileContent['features']; $this->regexCache = []; } diff --git a/src/Instance.php b/src/Featurevisor.php similarity index 87% rename from src/Instance.php rename to src/Featurevisor.php index 7f00971..a6f0aac 100644 --- a/src/Instance.php +++ b/src/Featurevisor.php @@ -5,47 +5,62 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -class Instance +class Featurevisor { - private array $context = []; + private array $context; private LoggerInterface $logger; - private ?array $sticky = null; + private ?array $sticky; private DatafileReader $datafileReader; private HooksManager $hooksManager; private Emitter $emitter; - public function __construct(array $options = []) + /** + * @param array{ + * datafile?: string|array, + * logger?: LoggerInterface, + * context?: array, + * sticky?: array, + * hooks?: array + * } $options + * @return self + */ + public static function createInstance(array $options): self { - // from options - $this->context = $options['context'] ?? []; - $this->logger = $options['logger'] ?? new NullLogger(); - $this->hooksManager = new HooksManager([ - 'hooks' => $options['hooks'] ?? [], - 'logger' => $this->logger - ]); - $this->emitter = new Emitter(); - $this->sticky = $options['sticky'] ?? null; - - // datafile - $emptyDatafile = [ - 'schemaVersion' => '2', - 'revision' => 'unknown', - 'segments' => [], - 'features' => [] - ]; - - $this->datafileReader = new DatafileReader([ - 'datafile' => $emptyDatafile, - 'logger' => $this->logger - ]); + $logger = $options['logger'] ?? new NullLogger(); + + return new self( + isset($options['datafile']) + ? DatafileReader::createFromMixed($options['datafile'], $logger) + : DatafileReader::createEmpty($logger), + $logger, + HooksManager::createFromOptions($options), + new Emitter(), + $options['context'] ?? [], + $options['sticky'] ?? null + ); + } - if (isset($options['datafile'])) { - $datafile = is_string($options['datafile']) ? json_decode($options['datafile'], true) : $options['datafile']; - $this->datafileReader = new DatafileReader([ - 'datafile' => $datafile, - 'logger' => $this->logger - ]); - } + public function __construct( + DatafileReader $datafile, + LoggerInterface $logger, + HooksManager $hooksManager, + Emitter $emitter, + array $context = [], + ?array $sticky = null + ) + { + $this->datafileReader = $datafile; + $this->logger = $logger; + $this->hooksManager = $hooksManager; + $this->sticky = $sticky; + $this->emitter = $emitter; + $this->context = $context; $this->logger->info('Featurevisor SDK initialized'); } @@ -53,10 +68,7 @@ public function __construct(array $options = []) public function setDatafile($datafile): void { try { - $newDatafileReader = new DatafileReader([ - 'datafile' => is_string($datafile) ? json_decode($datafile, true) : $datafile, - 'logger' => $this->logger - ]); + $newDatafileReader = DatafileReader::createFromMixed($datafile, $this->logger); $details = Events::getParamsForDatafileSetEvent($this->datafileReader, $newDatafileReader); diff --git a/src/HooksManager.php b/src/HooksManager.php index 6498c5f..1339f96 100644 --- a/src/HooksManager.php +++ b/src/HooksManager.php @@ -2,24 +2,63 @@ namespace Featurevisor; +use Closure; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; class HooksManager { private array $hooks = []; private LoggerInterface $logger; - public function __construct(array $options) + /** + * @param array{ + * hooks?: array, + * logger?: LoggerInterface, + * } $options + * @return self + */ + public static function createFromOptions(array $options): self { - $this->logger = $options['logger']; + return new self( + $options['hooks'] ?? [], + $options['logger'] ?? new NullLogger() + ); + } - if (isset($options['hooks'])) { - foreach ($options['hooks'] as $hook) { - $this->add($hook); - } + /** + * @param array $hooks + */ + public function __construct(array $hooks, LoggerInterface $logger) + { + $this->logger = $logger; + foreach ($hooks as $hook) { + $this->add($hook); } } + /** + * @param array{ + * name: string, + * before: Closure, + * after: Closure, + * bucketKey: Closure, + * bucketValue: Closure + * } $hook + * @return callable|null + */ public function add(array $hook): ?callable { foreach ($this->hooks as $existingHook) { diff --git a/src/functions.php b/src/functions.php index cf0fb45..a3374e4 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,9 +2,9 @@ namespace Featurevisor; -function createInstance(array $options = []): Instance +function createInstance(array $options = []): Featurevisor { - return new Instance($options); + return Featurevisor::createInstance($options); } function createLogger(array $options = []): Logger diff --git a/tests/ConditionsTest.php b/tests/ConditionsTest.php index fc94bd9..65cf851 100644 --- a/tests/ConditionsTest.php +++ b/tests/ConditionsTest.php @@ -13,15 +13,7 @@ class ConditionsTest extends TestCase { protected function setUp(): void { $logger = createLogger(); - $this->datafileReader = new DatafileReader([ - 'datafile' => [ - 'schemaVersion' => '2.0', - 'revision' => '1', - 'segments' => [], - 'features' => [], - ], - 'logger' => $logger, - ]); + $this->datafileReader = DatafileReader::createEmpty($logger); } public function testMatchAllViaStar() { diff --git a/tests/DatafileReaderTest.php b/tests/DatafileReaderTest.php index 21a59ea..94d45cf 100644 --- a/tests/DatafileReaderTest.php +++ b/tests/DatafileReaderTest.php @@ -50,7 +50,7 @@ public function testV2DatafileSchemaEntities() { ], ]; $logger = createLogger(); - $reader = new DatafileReader([ + $reader = DatafileReader::createFromOptions([ 'datafile' => $datafileJson, 'logger' => $logger, ]); @@ -113,7 +113,7 @@ public function testSegmentsMatching() { ], ]; $logger = createLogger(); - $datafileReader = new DatafileReader([ + $datafileReader = DatafileReader::createFromOptions([ 'datafile' => $datafileContent, 'logger' => $logger, ]); diff --git a/tests/EventsTest.php b/tests/EventsTest.php index ff695eb..16f9ac5 100644 --- a/tests/EventsTest.php +++ b/tests/EventsTest.php @@ -53,7 +53,7 @@ public function testGetParamsForDatafileSetEventEmptyToNew() 'level' => 'error', ]); - $previousDatafileReader = new DatafileReader([ + $previousDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '1', @@ -63,7 +63,7 @@ public function testGetParamsForDatafileSetEventEmptyToNew() 'logger' => $logger, ]); - $newDatafileReader = new DatafileReader([ + $newDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '2', @@ -92,7 +92,7 @@ public function testGetParamsForDatafileSetEventChangeHashAddition() 'level' => 'error', ]); - $previousDatafileReader = new DatafileReader([ + $previousDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '1', @@ -105,7 +105,7 @@ public function testGetParamsForDatafileSetEventChangeHashAddition() 'logger' => $logger, ]); - $newDatafileReader = new DatafileReader([ + $newDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '2', @@ -135,7 +135,7 @@ public function testGetParamsForDatafileSetEventChangeHashRemoval() 'level' => 'error', ]); - $previousDatafileReader = new DatafileReader([ + $previousDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '1', @@ -148,7 +148,7 @@ public function testGetParamsForDatafileSetEventChangeHashRemoval() 'logger' => $logger, ]); - $newDatafileReader = new DatafileReader([ + $newDatafileReader = DatafileReader::createFromOptions([ 'datafile' => [ 'schemaVersion' => '1.0.0', 'revision' => '2', From d5989821e05f5ba62c83edd69112a4416e3f4706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ku=C5=BAnik?= Date: Thu, 21 Aug 2025 09:21:06 +0200 Subject: [PATCH 2/5] refactor: Remove unnecessary createInstance function --- README.md | 26 ++++++------ featurevisor | 10 ++--- src/functions.php | 5 --- tests/ChildTest.php | 5 +-- ...{InstanceTest.php => FeaturevisorTest.php} | 41 +++++++++---------- 5 files changed, 40 insertions(+), 47 deletions(-) rename tests/{InstanceTest.php => FeaturevisorTest.php} (98%) diff --git a/README.md b/README.md index a00d40d..e614df6 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,14 @@ The SDK can be initialized by passing [datafile](https://featurevisor.com/docs/b ```php $datafileContent ]); ``` @@ -107,9 +107,9 @@ Context can be passed to SDK instance in various different ways, depending on yo You can set context at the time of initialization: ```php -use function Featurevisor\createInstance; +use Featurevisor\Featurevisor; -$f = createInstance([ +$f = Featurevisor::createInstance([ "context" => [ "deviceId" => "123", "country" => "nl", @@ -278,9 +278,9 @@ For the lifecycle of the SDK instance in your application, you can set some feat ### Initialize with sticky ```php -use function Featurevisor\createInstance; +use Featurevisor\Featurevisor; -$f = createInstance([ +$f = Featurevisor::createInstance([ "sticky" => [ "myFeatureKey" => [ "enabled" => true, @@ -363,10 +363,10 @@ If you choose `debug` level to make the logs more verbose, you can set it at the Setting `debug` level will print out all logs, including `info`, `warning`, and `error` levels. ```php -use function Featurevisor\createInstance; +use Featurevisor\Featurevisor; use function Featurevisor\createLogger; -$f = createInstance([ +$f = Featurevisor::createInstance([ "logger" => createLogger([ "level" => "debug", ]), @@ -376,7 +376,7 @@ $f = createInstance([ Alternatively, you can also set `logLevel` directly: ```php -$f = createInstance([ +$f = Featurevisor::createInstance([ "logLevel" => "debug", ]); ``` @@ -392,10 +392,10 @@ $f->setLogLevel("debug"); You can also pass your own log handler, if you do not wish to print the logs to the console: ```php -use function Featurevisor\createInstance; +use Featurevisor\Featurevisor; use function Featurevisor\createLogger; -$f = createInstance([ +$f = Featurevisor::createInstance([ "logger" => createLogger([ "level" => "info", "handler" => function ($level, $message, $details) { @@ -566,9 +566,9 @@ $myCustomHook = [ You can register hooks at the time of SDK initialization: ```php -use function Featurevisor\createInstance; +use Featurevisor\Featurevisor; -$f = createInstance([ +$f = Featurevisor::createInstance([ 'hooks' => [ $myCustomHook ], diff --git a/featurevisor b/featurevisor index 0c491a6..e3966fb 100755 --- a/featurevisor +++ b/featurevisor @@ -4,8 +4,8 @@ require __DIR__ . '/vendor/autoload.php'; use Featurevisor\DatafileReader; +use Featurevisor\Featurevisor; use Psr\Log\LogLevel; -use function Featurevisor\createInstance; use function Featurevisor\createLogger; /** @@ -304,7 +304,7 @@ function test(array $cliOptions) { $sdkInstancesByEnvironment = []; foreach ($environments as $environment) { $datafile = $datafilesByEnvironment[$environment]; - $sdkInstancesByEnvironment[$environment] = createInstance([ + $sdkInstancesByEnvironment[$environment] = Featurevisor::createInstance([ 'datafile' => $datafile, 'logger' => createLogger([ 'level' => $level, @@ -343,7 +343,7 @@ function test(array $cliOptions) { // If "at" parameter is provided, create a new SDK instance with the specific hook if (isset($assertion["at"])) { $datafile = $datafilesByEnvironment[$environment]; - $f = createInstance([ + $f = Featurevisor::createInstance([ 'datafile' => $datafile, 'logger' => createLogger([ 'level' => $level, @@ -420,7 +420,7 @@ function benchmark(array $cliOptions) { $level = getLoggerLevel($cliOptions); $datafilesByEnvironment = buildDatafiles($featurevisorProjectPath, [$cliOptions['environment']]); - $f = createInstance([ + $f = Featurevisor::createInstance([ 'datafile' => $datafilesByEnvironment[$cliOptions['environment']], 'logger' => createLogger([ 'level' => $level, @@ -481,7 +481,7 @@ function assessDistribution(array $cliOptions) { $datafilesByEnvironment = buildDatafiles($featurevisorProjectPath, [$cliOptions['environment']]); $level = getLoggerLevel($cliOptions); - $f = createInstance([ + $f = Featurevisor::createInstance([ 'datafile' => $datafilesByEnvironment[$cliOptions['environment']], 'logger' => createLogger([ 'level' => $level, diff --git a/src/functions.php b/src/functions.php index a3374e4..4c48667 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,11 +2,6 @@ namespace Featurevisor; -function createInstance(array $options = []): Featurevisor -{ - return Featurevisor::createInstance($options); -} - function createLogger(array $options = []): Logger { return new Logger($options); diff --git a/tests/ChildTest.php b/tests/ChildTest.php index 8aeb75f..1d75715 100644 --- a/tests/ChildTest.php +++ b/tests/ChildTest.php @@ -2,13 +2,12 @@ namespace Featurevisor\Tests; +use Featurevisor\Featurevisor; use PHPUnit\Framework\TestCase; -use function Featurevisor\createInstance; - class ChildTest extends TestCase { public function testCreateChildInstanceAndAllBehaviors() { - $f = createInstance([ + $f = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', diff --git a/tests/InstanceTest.php b/tests/FeaturevisorTest.php similarity index 98% rename from tests/InstanceTest.php rename to tests/FeaturevisorTest.php index c32ff62..67851e3 100644 --- a/tests/InstanceTest.php +++ b/tests/FeaturevisorTest.php @@ -2,17 +2,16 @@ namespace Featurevisor\Tests; +use Featurevisor\Featurevisor; use PHPUnit\Framework\TestCase; - use Psr\Log\LogLevel; -use function Featurevisor\createInstance; use function Featurevisor\createLogger; -class InstanceTest extends TestCase +class FeaturevisorTest extends TestCase { public function testShouldCreateInstanceWithDatafileContent() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -28,7 +27,7 @@ public function testShouldConfigurePlainBucketBy() { $capturedBucketKey = ''; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -77,7 +76,7 @@ public function testShouldConfigureAndBucketBy() { $capturedBucketKey = ''; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -126,7 +125,7 @@ public function testShouldConfigureOrBucketBy() { $capturedBucketKey = ''; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -183,7 +182,7 @@ public function testShouldInterceptContextBeforeHook() $interceptedFeatureKey = ''; $interceptedVariableKey = ''; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -236,7 +235,7 @@ public function testShouldInterceptValueAfterHook() $interceptedFeatureKey = ''; $interceptedVariableKey = ''; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -310,7 +309,7 @@ public function testShouldInitializeWithStickyFeatures() 'segments' => [], ]; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'sticky' => [ 'test' => [ 'enabled' => true, @@ -346,7 +345,7 @@ public function testShouldInitializeWithStickyFeatures() public function testShouldHonourSimpleRequiredFeatures() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -385,7 +384,7 @@ public function testShouldHonourSimpleRequiredFeatures() self::assertFalse($sdk->isEnabled('myKey')); // enabling required should enable the feature too - $sdk2 = createInstance([ + $sdk2 = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -425,7 +424,7 @@ public function testShouldHonourSimpleRequiredFeatures() public function testShouldHonourRequiredFeaturesWithVariation() { // should be disabled because required has different variation - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -472,7 +471,7 @@ public function testShouldHonourRequiredFeaturesWithVariation() self::assertFalse($sdk->isEnabled('myKey')); // child should be enabled because required has desired variation - $sdk2 = createInstance([ + $sdk2 = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -522,7 +521,7 @@ public function testShouldEmitWarningsForDeprecatedFeature() { $deprecatedCount = 0; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -586,7 +585,7 @@ public function testShouldEmitWarningsForDeprecatedFeature() public function testShouldCheckIfEnabledForOverriddenFlagsFromRules() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -634,7 +633,7 @@ public function testShouldCheckIfEnabledForMutuallyExclusiveFeatures() { $bucketValue = 10000; - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'hooks' => [ [ 'name' => 'unit-test', @@ -670,7 +669,7 @@ public function testShouldCheckIfEnabledForMutuallyExclusiveFeatures() public function testShouldGetVariation() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -750,7 +749,7 @@ public function testShouldGetVariation() public function testShouldGetVariable() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -1037,7 +1036,7 @@ public function testShouldGetVariable() public function testShouldGetVariablesWithoutAnyVariations() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', @@ -1099,7 +1098,7 @@ public function testShouldGetVariablesWithoutAnyVariations() public function testShouldCheckIfEnabledForIndividuallyNamedSegments() { - $sdk = createInstance([ + $sdk = Featurevisor::createInstance([ 'datafile' => [ 'schemaVersion' => '2', 'revision' => '1.0', From fdd01907d4171a9db043eb3373fa0ac7d3450f74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ku=C5=BAnik?= Date: Thu, 21 Aug 2025 17:26:47 +0200 Subject: [PATCH 3/5] refactor: Remove unnecessary createLogger function --- README.md | 8 ++++---- composer.json | 5 +---- featurevisor | 12 ++++++------ src/Logger.php | 16 ++++++++++++---- src/functions.php | 8 -------- tests/BucketerTest.php | 17 ++++++++--------- tests/ConditionsTest.php | 8 +++----- tests/DatafileReaderTest.php | 7 +++---- tests/EventsTest.php | 8 ++++---- tests/FeaturevisorTest.php | 4 ++-- tests/LoggerTest.php | 19 +++++++++---------- 11 files changed, 52 insertions(+), 60 deletions(-) delete mode 100644 src/functions.php diff --git a/README.md b/README.md index e614df6..d6a0c77 100644 --- a/README.md +++ b/README.md @@ -364,10 +364,10 @@ Setting `debug` level will print out all logs, including `info`, `warning`, and ```php use Featurevisor\Featurevisor; -use function Featurevisor\createLogger; +use Featurevisor\Logger; $f = Featurevisor::createInstance([ - "logger" => createLogger([ + "logger" => Logger::create([ "level" => "debug", ]), ]); @@ -393,10 +393,10 @@ You can also pass your own log handler, if you do not wish to print the logs to ```php use Featurevisor\Featurevisor; -use function Featurevisor\createLogger; +use Featurevisor\Logger; $f = Featurevisor::createInstance([ - "logger" => createLogger([ + "logger" => Logger::create([ "level" => "info", "handler" => function ($level, $message, $details) { // do something with the log diff --git a/composer.json b/composer.json index 5989581..fb18481 100644 --- a/composer.json +++ b/composer.json @@ -27,10 +27,7 @@ "autoload": { "psr-4": { "Featurevisor\\": "src/" - }, - "files": [ - "src/functions.php" - ] + } }, "autoload-dev": { "psr-4": { diff --git a/featurevisor b/featurevisor index e3966fb..98542d0 100755 --- a/featurevisor +++ b/featurevisor @@ -5,8 +5,8 @@ require __DIR__ . '/vendor/autoload.php'; use Featurevisor\DatafileReader; use Featurevisor\Featurevisor; +use Featurevisor\Logger; use Psr\Log\LogLevel; -use function Featurevisor\createLogger; /** * CLI Options @@ -257,7 +257,7 @@ function testSegment(array $assertion, array $segment, string $level): array { $datafileReader = DatafileReader::createFromOptions([ 'datafile' => $datafile, - 'logger' => createLogger([ + 'logger' => Logger::create([ 'level' => $level, ]), ]); @@ -306,7 +306,7 @@ function test(array $cliOptions) { $datafile = $datafilesByEnvironment[$environment]; $sdkInstancesByEnvironment[$environment] = Featurevisor::createInstance([ 'datafile' => $datafile, - 'logger' => createLogger([ + 'logger' => Logger::create([ 'level' => $level, ]), 'hooks' => [ @@ -345,7 +345,7 @@ function test(array $cliOptions) { $datafile = $datafilesByEnvironment[$environment]; $f = Featurevisor::createInstance([ 'datafile' => $datafile, - 'logger' => createLogger([ + 'logger' => Logger::create([ 'level' => $level, ]), 'hooks' => [ @@ -422,7 +422,7 @@ function benchmark(array $cliOptions) { $f = Featurevisor::createInstance([ 'datafile' => $datafilesByEnvironment[$cliOptions['environment']], - 'logger' => createLogger([ + 'logger' => Logger::create([ 'level' => $level, ]), ]); @@ -483,7 +483,7 @@ function assessDistribution(array $cliOptions) { $f = Featurevisor::createInstance([ 'datafile' => $datafilesByEnvironment[$cliOptions['environment']], - 'logger' => createLogger([ + 'logger' => Logger::create([ 'level' => $level, ]), ]); diff --git a/src/Logger.php b/src/Logger.php index 563242c..92e12b4 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -36,10 +36,18 @@ class Logger implements LoggerInterface * handler?: Closure, * } $options */ - public function __construct(array $options = []) + public static function create(array $options = []): self { - $this->level = $options['level'] ?? self::DEFAULT_LEVEL; - $this->handler = $options['handler'] ?? static fn ($level, $message, array $context) => self::defaultLogHandler($level, $message, $context); + return new self( + $options['level'] ?? self::DEFAULT_LEVEL, + $options['handler'] ?? null + ); + } + + public function __construct(string $level = self::DEFAULT_LEVEL, Closure $handler = null) + { + $this->handler = $handler ?? static fn ($level, $message, array $context) => self::defaultLogHandler($level, $message, $context); + $this->level = $level; } public function setLevel(string $level): void @@ -62,7 +70,7 @@ public function log($level, $message, array $context = []): void ($this->handler)($level, self::MSG_PREFIX.' '.$message, $context); } - public static function defaultLogHandler($level, $message, ?array $details = null): void + private static function defaultLogHandler($level, $message, ?array $details = null): void { if (STDOUT === false) { return; diff --git a/src/functions.php b/src/functions.php deleted file mode 100644 index 4c48667..0000000 --- a/src/functions.php +++ /dev/null @@ -1,8 +0,0 @@ - '123', 'browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -57,7 +56,7 @@ public function testGetBucketKeyPlainMissingContext() { $featureKey = 'test-feature'; $bucketBy = 'userId'; $context = ['browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -71,7 +70,7 @@ public function testGetBucketKeyAndAllPresent() { $featureKey = 'test-feature'; $bucketBy = ['organizationId', 'userId']; $context = ['organizationId' => '123', 'userId' => '234', 'browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -85,7 +84,7 @@ public function testGetBucketKeyAndPartial() { $featureKey = 'test-feature'; $bucketBy = ['organizationId', 'userId']; $context = ['organizationId' => '123', 'browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -103,7 +102,7 @@ public function testGetBucketKeyAndDotSeparated() { 'user' => ['id' => '234'], 'browser' => 'chrome', ]; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -121,7 +120,7 @@ public function testGetBucketKeyOrFirstAvailable() { $featureKey = 'test-feature'; $bucketBy = ['or' => ['userId', 'deviceId']]; $context = ['deviceId' => 'deviceIdHere', 'userId' => '234', 'browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, @@ -135,7 +134,7 @@ public function testGetBucketKeyOrOnlyDeviceId() { $featureKey = 'test-feature'; $bucketBy = ['or' => ['userId', 'deviceId']]; $context = ['deviceId' => 'deviceIdHere', 'browser' => 'chrome']; - $logger = createLogger(['level' => LogLevel::WARNING]); + $logger = Logger::create(['level' => LogLevel::WARNING]); $bucketKey = Bucketer::getBucketKey([ 'featureKey' => $featureKey, 'bucketBy' => $bucketBy, diff --git a/tests/ConditionsTest.php b/tests/ConditionsTest.php index 65cf851..d16ab9f 100644 --- a/tests/ConditionsTest.php +++ b/tests/ConditionsTest.php @@ -3,17 +3,15 @@ namespace Featurevisor\Tests; use DateTime; +use Featurevisor\Logger; use PHPUnit\Framework\TestCase; - use Featurevisor\DatafileReader; -use function Featurevisor\createLogger; class ConditionsTest extends TestCase { - private $datafileReader; + private DatafileReader $datafileReader; protected function setUp(): void { - $logger = createLogger(); - $this->datafileReader = DatafileReader::createEmpty($logger); + $this->datafileReader = DatafileReader::createEmpty(Logger::create()); } public function testMatchAllViaStar() { diff --git a/tests/DatafileReaderTest.php b/tests/DatafileReaderTest.php index 94d45cf..08b2ad9 100644 --- a/tests/DatafileReaderTest.php +++ b/tests/DatafileReaderTest.php @@ -2,10 +2,9 @@ namespace Featurevisor\Tests; +use Featurevisor\Logger; use PHPUnit\Framework\TestCase; - use Featurevisor\DatafileReader; -use function Featurevisor\createLogger; class DatafileReaderTest extends TestCase { @@ -49,7 +48,7 @@ public function testV2DatafileSchemaEntities() { ], ], ]; - $logger = createLogger(); + $logger = Logger::create(); $reader = DatafileReader::createFromOptions([ 'datafile' => $datafileJson, 'logger' => $logger, @@ -112,7 +111,7 @@ public function testSegmentsMatching() { ], ], ]; - $logger = createLogger(); + $logger = Logger::create(); $datafileReader = DatafileReader::createFromOptions([ 'datafile' => $datafileContent, 'logger' => $logger, diff --git a/tests/EventsTest.php b/tests/EventsTest.php index 16f9ac5..3ec7a2b 100644 --- a/tests/EventsTest.php +++ b/tests/EventsTest.php @@ -2,11 +2,11 @@ namespace Featurevisor\Tests; +use Featurevisor\Logger; use PHPUnit\Framework\TestCase; use Featurevisor\Events; use Featurevisor\DatafileReader; -use function Featurevisor\createLogger; class EventsTest extends TestCase { @@ -49,7 +49,7 @@ public function testGetParamsForStickySetEventAddChangeRemove() public function testGetParamsForDatafileSetEventEmptyToNew() { - $logger = createLogger([ + $logger = Logger::create([ 'level' => 'error', ]); @@ -88,7 +88,7 @@ public function testGetParamsForDatafileSetEventEmptyToNew() public function testGetParamsForDatafileSetEventChangeHashAddition() { - $logger = createLogger([ + $logger = Logger::create([ 'level' => 'error', ]); @@ -131,7 +131,7 @@ public function testGetParamsForDatafileSetEventChangeHashAddition() public function testGetParamsForDatafileSetEventChangeHashRemoval() { - $logger = createLogger([ + $logger = Logger::create([ 'level' => 'error', ]); diff --git a/tests/FeaturevisorTest.php b/tests/FeaturevisorTest.php index 67851e3..6167da6 100644 --- a/tests/FeaturevisorTest.php +++ b/tests/FeaturevisorTest.php @@ -3,9 +3,9 @@ namespace Featurevisor\Tests; use Featurevisor\Featurevisor; +use Featurevisor\Logger; use PHPUnit\Framework\TestCase; use Psr\Log\LogLevel; -use function Featurevisor\createLogger; class FeaturevisorTest extends TestCase { @@ -562,7 +562,7 @@ public function testShouldEmitWarningsForDeprecatedFeature() ], 'segments' => [], ], - 'logger' => createLogger([ + 'logger' => Logger::create([ 'handler' => function($level, $message) use (&$deprecatedCount) { if ($level === LogLevel::WARNING && strpos($message, 'is deprecated') !== false) { $deprecatedCount += 1; diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 54194e6..0422582 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -7,7 +7,6 @@ use Featurevisor\Logger; use Psr\Log\LogLevel; -use function Featurevisor\createLogger; class LoggerTest extends TestCase { @@ -29,13 +28,13 @@ protected function setUp(): void public function testCreateLoggerWithDefaultOptions(): void { - $logger = createLogger(); + $logger = Logger::create(); self::assertInstanceOf(Logger::class, $logger); } public function testCreateLoggerWithCustomLevel(): void { - $logger = createLogger(['level' => 'debug']); + $logger = Logger::create(['level' => 'debug']); self::assertInstanceOf(Logger::class, $logger); } @@ -49,7 +48,7 @@ public function testCreateLoggerWithCustomHandler(): void self::assertSame([], $details); }; - $logger = createLogger(['handler' => $customHandler]); + $logger = Logger::create(['handler' => $customHandler]); $logger->info('test message'); self::assertTrue($customHandlerCalled); @@ -57,7 +56,7 @@ public function testCreateLoggerWithCustomHandler(): void public function testLoggerConstructorUsesDefaultLogLevelWhenNoneProvided(): void { - $logger = new Logger([]); + $logger = Logger::create(); // Capture output to verify debug is not logged with default level (info) $logger->debug('debug message'); @@ -94,7 +93,7 @@ public function testLoggerConstructorUsesProvidedHandler(): void self::assertSame([], $details); }; - $logger = new Logger(['handler' => $customHandler]); + $logger = Logger::create(['handler' => $customHandler]); $logger->info('test message'); self::assertTrue($customHandlerCalled); @@ -205,7 +204,7 @@ public function testLogMethodCallsHandlerWithCorrectParameters(): void self::assertEquals(['test' => true], $details); }; - $logger = new Logger(['handler' => $customHandler, 'level' => 'debug']); + $logger = Logger::create(['handler' => $customHandler, 'level' => 'debug']); $details = ['test' => true]; $logger->log('info', 'test message', $details); @@ -220,7 +219,7 @@ public function testLogMethodNotCallHandlerWhenLevelIsFilteredOut(): void $customHandlerCalled = true; }; - $logger = new Logger(['handler' => $customHandler, 'level' => LogLevel::WARNING]); + $logger = Logger::create(['handler' => $customHandler, 'level' => LogLevel::WARNING]); $logger->log('debug', 'debug message'); self::assertFalse($customHandlerCalled); @@ -228,9 +227,9 @@ public function testLogMethodNotCallHandlerWhenLevelIsFilteredOut(): void private function getLogger(string $level = Logger::DEFAULT_LEVEL): Logger { - return new Logger(['level' => $level, 'handler' => function ($level, $message, array $context) { + return Logger::create(['level' => $level, 'handler' => function ($level, $message, array $context) { $context = $context !== [] ? ' ' . json_encode($context, JSON_THROW_ON_ERROR) : ''; - $this->logBuffer .= $message.$context.PHP_EOL; + $this->logBuffer .= $message . $context . PHP_EOL; }]); } } From 3e734479282a3a5f51df2c827e885d5e0dcc885d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ku=C5=BAnik?= Date: Thu, 21 Aug 2025 17:27:08 +0200 Subject: [PATCH 4/5] docs: Add PHPDoc for better API experience --- src/DatafileReader.php | 13 ++- src/Featurevisor.php | 192 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 200 insertions(+), 5 deletions(-) diff --git a/src/DatafileReader.php b/src/DatafileReader.php index 8332e45..9aacdf7 100644 --- a/src/DatafileReader.php +++ b/src/DatafileReader.php @@ -2,6 +2,9 @@ namespace Featurevisor; +use Exception; +use InvalidArgumentException; +use JsonException; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -29,6 +32,10 @@ public static function createEmpty(LoggerInterface $logger): self ]); } + /** + * @param string|array $datafile + * @throws JsonException + */ public static function createFromMixed($datafile, LoggerInterface $logger): self { return is_string($datafile) @@ -40,7 +47,7 @@ public static function createFromMixed($datafile, LoggerInterface $logger): self } /** - * @throws \JsonException + * @throws JsonException */ public static function createFromJson(string $json, LoggerInterface $logger): self { @@ -55,7 +62,7 @@ public static function createFromJson(string $json, LoggerInterface $logger): se public static function createFromOptions(array $data): self { if (array_key_exists('datafile', $data) === false ) { - throw new \InvalidArgumentException('Missing datafile key in data array'); + throw new InvalidArgumentException('Missing datafile key in data array'); } return new self( @@ -203,7 +210,7 @@ public function allConditionsAreMatched($conditions, array $context): bool if (isset($conditions['attribute'])) { try { return Conditions::conditionIsMatched($conditions, $context, $getRegex); - } catch (\Exception $e) { + } catch (Exception $e) { $this->logger->warning($e->getMessage(), [ 'exception' => $e, 'condition' => $conditions, diff --git a/src/Featurevisor.php b/src/Featurevisor.php index a6f0aac..bf98fa9 100644 --- a/src/Featurevisor.php +++ b/src/Featurevisor.php @@ -16,7 +16,7 @@ class Featurevisor /** * @param array{ - * datafile?: string|array, + * datafile?: string|array, * logger?: LoggerInterface, * context?: array, * sticky?: array, @@ -65,6 +65,9 @@ public function __construct( $this->logger->info('Featurevisor SDK initialized'); } + /** + * @param string|array $datafile + */ public function setDatafile($datafile): void { try { @@ -77,10 +80,13 @@ public function setDatafile($datafile): void $this->logger->info('datafile set', $details); $this->emitter->trigger('datafile_set', $details); } catch (\Exception $e) { - $this->logger->error('could not parse datafile', ['error' => $e->getMessage(), 'exception' => $e]);; + $this->logger->error('could not parse datafile', ['error' => $e->getMessage(), 'exception' => $e]); } } + /** + * @param array $sticky + */ public function setSticky(array $sticky, bool $replace = false): void { $previousStickyFeatures = $this->sticky ?? []; @@ -122,6 +128,9 @@ public function close(): void $this->emitter->clearAll(); } + /** + * @param array $context + */ public function setContext(array $context, bool $replace = false): void { if ($replace) { @@ -141,11 +150,22 @@ public function setContext(array $context, bool $replace = false): void ]); } + /** + * @param array $context + * @return array + */ public function getContext(array $context = []): array { return !empty($context) ? array_merge($this->context, $context) : $this->context; } + /** + * @param array $context + * @param array{ + * sticky?: array + * } $options + * @return Child + */ public function spawn(array $context = [], array $options = []): Child { return new Child([ @@ -155,6 +175,16 @@ public function spawn(array $context = [], array $options = []): Child ]); } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array + */ private function getEvaluationDependencies(array $context, array $options = []): array { $sticky = $this->sticky; @@ -177,6 +207,24 @@ private function getEvaluationDependencies(array $context, array $options = []): ]); } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array{ + * type: string, + * featureKey: string, + * reason: string, + * bucketKey: string, + * bucketValue: string, + * enabled: bool, + * error?: string, + * } + */ public function evaluateFlag(string $featureKey, array $context = [], array $options = []): array { $deps = $this->getEvaluationDependencies($context, $options); @@ -187,6 +235,15 @@ public function evaluateFlag(string $featureKey, array $context = [], array $opt ])); } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function isEnabled(string $featureKey, array $context = [], array $options = []): bool { $evaluation = $this->evaluateFlag($featureKey, $context, $options); @@ -194,6 +251,24 @@ public function isEnabled(string $featureKey, array $context = [], array $option return $evaluation['enabled'] ?? false; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array{ + * type: string, + * featureKey: string, + * reason: string, + * bucketKey: string, + * bucketValue: string, + * enabled: bool, + * error?: string, + * } + */ public function evaluateVariation(string $featureKey, array $context = [], array $options = []): array { $deps = $this->getEvaluationDependencies($context, $options); @@ -204,6 +279,16 @@ public function evaluateVariation(string $featureKey, array $context = [], array ])); } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return mixed|null + */ public function getVariation(string $featureKey, array $context = [], array $options = []) { try { @@ -229,6 +314,24 @@ public function getVariation(string $featureKey, array $context = [], array $opt } } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array{ + * type: string, + * featureKey: string, + * reason: string, + * bucketKey: string, + * bucketValue: string, + * enabled: bool, + * error?: string, + * } + */ public function evaluateVariable(string $featureKey, string $variableKey, array $context = [], array $options = []): array { $deps = $this->getEvaluationDependencies($context, $options); @@ -240,6 +343,16 @@ public function evaluateVariable(string $featureKey, string $variableKey, array ])); } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return mixed|null + */ public function getVariable(string $featureKey, string $variableKey, array $context = [], array $options = []) { try { @@ -271,6 +384,15 @@ public function getVariable(string $featureKey, string $variableKey, array $cont } } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableBoolean(string $featureKey, string $variableKey, array $context = [], array $options = []): ?bool { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -282,6 +404,15 @@ public function getVariableBoolean(string $featureKey, string $variableKey, arra return (bool) $value; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableString(string $featureKey, string $variableKey, array $context = [], array $options = []): ?string { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -293,6 +424,15 @@ public function getVariableString(string $featureKey, string $variableKey, array return (string) $value; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableInteger(string $featureKey, string $variableKey, array $context = [], array $options = []): ?int { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -304,6 +444,15 @@ public function getVariableInteger(string $featureKey, string $variableKey, arra return (int) $value; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableDouble(string $featureKey, string $variableKey, array $context = [], array $options = []): ?float { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -315,6 +464,15 @@ public function getVariableDouble(string $featureKey, string $variableKey, array return (float) $value; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableArray(string $featureKey, string $variableKey, array $context = [], array $options = []): ?array { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -326,6 +484,15 @@ public function getVariableArray(string $featureKey, string $variableKey, array return is_array($value) ? $value : [$value]; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + */ public function getVariableObject(string $featureKey, string $variableKey, array $context = [], array $options = []) { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -337,6 +504,16 @@ public function getVariableObject(string $featureKey, string $variableKey, array return is_array($value) ? $value : null; } + /** + * @param array $context + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array|mixed|null + */ public function getVariableJSON(string $featureKey, string $variableKey, array $context = [], array $options = []) { $value = $this->getVariable($featureKey, $variableKey, $context, $options); @@ -353,6 +530,17 @@ public function getVariableJSON(string $featureKey, string $variableKey, array $ return $value; } + /** + * @param array $context + * @param array $featureKeys + * @param array{ + * defaultVariationValue?: mixed, + * defaultVariableValue?: mixed, + * flagEvaluation?: array, + * sticky?: array + * } $options + * @return array + */ public function getAllEvaluations(array $context = [], array $featureKeys = [], array $options = []): array { $deps = $this->getEvaluationDependencies($context, $options); From eb88fee487ce4650cc64e25fe736f29c28728189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ku=C5=BAnik?= Date: Thu, 21 Aug 2025 17:30:05 +0200 Subject: [PATCH 5/5] fix: Fix SAST issues --- README.md | 2 +- src/Featurevisor.php | 2 ++ tests/LoggerTest.php | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d6a0c77..bf4f34f 100644 --- a/README.md +++ b/README.md @@ -335,7 +335,7 @@ $f->setDatafile($datafileContent); ### Updating datafile -You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile-set) event that you can listen and react to accordingly. +You can set the datafile as many times as you want in your application, which will result in emitting a [`datafile_set`](#datafile_set) event that you can listen and react to accordingly. The triggers for setting the datafile again can be: diff --git a/src/Featurevisor.php b/src/Featurevisor.php index bf98fa9..d58ae9e 100644 --- a/src/Featurevisor.php +++ b/src/Featurevisor.php @@ -2,6 +2,7 @@ namespace Featurevisor; +use Closure; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -265,6 +266,7 @@ public function isEnabled(string $featureKey, array $context = [], array $option * reason: string, * bucketKey: string, * bucketValue: string, + * variation: array, * enabled: bool, * error?: string, * } diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 0422582..7379b5b 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -2,7 +2,6 @@ namespace Featurevisor\Tests; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Featurevisor\Logger;