diff --git a/.gitignore b/.gitignore index 98d8c720..514bed27 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ flagship /.phpunit.cache demo-test.php docker-compose.demo.yml +sample.php +sample.php diff --git a/.vscode/settings.json b/.vscode/settings.json index ce6abab7..815d4b97 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,5 @@ }, "php-cs-fixer.executablePath": "./vendor/bin/php-cs-fixer", "php-cs-fixer.onsave": true, + "intelephense.environment.phpVersion": "8.1.0", } diff --git a/composer.json b/composer.json index a49799a4..00343e14 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,11 @@ "name": "flagship-io/flagship-php-sdk", "type": "library", "description": "Flagship PHP SDK", - "keywords": ["flagship", "sdk", "flagship-php"], + "keywords": [ + "flagship", + "sdk", + "flagship-php" + ], "homepage": "https://github.com/flagship-io/flagship-php-sdk", "authors": [ { @@ -18,7 +22,12 @@ }, "require-dev": { "squizlabs/php_codesniffer": "^3.10", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "php-mock/php-mock-phpunit": "^2.14" }, "autoload": { "psr-4": { @@ -31,8 +40,19 @@ } }, "scripts": { + "test": "phpunit", + "test:all": [ + "@phpstan:all", + "phpunit" + ], "check-style": "phpcs --standard=PSR12 -n src tests", - "fix-style": "phpcbf --standard=phpcs.xml src tests" + "fix-style": "phpcbf --standard=phpcs.xml src tests", + "phpstan": "phpstan analyse --configuration=phpstan.neon --memory-limit=512M", + "phpstan:future": "phpstan analyse --configuration=phpstan-future.neon --memory-limit=512M", + "phpstan:all": [ + "@phpstan", + "@phpstan:future" + ] } } diff --git a/composer.lock b/composer.lock index c0719d09..564e0075 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "676c01a7b9b8f0b01e28db776080a602", + "content-hash": "eae07c148068ea0f65789a193ba0030f", "packages": [ { "name": "psr/log", @@ -354,6 +354,414 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-mock/php-mock", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock.git", + "reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/e134d210e4707c29724ebc7fe50d220123f0fdd9", + "reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0 || ^8.0", + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" + }, + "replace": { + "malkusch/php-mock": "*" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", + "squizlabs/php_codesniffer": "^3.8" + }, + "suggest": { + "php-mock/php-mock-phpunit": "Allows integration into PHPUnit testcase with the trait PHPMock." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "phpmock\\": [ + "classes/", + "tests/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "PHP-Mock can mock built-in PHP functions (e.g. time()). PHP-Mock relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock/issues", + "source": "https://github.com/php-mock/php-mock/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2025-08-18T19:59:14+00:00" + }, + { + "name": "php-mock/php-mock-integration", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-integration.git", + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/8ceb860f343a143af604efeb66a7a124381cc52e", + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "php-mock/php-mock": "^2.5", + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\integration\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Integration package for PHP-Mock", + "homepage": "https://github.com/php-mock/php-mock-integration", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "stub", + "test", + "test double" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-integration/issues", + "source": "https://github.com/php-mock/php-mock-integration/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2025-03-08T19:22:38+00:00" + }, + { + "name": "php-mock/php-mock-phpunit", + "version": "2.14.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-phpunit.git", + "reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/c074f7a260cb80bdc7cf0823dc23174bc49064e1", + "reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1", + "shasum": "" + }, + "require": { + "php": ">=7", + "php-mock/php-mock-integration": "^3.0", + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9" + }, + "require-dev": { + "mockery/mockery": "^1.3.6" + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "phpmock\\phpunit\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with PHPUnit. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-phpunit", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "phpunit", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-phpunit/issues", + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.14.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2025-11-19T21:07:31+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "2.1.33", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", + "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2025-12-05T10:24:31+00:00" + }, + { + "name": "phpstan/phpstan-deprecation-rules", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/468e02c9176891cc901143da118f09dc9505fc2f", + "reference": "468e02c9176891cc901143da118f09dc9505fc2f", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.15" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", + "support": { + "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.3" + }, + "time": "2025-05-14T10:56:57+00:00" + }, + { + "name": "phpstan/phpstan-phpunit", + "version": "2.0.11", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phpunit.git", + "reference": "5e30669bef866eff70db8b58d72a5c185aa82414" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/5e30669bef866eff70db8b58d72a5c185aa82414", + "reference": "5e30669bef866eff70db8b58d72a5c185aa82414", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.32" + }, + "conflict": { + "phpunit/phpunit": "<7.0" + }, + "require-dev": { + "nikic/php-parser": "^5", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon", + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPUnit extensions and rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-phpunit/issues", + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.11" + }, + "time": "2025-12-19T09:05:35+00:00" + }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.7", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/d6211c46213d4181054b3d77b10a5c5cb0d59538", + "reference": "d6211c46213d4181054b3d77b10a5c5cb0d59538", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.1.29" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.7" + }, + "time": "2025-09-26T11:19:08+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.24", @@ -1872,7 +2280,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -1880,6 +2288,6 @@ "ext-curl": "*", "ext-json": "*" }, - "platform-dev": [], - "plugin-api-version": "2.6.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/phpcs.xml b/phpcs.xml index f6d98641..057919ad 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -5,6 +5,8 @@ + + src tests diff --git a/phpstan-future.neon b/phpstan-future.neon new file mode 100644 index 00000000..7130b4e9 --- /dev/null +++ b/phpstan-future.neon @@ -0,0 +1,5 @@ +includes: + - phpstan.neon + +parameters: + phpVersion: 80500 \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..b9462e10 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,18 @@ +includes: + - vendor/phpstan/phpstan-deprecation-rules/rules.neon + # - vendor/phpstan/phpstan-phpunit/extension.neon + +parameters: + level: max + paths: + - src + phpVersion: 80100 + + # Treat PHPDoc types as certain for better inference + treatPhpDocTypesAsCertain: false + + # Ignore vendor and optional files + excludePaths: + - tests/bootstrap.php (?) + - vendor/ + diff --git a/phpunit.xml b/phpunit.xml index 7f1bed60..3ac500aa 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -25,9 +25,6 @@ src - - src/Utils/FlagshipLogManager.php - diff --git a/src/Api/BatchingCachingStrategyAbstract.php b/src/Api/BatchingCachingStrategyAbstract.php index 47ed18ee..abcef2d6 100644 --- a/src/Api/BatchingCachingStrategyAbstract.php +++ b/src/Api/BatchingCachingStrategyAbstract.php @@ -138,33 +138,33 @@ public function getActivatePoolQueue(): array } /** - * @param $key + * @param string $key * @param HitAbstract $hit * @return void */ - public function hydrateHitsPoolQueue($key, HitAbstract $hit): void + public function hydrateHitsPoolQueue(string $key, HitAbstract $hit): void { $this->hitsPoolQueue[$key] = $hit; } /** - * @param $key + * @param string $key * @param Activate $hit * @return void */ - public function hydrateActivatePoolQueue($key, Activate $hit): void + public function hydrateActivatePoolQueue(string $key, Activate $hit): void { $this->activatePoolQueue[$key] = $hit; } /** - * @return array + * @return array */ public function getActivateHeaders(): array { return [ - FlagshipConstant::HEADER_X_API_KEY => $this->config->getApiKey(), + FlagshipConstant::HEADER_X_API_KEY => $this->config->getApiKey() ?? '', FlagshipConstant::HEADER_X_SDK_VERSION => FlagshipConstant::SDK_VERSION, FlagshipConstant::HEADER_CONTENT_TYPE => FlagshipConstant::HEADER_APPLICATION_JSON, FlagshipConstant::HEADER_X_SDK_CLIENT => FlagshipConstant::SDK_LANGUAGE, @@ -172,10 +172,10 @@ public function getActivateHeaders(): array } /** - * @param $visitorId + * @param string $visitorId * @return string */ - public function generateHitKey($visitorId): string + public function generateHitKey(string $visitorId): string { return $visitorId . ":" . $this->newGuid(); } @@ -280,7 +280,7 @@ protected function onVisitorExposed(Activate $activate): void } $exposedFlag = new ExposedFlag( - $activate->getFlagKey(), + $activate->getFlagKey() ?? '', $activate->getFlagValue(), $activate->getFlagDefaultValue(), $activate->getFlagMetadata() @@ -288,7 +288,7 @@ protected function onVisitorExposed(Activate $activate): void $exposedUser = new ExposedVisitor( $activate->getVisitorId(), $activate->getAnonymousId(), - $activate->getVisitorContext() + $activate->getVisitorContext() ?? [] ); try { @@ -298,7 +298,7 @@ protected function onVisitorExposed(Activate $activate): void } } - protected function sendActivateHitBatch(ActivateBatch $activateBatch) + protected function sendActivateHitBatch(ActivateBatch $activateBatch): void { $headers = $this->getActivateHeaders(); $requestBody = $activateBatch->toApiKeys(); @@ -346,7 +346,7 @@ protected function sendActivateHitBatch(ActivateBatch $activateBatch) ->setFlagshipInstanceId($this->flagshipInstanceId) ->setLogLevel(LogLevel::ERROR) ->setTraffic(100)->setConfig($this->config) - ->setVisitorId($this->flagshipInstanceId); + ->setVisitorId($this->flagshipInstanceId ?? ""); $this->addTroubleshootingHit($troubleshooting); $this->sendTroubleshootingQueue(); @@ -386,7 +386,7 @@ protected function sendActivateHit(): void /** * @param string $visitorId - * @return string [] + * @return string[] */ protected function commonNotConsent(string $visitorId): array { @@ -497,7 +497,7 @@ public function sendBatch(): void ->setHttpResponseBody($exception->getMessage()) ->setHttpResponseTime($this->getNow() - $now) ->setTraffic(100)->setConfig($this->config) - ->setVisitorId($this->flagshipInstanceId); + ->setVisitorId($this->flagshipInstanceId ?? ""); $this->addTroubleshootingHit($troubleshooting); $this->sendTroubleshootingQueue(); $this->logErrorSprintf( @@ -532,7 +532,7 @@ public function cacheHit(array $hits): void HitCacheFields::DATA => [ HitCacheFields::VISITOR_ID => $hit->getVisitorId(), HitCacheFields::ANONYMOUS_ID => $hit->getAnonymousId(), - HitCacheFields::TYPE => $hit->getType(), + HitCacheFields::TYPE => $hit->getType()->value, HitCacheFields::CONTENT => $hit->toArray(), HitCacheFields::TIME => $this->getNow(), ], @@ -563,7 +563,7 @@ public function cacheHit(array $hits): void } /** - * @param array $hitKeys + * @param array $hitKeys * @return void */ public function flushHits(array $hitKeys): void @@ -649,7 +649,7 @@ public function addTroubleshootingHit(Troubleshooting $hit): void return; } $troubleshootingData = $this->getTroubleshootingData(); - if ($troubleshootingData->getTraffic() < $hit->getTraffic()) { + if ($troubleshootingData?->getTraffic() < $hit->getTraffic()) { return; } $hitKey = $this->generateHitKey($hit->getVisitorId()); diff --git a/src/Api/NoBatchingContinuousCachingStrategy.php b/src/Api/NoBatchingContinuousCachingStrategy.php index bc311fbc..2dbd023b 100644 --- a/src/Api/NoBatchingContinuousCachingStrategy.php +++ b/src/Api/NoBatchingContinuousCachingStrategy.php @@ -94,7 +94,7 @@ protected function sendHit(HitAbstract $hit): void ->setHttpResponseBody($exception->getMessage()) ->setHttpResponseTime($this->getNow() - $now) ->setTraffic(100)->setConfig($this->config) - ->setVisitorId($this->flagshipInstanceId) + ->setVisitorId($this->flagshipInstanceId ?? "") ; $this->addTroubleshootingHit($troubleshooting); $this->sendTroubleshootingQueue(); @@ -155,7 +155,7 @@ public function activateFlag(Activate $hit): void ->setHttpResponseTime($this->getNow() - $now) ->setLogLevel(LogLevel::ERROR) ->setTraffic(100)->setConfig($this->config) - ->setVisitorId($this->flagshipInstanceId); + ->setVisitorId($this->flagshipInstanceId ?? ""); $this->addTroubleshootingHit($troubleshooting); $this->sendTroubleshootingQueue(); } @@ -165,7 +165,7 @@ public function activateFlag(Activate $hit): void protected function notConsent(string $visitorId): void { $keysToFlush = $this->commonNotConsent($visitorId); - $mergedQueue = array_merge($keysToFlush, $this->cacheHitKeys); + $mergedQueue = array_merge($keysToFlush, $this->cacheHitKeys); if (count($mergedQueue) === 0) { return; } diff --git a/src/Api/TrackingManagerAbstract.php b/src/Api/TrackingManagerAbstract.php index c8385cc3..6aaa6baf 100644 --- a/src/Api/TrackingManagerAbstract.php +++ b/src/Api/TrackingManagerAbstract.php @@ -23,6 +23,7 @@ /** * Class TrackingManagerAbstract * @package Flagship\Api + * @phpstan-import-type HitCacheDataArray from \Flagship\Model\Types */ abstract class TrackingManagerAbstract implements TrackingManagerInterface { @@ -124,28 +125,41 @@ public function initStrategy(): BatchingCachingStrategyAbstract }; } + /** + * @param array $item + * @return bool + */ protected function checkLookupHitData(array $item): bool { - if ( - isset($item[HitCacheFields::DATA][HitCacheFields::CONTENT]) && - isset($item[HitCacheFields::DATA][HitCacheFields::TYPE]) && - isset($item[HitCacheFields::VERSION]) && $item[HitCacheFields::VERSION] == 1 - ) { - return true; + $data = $item[HitCacheFields::DATA] ?? null; + if (is_null($data) || !is_array($data)) { + $this->logErrorSprintf( + $this->config, + FlagshipConstant::PROCESS_CACHE, + FlagshipConstant::HIT_CACHE_FORMAT_ERROR, + [$item] + ); + return false; + } + $content = $data[HitCacheFields::CONTENT] ?? null; + $type = $data[HitCacheFields::TYPE] ?? null; + $version = $item[HitCacheFields::VERSION] ?? null; + if (is_null($content) || is_null($type) || is_null($version) || !is_array($content)) { + $this->logErrorSprintf( + $this->config, + FlagshipConstant::PROCESS_CACHE, + FlagshipConstant::HIT_CACHE_FORMAT_ERROR, + [$item] + ); + return false; } - $this->logErrorSprintf( - $this->config, - FlagshipConstant::PROCESS_CACHE, - FlagshipConstant::HIT_CACHE_FORMAT_ERROR, - [$item] - ); - return false; + return true; } - protected function checkHitTime($time): bool + protected function checkHitTime(float $time): bool { $now = round(microtime(true) * 1000); - return ($now - $time) >= FlagshipConstant::DEFAULT_HIT_CACHE_TIME_MS; + return ($now - $time) <= FlagshipConstant::DEFAULT_HIT_CACHE_TIME_MS; } public function lookupHits(): void { @@ -163,52 +177,52 @@ public function lookupHits(): void [$hitsCache] ); - if (!is_array($hitsCache) || !count($hitsCache)) { + if (empty($hitsCache)) { return; } $hitKeysToRemove = []; + foreach ($hitsCache as $key => $item) { - $hitKeysToRemove [] = $key; - if ( - !$this->checkLookupHitData($item) || - $this->checkHitTime($item[HitCacheFields::DATA][HitCacheFields::TIME]) - ) { + $hitKeysToRemove[] = $key; + + if (!$this->checkLookupHitData($item)) { + continue; + } + + $hitCacheData = $item[HitCacheFields::DATA]; + $hitTime = $hitCacheData[HitCacheFields::TIME] ?? 0; + + if (!$this->checkHitTime($hitTime)) { continue; } $type = $item[HitCacheFields::DATA][HitCacheFields::TYPE]; $content = $item[HitCacheFields::DATA][HitCacheFields::CONTENT]; - switch ($type) { - case HitType::EVENT->value: - $hit = HitAbstract::hydrate(Event::getClassName(), $content); - break; - case HitType::ITEM->value: - $hit = HitAbstract::hydrate(Item::getClassName(), $content); - break; - case HitType::PAGE_VIEW->value: - $hit = HitAbstract::hydrate(Page::getClassName(), $content); - break; - case HitType::SCREEN_VIEW->value: - $hit = HitAbstract::hydrate(Screen::getClassName(), $content); - break; - case HitType::SEGMENT->value: - $hit = HitAbstract::hydrate(Segment::getClassName(), $content); - break; - case HitType::ACTIVATE->value: - $hit = HitAbstract::hydrate(Activate::getClassName(), $content); - $hit->setConfig($this->config); - $this->getStrategy()->hydrateActivatePoolQueue($hit->getKey(), $hit); - continue 2; - case HitType::TRANSACTION->value: - $hit = HitAbstract::hydrate(Transaction::getClassName(), $content); - break; - default: - continue 2; + if (HitType::tryFrom($type) === HitType::ACTIVATE) { + $hit = HitAbstract::hydrate(Activate::class, $content); + $hit->setConfig($this->config); + $this->getStrategy()->hydrateActivatePoolQueue($hit->getKey(), $hit); + continue; } + $hit = match (HitType::tryFrom($type)) { + HitType::EVENT => HitAbstract::hydrate(Event::class, $content), + HitType::ITEM => HitAbstract::hydrate(Item::class, $content), + HitType::PAGE_VIEW => HitAbstract::hydrate(Page::class, $content), + HitType::SCREEN_VIEW => HitAbstract::hydrate(Screen::class, $content), + HitType::SEGMENT => HitAbstract::hydrate(Segment::class, $content), + HitType::TRANSACTION => HitAbstract::hydrate(Transaction::class, $content), + default => null, + }; + + if (is_null($hit)) { + continue; + } + + $hit->setConfig($this->config); $this->getStrategy()->hydrateHitsPoolQueue($hit->getKey(), $hit); } @@ -220,8 +234,8 @@ public function lookupHits(): void FlagshipConstant::PROCESS_CACHE, FlagshipConstant::HIT_CACHE_ERROR, [ - "lookupHits", - $exception->getMessage(), + "lookupHits", + $exception->getMessage(), ] ); } diff --git a/src/Cache/IHitCacheImplementation.php b/src/Cache/IHitCacheImplementation.php index 72cee2c3..9957bf09 100644 --- a/src/Cache/IHitCacheImplementation.php +++ b/src/Cache/IHitCacheImplementation.php @@ -2,12 +2,15 @@ namespace Flagship\Cache; +/** + * @phpstan-import-type HitCacheArray from \Flagship\Model\Types + */ interface IHitCacheImplementation { /** * This method will be called to cache visitor hits when a hit has failed to be sent if there is no internet, * there has been a timeout or if the request responded with something > 2XX. - * @param array $hits + * @param array $hits * @return void */ public function cacheHit(array $hits): void; @@ -16,13 +19,13 @@ public function cacheHit(array $hits): void; * This method will be called to load hits corresponding to visitor ID * from your database and trying to send them again in the background. * Note: Hits older than 4H will be ignored - * @return array + * @return array */ public function lookupHits(): array; /** * This method will be called to erase the visitor hits cache corresponding to visitor ID from your database. - * @param array $hitKeys + * @param string[] $hitKeys * @return void */ public function flushHits(array $hitKeys): void; diff --git a/src/Cache/IVisitorCacheImplementation.php b/src/Cache/IVisitorCacheImplementation.php index f9beeb1b..f5b3458e 100644 --- a/src/Cache/IVisitorCacheImplementation.php +++ b/src/Cache/IVisitorCacheImplementation.php @@ -2,12 +2,16 @@ namespace Flagship\Cache; + +/** + * @phpstan-import-type VisitorCacheArray from \Flagship\Model\Types + */ interface IVisitorCacheImplementation { /** * This method is called when the SDK needs to cache visitor information in your database. * @param string $visitorId - * @param array $data + * @param VisitorCacheArray $data * @return void */ public function cacheVisitor(string $visitorId, array $data): void; @@ -16,9 +20,9 @@ public function cacheVisitor(string $visitorId, array $data): void; * This method is called when the SDK needs to get the visitor * information corresponding to visitor ID from your database. * @param string $visitorId - * @return array + * @return VisitorCacheArray */ - public function lookupVisitor(string $visitorId): array; + public function lookupVisitor(string $visitorId): array|null; /** * This method is called when the SDK needs to erase the visitor diff --git a/src/Config/BucketingConfig.php b/src/Config/BucketingConfig.php index 8cbbd059..ac2a2543 100644 --- a/src/Config/BucketingConfig.php +++ b/src/Config/BucketingConfig.php @@ -44,7 +44,7 @@ public function getSyncAgentUrl(): string * @param string $syncAgentUrl * @return BucketingConfig */ - public function setSyncAgentUrl(string $syncAgentUrl): static + public function setSyncAgentUrl(string $syncAgentUrl): self { $this->syncAgentUrl = $syncAgentUrl; return $this; @@ -68,7 +68,7 @@ public function getFetchThirdPartyData(): bool * @param bool $fetchThirdPartyData * @return BucketingConfig */ - public function setFetchThirdPartyData(bool $fetchThirdPartyData): static + public function setFetchThirdPartyData(bool $fetchThirdPartyData): self { $this->fetchThirdPartyData = $fetchThirdPartyData; return $this; @@ -79,6 +79,9 @@ public function setFetchThirdPartyData(bool $fetchThirdPartyData): static */ public function jsonSerialize(): mixed { + /** + * @var array $parent + */ $parent = parent::jsonSerialize(); $parent[FlagshipField::FIELD_BUCKETING_URL] = $this->syncAgentUrl; return $parent; diff --git a/src/Config/FlagshipConfig.php b/src/Config/FlagshipConfig.php index 1242b85f..3143e6f2 100644 --- a/src/Config/FlagshipConfig.php +++ b/src/Config/FlagshipConfig.php @@ -38,9 +38,9 @@ abstract class FlagshipConfig implements JsonSerializable */ private DecisionMode $decisionMode = DecisionMode::DECISION_API; /** - * @var int + * @var float */ - private int $timeout = FlagshipConstant::REQUEST_TIME_OUT; + private float $timeout = FlagshipConstant::REQUEST_TIME_OUT; /** * @var ?LoggerInterface */ @@ -108,7 +108,7 @@ public function getEnvId(): ?string * @param string $envId environment id. * @return $this */ - public function setEnvId(string $envId): static + public function setEnvId(string $envId): self { $this->envId = $envId; return $this; @@ -128,7 +128,7 @@ public function getApiKey(): ?string * @param string $apiKey secure api key. * @return $this */ - public function setApiKey(string $apiKey): static + public function setApiKey(string $apiKey): self { $this->apiKey = $apiKey; return $this; @@ -148,16 +148,16 @@ public function getDecisionMode(): DecisionMode * @param DecisionMode $decisionMode decision mode value e.g DecisionMode::DECISION_API * @return $this */ - protected function setDecisionMode(DecisionMode $decisionMode): static + protected function setDecisionMode(DecisionMode $decisionMode): self { $this->decisionMode = $decisionMode; return $this; } /** - * @return int + * @return float */ - public function getTimeout(): int + public function getTimeout(): float { return $this->timeout * 1000; } @@ -168,7 +168,7 @@ public function getTimeout(): int * @param int $timeout Milliseconds for connect and read timeouts. Default is 2000ms. * @return $this */ - public function setTimeout(int $timeout): static + public function setTimeout(int $timeout): self { if ($timeout <= 0) { $this->logError($this, FlagshipConstant::TIMEOUT_TYPE_ERROR); @@ -192,7 +192,7 @@ public function getLogManager(): ?LoggerInterface * @param LoggerInterface $logManager custom implementation of LogManager. * @return $this */ - public function setLogManager(LoggerInterface $logManager): static + public function setLogManager(LoggerInterface $logManager): self { $this->logManager = $logManager; return $this; @@ -211,7 +211,7 @@ public function getLogLevel(): LogLevel * @param LogLevel $logLevel * @return $this */ - public function setLogLevel(LogLevel $logLevel): static + public function setLogLevel(LogLevel $logLevel): self { $this->logLevel = $logLevel; return $this; @@ -231,7 +231,7 @@ public function getCacheStrategy(): CacheStrategy * @param CacheStrategy $cacheStrategy * @return FlagshipConfig */ - public function setCacheStrategy(CacheStrategy $cacheStrategy): static + public function setCacheStrategy(CacheStrategy $cacheStrategy): self { $this->cacheStrategy = $cacheStrategy; return $this; @@ -250,7 +250,7 @@ public function getOnSdkStatusChanged(): ?callable * @param callable(FSSdkStatus $status): void $onSdkStatusChanged * @return $this */ - public function setOnSdkStatusChanged(callable $onSdkStatusChanged): static + public function setOnSdkStatusChanged(callable $onSdkStatusChanged): self { $this->onSdkStatusChanged = $onSdkStatusChanged; return $this; @@ -269,7 +269,7 @@ public function getVisitorCacheImplementation(): ?IVisitorCacheImplementation * @param IVisitorCacheImplementation $visitorCacheImplementation * @return FlagshipConfig */ - public function setVisitorCacheImplementation(IVisitorCacheImplementation $visitorCacheImplementation): static + public function setVisitorCacheImplementation(IVisitorCacheImplementation $visitorCacheImplementation): self { $this->visitorCacheImplementation = $visitorCacheImplementation; return $this; @@ -287,7 +287,7 @@ public function getHitCacheImplementation(): ?IHitCacheImplementation * @param ?IHitCacheImplementation $hitCacheImplementation * @return FlagshipConfig */ - public function setHitCacheImplementation(?IHitCacheImplementation $hitCacheImplementation): static + public function setHitCacheImplementation(?IHitCacheImplementation $hitCacheImplementation): self { $this->hitCacheImplementation = $hitCacheImplementation; return $this; @@ -305,7 +305,7 @@ public function getOnVisitorExposed(): ?callable * @param callable(ExposedVisitor $exposedUser, ExposedFlag $exposedFlag): void $onVisitorExposed * @return FlagshipConfig */ - public function setOnVisitorExposed(callable $onVisitorExposed): static + public function setOnVisitorExposed(callable $onVisitorExposed): self { $this->onVisitorExposed = $onVisitorExposed; return $this; @@ -323,7 +323,7 @@ public function disableDeveloperUsageTracking(): bool * @param bool $disableDeveloperUsageTracking * @return FlagshipConfig */ - public function setDisableDeveloperUsageTracking(bool $disableDeveloperUsageTracking): static + public function setDisableDeveloperUsageTracking(bool $disableDeveloperUsageTracking): self { $this->disableDeveloperUsageTracking = $disableDeveloperUsageTracking; return $this; diff --git a/src/Decision/ApiManager.php b/src/Decision/ApiManager.php index 50dc6255..5e7d7ab7 100644 --- a/src/Decision/ApiManager.php +++ b/src/Decision/ApiManager.php @@ -5,9 +5,10 @@ use DateTime; use Exception; use Flagship\Enum\LogLevel; +use Flagship\Model\CampaignDTO; use Flagship\Enum\FlagshipField; -use Flagship\Enum\FSFetchStatus; use Flagship\Enum\FSFetchReason; +use Flagship\Enum\FSFetchStatus; use Flagship\Hit\Troubleshooting; use Flagship\Enum\FlagshipConstant; use Flagship\Model\FetchFlagsStatus; @@ -23,20 +24,47 @@ class ApiManager extends DecisionManagerAbstract { /** + * @param array|null $body * @throws Exception */ protected function setTroubleshootingData(?array $body): void { $this->troubleshootingData = null; + if (empty($body) || !isset($body[FlagshipField::EXTRAS])) { + return; + } + + /** @var mixed[]|null $extras*/ + $extras = $body[FlagshipField::EXTRAS]; + + if (!is_array($extras) || empty($extras)) { + return; + } + + if (!isset($extras[FlagshipField::ACCOUNT_SETTINGS])) { + return; + } + $accountSettings = $extras[FlagshipField::ACCOUNT_SETTINGS]; + if (!is_array($accountSettings) || empty($accountSettings)) { + return; + } + + if ( - $body === null || !isset($body[FlagshipField::EXTRAS]) || - !isset($body[FlagshipField::EXTRAS][FlagshipField::ACCOUNT_SETTINGS]) || - !isset($body[FlagshipField::EXTRAS][FlagshipField::ACCOUNT_SETTINGS][FlagshipField::TROUBLESHOOTING]) + !isset($accountSettings[FlagshipField::TROUBLESHOOTING]) ) { return; } - $troubleshooting = $body[FlagshipField::EXTRAS][FlagshipField::ACCOUNT_SETTINGS] - [FlagshipField::TROUBLESHOOTING]; + + /** + * @var array{startDate: string, endDate: string, timezone: string, traffic: int}|null $troubleshooting + */ + $troubleshooting = $accountSettings[FlagshipField::TROUBLESHOOTING]; + + if (!is_array($troubleshooting)) { + return; + } + $startDate = new DateTime($troubleshooting[FlagshipField::START_DATE]); $endDate = new DateTime($troubleshooting[FlagshipField::END_DATE]); $troubleshootingData = new TroubleshootingData(); @@ -44,15 +72,18 @@ protected function setTroubleshootingData(?array $body): void $this->troubleshootingData = $troubleshootingData; } + /** + * @inheritDoc + */ public function getCampaigns(VisitorAbstract $visitor): array|null { $postData = [ - "visitorId" => $visitor->getVisitorId(), - "anonymousId" => $visitor->getAnonymousId(), - "trigger_hit" => false, - "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, - "visitor_consent" => $visitor->hasConsented(), - ]; + "visitorId" => $visitor->getVisitorId(), + "anonymousId" => $visitor->getAnonymousId(), + "trigger_hit" => false, + "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, + "visitor_consent" => $visitor->hasConsented(), + ]; $headers = $this->buildHeader($this->getConfig()->getApiKey()); $url = $this->buildDecisionApiUrl($this->getConfig()->getEnvId() . '/' . FlagshipConstant::URL_CAMPAIGNS . '?' . @@ -63,6 +94,8 @@ public function getCampaigns(VisitorAbstract $visitor): array|null $this->httpClient->setTimeout($this->getConfig()->getTimeout() / 1000); $response = $this->httpClient->post($url, [], $postData); + + /** @var array $body*/ $body = $response->getBody(); $hasPanicMode = !empty($body["panic"]); @@ -70,24 +103,33 @@ public function getCampaigns(VisitorAbstract $visitor): array|null $this->setTroubleshootingData($body); - return $body[FlagshipField::FIELD_CAMPAIGNS] ?? null; + /** + * @var array|null + */ + $campaigns = $body[FlagshipField::FIELD_CAMPAIGNS] ?? null; + + if (!is_array($campaigns)) { + return null; + } + + return array_map(CampaignDTO::fromArray(...), $campaigns); } catch (Exception $exception) { $visitor->setFetchStatus(new FetchFlagsStatus(FSFetchStatus::FETCH_REQUIRED, FSFetchReason::FETCH_ERROR)); $this->logError($this->getConfig(), $exception->getMessage(), [FlagshipConstant::TAG => __FUNCTION__]); $troubleshooting = new Troubleshooting(); - $troubleshooting->setLabel(TroubleshootingLabel::GET_CAMPAIGNS_ROUTE_RESPONSE_ERROR)->setHttpRequestBody($postData)->setHttpRequestHeaders($headers)->setHttpRequestMethod("POST")->setHttpRequestUrl($url)->setHttpResponseBody($exception->getMessage())->setHttpResponseTime($this->getNow() - $now)->setVisitorContext($visitor->getContext())->setLogLevel(LogLevel::ERROR)->setVisitorSessionId($visitor->getInstanceId())->setFlagshipInstanceId($visitor->getFlagshipInstanceId())->setTraffic(100)->setConfig($this->getConfig())->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId()); + $troubleshooting->setLabel(TroubleshootingLabel::GET_CAMPAIGNS_ROUTE_RESPONSE_ERROR) + ->setHttpRequestBody($postData) + ->setHttpRequestHeaders($headers) + ->setHttpRequestMethod("POST") + ->setHttpRequestUrl($url) + ->setHttpResponseBody($exception->getMessage()) + ->setHttpResponseTime($this->getNow() - $now) + ->setVisitorContext($visitor->getContext()) + ->setLogLevel(LogLevel::ERROR) + ->setVisitorSessionId($visitor->getInstanceId())->setFlagshipInstanceId($visitor->getFlagshipInstanceId())->setTraffic(100)->setConfig($this->getConfig())->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId()); $visitor->sendTroubleshootingHit($troubleshooting); return null; } } - - /** - * @inheritDoc - */ - public function getCampaignFlags(VisitorAbstract $visitor): array - { - $campaigns = $this->getCampaigns($visitor); - return $this->getFlagsData($campaigns); - } } diff --git a/src/Decision/BucketingManager.php b/src/Decision/BucketingManager.php index c5972b93..e5cd65c8 100644 --- a/src/Decision/BucketingManager.php +++ b/src/Decision/BucketingManager.php @@ -2,22 +2,30 @@ namespace Flagship\Decision; -use DateTime; use Exception; -use Flagship\Config\BucketingConfig; -use Flagship\Config\FlagshipConfig; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FlagshipField; -use Flagship\Enum\LogLevel; -use Flagship\Enum\TroubleshootingLabel; use Flagship\Hit\Segment; -use Flagship\Hit\Troubleshooting; -use Flagship\Model\TroubleshootingData; -use Flagship\Utils\HttpClientInterface; +use Flagship\Enum\LogLevel; use Flagship\Utils\MurmurHash; +use Flagship\Model\CampaignDTO; +use Flagship\Enum\FlagshipField; +use Flagship\Model\BucketingDTO; +use Flagship\Model\VariationDTO; +use Flagship\Hit\Troubleshooting; +use Flagship\Model\TargetingsDTO; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\BucketingConfig; +use Flagship\Enum\TargetingOperator; +use Flagship\Model\TargetingGroupDTO; +use Flagship\Model\VariationGroupDTO; use Flagship\Visitor\VisitorAbstract; -use Flagship\Visitor\StrategyAbstract; +use Flagship\Enum\TroubleshootingLabel; +use Flagship\Utils\HttpClientInterface; +use Flagship\Model\BucketingCampaignDTO; +use Flagship\Model\BucketingVariationDTO; +/** + * @phpstan-import-type FlagValue from \Flagship\Model\Types + */ class BucketingManager extends DecisionManagerAbstract { public const NB_MIN_CONTEXT_KEYS = 4; @@ -32,11 +40,6 @@ class BucketingManager extends DecisionManagerAbstract */ private MurmurHash $murmurHash; - /** - * @var BucketingConfig - */ - protected FlagshipConfig $config; - /** * @var Troubleshooting */ @@ -45,21 +48,12 @@ class BucketingManager extends DecisionManagerAbstract /** * @return BucketingConfig */ - public function getConfig(): FlagshipConfig + public function getConfig(): BucketingConfig { + /** @var BucketingConfig */ return $this->config; } - /** - * @param FlagshipConfig $config - * @return BucketingManager - */ - public function setConfig(FlagshipConfig $config): static - { - $this->config = $config; - return $this; - } - /** * @param HttpClientInterface $httpClient * @param BucketingConfig $config @@ -89,7 +83,7 @@ protected function sendContext(VisitorAbstract $visitor): void /** * @param string $visitorId - * @return array + * @return array */ protected function getThirdPartySegment(string $visitorId): array { @@ -98,11 +92,15 @@ protected function getThirdPartySegment(string $visitorId): array $context = []; try { $response = $this->httpClient->get($url); + /** + * @var array> + */ $content = $response->getBody(); foreach ($content as $item) { $key = $item[self::PARTNER] . "::" . $item[self::SEGMENT]; $context[$key] = $item[self::VALUE]; } + $this->logDebugSprintf( $this->config, self::GET_THIRD_PARTY_SEGMENT, @@ -142,7 +140,7 @@ protected function getThirdPartySegment(string $visitorId): array } /** - * @return mixed|null + * @return BucketingDTO|null */ protected function getBucketingFile(): mixed { @@ -166,10 +164,15 @@ protected function getBucketingFile(): mixed ->setHttpResponseHeaders($response->getHeaders()) ->setHttpResponseCode($response->getStatusCode()) ->setHttpResponseTime($this->getNow() - $now) - ->setVisitorId($this->getFlagshipInstanceId()) + ->setVisitorId($this->getFlagshipInstanceId() ?? '') ->setConfig($this->getConfig()); $this->troubleshootingHit = $troubleshooting; - return $response->getBody(); + /** @var array|null */ + $body = $response->getBody(); + if (!is_array($body)) { + return null; + } + return BucketingDTO::fromArray($body); } catch (Exception $exception) { $this->logError($this->getConfig(), $exception->getMessage(), [FlagshipConstant::TAG => __FUNCTION__]); $troubleshooting = new Troubleshooting(); @@ -182,7 +185,7 @@ protected function getBucketingFile(): mixed ->setTraffic(0) ->setLogLevel(LogLevel::ERROR) ->setConfig($this->getConfig()) - ->setVisitorId($this->getFlagshipInstanceId()); + ->setVisitorId($this->getFlagshipInstanceId() ?? ""); $this->troubleshootingHit = $troubleshooting; } return null; @@ -198,45 +201,33 @@ public function getCampaigns(VisitorAbstract $visitor): array|null if (!$bucketingCampaigns) { return []; } + $this->troubleshootingData = null; - if (isset($bucketingCampaigns[FlagshipField::ACCOUNT_SETTINGS][FlagshipField::TROUBLESHOOTING])) { - $troubleshooting = $bucketingCampaigns[FlagshipField::ACCOUNT_SETTINGS][FlagshipField::TROUBLESHOOTING]; - $troubleshootingData = new TroubleshootingData(); + $troubleshooting = $bucketingCampaigns->getAccountSettings()?->getTroubleshooting(); - if (isset($troubleshooting[FlagshipField::START_DATE])) { - $startDate = new DateTime($troubleshooting[FlagshipField::START_DATE]); - $troubleshootingData->setStartDate($startDate); - } - if (isset($troubleshooting[FlagshipField::END_DATE])) { - $endDate = new DateTime($troubleshooting[FlagshipField::END_DATE]); - $troubleshootingData->setEndDate($endDate); - } - if (isset($troubleshooting[FlagshipField::TRAFFIC])) { - $troubleshootingData->setTraffic($troubleshooting[FlagshipField::TRAFFIC]); - } - if (isset($troubleshooting[FlagshipField::TIMEZONE])) { - $troubleshootingData->setTimezone($troubleshooting[FlagshipField::TIMEZONE]); - } + if ($troubleshooting) { + $troubleshootingData = $troubleshooting->toTroubleshootingData(); $this->troubleshootingData = $troubleshootingData; - $this->getTrackingManager()->setTroubleshootingData($troubleshootingData); - $this->getTrackingManager()->addTroubleshootingHit($this->troubleshootingHit); + if ($troubleshootingData) { + $this->getTrackingManager()->setTroubleshootingData($troubleshootingData); + $this->getTrackingManager()->addTroubleshootingHit($this->troubleshootingHit); + } } - if (isset($bucketingCampaigns[FlagshipField::FIELD_PANIC])) { - $hasPanicMode = !empty($bucketingCampaigns[FlagshipField::FIELD_PANIC]); - $this->setIsPanicMode($hasPanicMode); + if ($bucketingCampaigns->getPanic() !== null) { + $this->setIsPanicMode($bucketingCampaigns->getPanic()); return []; } $this->setIsPanicMode(false); - if (!isset($bucketingCampaigns[FlagshipField::FIELD_CAMPAIGNS])) { + $campaigns = $bucketingCampaigns->getCampaigns(); + + if (empty($campaigns)) { return []; } - $campaigns = $bucketingCampaigns[FlagshipField::FIELD_CAMPAIGNS]; - $visitorCampaigns = []; if ($this->getConfig()->getFetchThirdPartyData()) { @@ -247,300 +238,276 @@ public function getCampaigns(VisitorAbstract $visitor): array|null $this->sendContext($visitor); foreach ($campaigns as $campaign) { - if (!isset($campaign[FlagshipField::FIELD_VARIATION_GROUPS])) { - continue; - } - $variationGroups = $campaign[FlagshipField::FIELD_VARIATION_GROUPS]; $currentCampaigns = $this->getVisitorCampaigns( - $variationGroups, - $campaign[FlagshipField::FIELD_ID], $visitor, - $campaign[FlagshipField::FIELD_CAMPAIGN_TYPE], - $campaign[FlagshipField::FIELD_SLUG] ?? null, - $campaign[FlagshipField::FIELD_NANE] ?? null + $campaign ); - $visitorCampaigns = array_merge($visitorCampaigns, $currentCampaigns); + if ($currentCampaigns !== null) { + $visitorCampaigns[] = $currentCampaigns; + } } return $visitorCampaigns; } /** - * @param array $variationGroups - * @param string $campaignId * @param VisitorAbstract $visitor - * @param string $campaignType - * @param ?string $slug - * @param ?string $campaignName - * @return array + * @param BucketingCampaignDTO $campaign + * @return CampaignDTO|null */ private function getVisitorCampaigns( - array $variationGroups, - string $campaignId, VisitorAbstract $visitor, - string $campaignType, - ?string $slug, - ?string $campaignName - ): array { - $visitorCampaigns = []; + BucketingCampaignDTO $campaign + ): CampaignDTO|null { + $variationGroups = $campaign->getVariationGroups(); + $campaignId = $campaign->getId(); + $campaignType = $campaign->getType(); foreach ($variationGroups as $variationGroup) { - if ($this->isMatchTargeting($variationGroup, $visitor)) { - $variations = $this->getVariation( + if ($this->checkVisitorMatchesTargeting($variationGroup, $visitor)) { + $variation = $this->getVariation( $variationGroup, $visitor ); - $visitorCampaigns[] = [ - FlagshipField::FIELD_ID => $campaignId, - FlagshipField::FIELD_NANE => $campaignName, - FlagshipField::FIELD_SLUG => $slug, - FlagshipField::FIELD_VARIATION_GROUP_ID => $variationGroup[FlagshipField::FIELD_ID], - FlagshipField::FIELD_VARIATION_GROUP_NAME => $variationGroup[FlagshipField::FIELD_NANE] ?? null, - FlagshipField::FIELD_VARIATION => $variations, - FlagshipField::FIELD_CAMPAIGN_TYPE => $campaignType, - ]; - break; + if (empty($variation)) { + return null; + } + $newCampaign = new CampaignDTO( + $campaignId, + $variationGroup->getId(), + $variation + ); + $newCampaign->setName($campaign->getName()); + $newCampaign->setSlug($campaign->getSlug()); + $newCampaign->setType($campaignType); + $newCampaign->setVariationGroupName( + $variationGroup->getName() + ); + return $newCampaign; } } - return $visitorCampaigns; + return null; } /** * @param string $variationGroupId * @param VisitorAbstract $visitor - * @return mixed|null + * @return string|null */ - private function getVisitorAssignmentsHistory(string $variationGroupId, VisitorAbstract $visitor): mixed + private function getVisitorAssignmentsHistory(string $variationGroupId, VisitorAbstract $visitor): string|null { - return $visitor->visitorCache[StrategyAbstract::DATA][StrategyAbstract::ASSIGNMENTS_HISTORY][$variationGroupId] ?? null; + $assignmentsHistory = $visitor->visitorCache?->getData()->getAssignmentsHistory(); + return $assignmentsHistory[$variationGroupId] ?? null; } - private function findVariationById(array $variations, $key) + /** + * + * @param BucketingVariationDTO[] $variations + * @param string $assignmentsVariationId + */ + private function findVariationById(array $variations, string $assignmentsVariationId): BucketingVariationDTO|null { - foreach ($variations as $item) { - if ($item[FlagshipField::FIELD_ID] === $key) { - return $item; - } - } - return null; + return $this->array_find( + $variations, + fn(BucketingVariationDTO $variation) => $variation->getId() === $assignmentsVariationId + ); } /** * - * @param array $variationGroup + * @param VariationGroupDTO $variationGroup * @param VisitorAbstract $visitor - * @return array + * @return VariationDTO|null */ - private function getVariation(array $variationGroup, VisitorAbstract $visitor): array + private function getVariation(VariationGroupDTO $variationGroup, VisitorAbstract $visitor): VariationDTO|null { $visitorVariation = []; - if (!isset($variationGroup[FlagshipField::FIELD_ID])) { - return $visitorVariation; + if (empty($variationGroup->getId())) { + return null; } - $groupVariationId = $variationGroup[FlagshipField::FIELD_ID]; + + $groupVariationId = $variationGroup->getId(); $hash = $this->murmurHash->murmurHash3Int32($groupVariationId . $visitor->getVisitorId()); $hashAllocation = $hash % 100; - $variations = $variationGroup[FlagshipField::FIELD_VARIATIONS]; + $variations = $variationGroup->getVariations(); $totalAllocation = 0; foreach ($variations as $variation) { - if (!isset($variation[FlagshipField::FIELD_ALLOCATION])) { + if ($variation->getAllocation() === null) { continue; } $assignmentsVariationId = $this->getVisitorAssignmentsHistory($groupVariationId, $visitor); if ($assignmentsVariationId) { - $newVariation = $this->findVariationById($variations, $assignmentsVariationId); - if (!$newVariation) { + $assignedVariation = $this->findVariationById($variations, $assignmentsVariationId); + if (!$assignedVariation) { continue; } - $visitorVariation = [ - FlagshipField::FIELD_ID => $newVariation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_MODIFICATIONS => $newVariation[FlagshipField::FIELD_MODIFICATIONS], - FlagshipField::FIELD_REFERENCE => !empty($newVariation[FlagshipField::FIELD_REFERENCE]), - FlagshipField::FIELD_NANE => $newVariation[FlagshipField::FIELD_NANE] ?? null, - ]; - break; + $visitorVariation = new VariationDTO( + $assignedVariation->getId(), + $variation->getModifications() + ); + $visitorVariation->setReference($assignedVariation->getReference()); + $visitorVariation->setName($assignedVariation->getName()); + return $visitorVariation; } - if (!isset($variation[FlagshipField::FIELD_ALLOCATION]) || $variation[FlagshipField::FIELD_ALLOCATION] <= 0) { + if ($variation->getAllocation() <= 0) { continue; } - $totalAllocation += $variation[FlagshipField::FIELD_ALLOCATION]; + $totalAllocation += $variation->getAllocation(); if ($hashAllocation < $totalAllocation) { - $visitorVariation = [ - FlagshipField::FIELD_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_MODIFICATIONS => $variation[FlagshipField::FIELD_MODIFICATIONS], - FlagshipField::FIELD_REFERENCE => !empty($variation[FlagshipField::FIELD_REFERENCE]), - FlagshipField::FIELD_NANE => $variation[FlagshipField::FIELD_NANE] ?? null, - ]; - break; + $visitorVariation = new VariationDTO( + $variation->getId(), + $variation->getModifications() + ); + $visitorVariation->setReference($variation->getReference()); + $visitorVariation->setName($variation->getName()); + + return $visitorVariation; } } - return $visitorVariation; + return null; } /** - * @param array $variationGroup + * @param VariationGroupDTO $variationGroup * @param VisitorAbstract $visitor * @return bool */ - private function isMatchTargeting(array $variationGroup, VisitorAbstract $visitor): bool + private function checkVisitorMatchesTargeting(VariationGroupDTO $variationGroup, VisitorAbstract $visitor): bool { - if (!isset($variationGroup[FlagshipField::FIELD_TARGETING])) { + $targetingGroups = $variationGroup->getTargeting()->getTargetingGroups(); + if (empty($targetingGroups)) { return false; } - $targeting = $variationGroup[FlagshipField::FIELD_TARGETING]; + // OR logic: at least one targeting group must match + return $this->array_any( + $targetingGroups, + fn(TargetingGroupDTO $targetingGroup) + => $this->checkAllTargetingRulesMatch( + $targetingGroup->getTargetings(), + $visitor + ) + ); + } - if (!isset($targeting[FlagshipField::FIELD_TARGETING_GROUPS])) { + /** + * + * @param TargetingsDTO[] $targetings + * @return bool + */ + private function checkAllTargetingRulesMatch(array $targetings, VisitorAbstract $visitor): bool + { + if (empty($targetings)) { return false; } - $targetingGroups = $targeting[FlagshipField::FIELD_TARGETING_GROUPS]; - - foreach ($targetingGroups as $targetingGroup) { - if (!isset($targetingGroup[FlagshipField::FIELD_TARGETINGS])) { - continue; - } - - $innerTargetings = $targetingGroup[FlagshipField::FIELD_TARGETINGS]; - - if ($this->checkAndTargeting($innerTargetings, $visitor)) { - return true; - } - } - - return false; + // AND logic: ALL targeting rules must match + return $this->array_all( + $targetings, + fn(TargetingsDTO $targeting) + => $this->matchesTargetingCriteria( + $targeting, + $visitor + ) + ); } - /** - * @param array $innerTargetings - * @param VisitorAbstract $visitor - * @return bool - */ - private function checkAndTargeting(array $innerTargetings, VisitorAbstract $visitor): bool + + private function matchesArrayTargeting(TargetingsDTO $targeting, VisitorAbstract $visitor): bool { - $isMatching = false; - foreach ($innerTargetings as $innerTargeting) { - $key = $innerTargeting['key']; - $operator = $innerTargeting["operator"]; - $targetingValue = $innerTargeting["value"]; - $visitorContext = $visitor->getContext(); - - if ($operator === "EXISTS") { - if (array_key_exists($key, $visitorContext)) { - $isMatching = true; - continue; - } - $isMatching = false; - break; - } - if ($operator === "NOT_EXISTS") { - if (array_key_exists($key, $visitorContext)) { - $isMatching = false; - break; - } - $isMatching = true; - continue; - } + /** + * @var array + */ + $targetingValue = $targeting->getValue(); + + $notOperator = in_array( + $targeting->getOperator()->value, + [ + TargetingOperator::NOT_CONTAINS->value, + TargetingOperator::NOT_EQUALS->value, + ] + ); + + $cloneTargeting = clone $targeting; + + /** + * + * @param FlagValue $value + * @return void + */ + $checkOperator = function (mixed $value) use ($cloneTargeting, $visitor): bool { + $cloneTargeting->setValue($value); + return $this->matchesTargetingCriteria($cloneTargeting, $visitor); + }; - switch ($key) { - case "fs_all_users": - $isMatching = true; - continue 2; - case "fs_users": - $contextValue = $visitor->getVisitorId(); - break; - default: - if (!isset($visitorContext[$key])) { - $isMatching = false; - break 2; - } - $contextValue = $visitorContext[$key]; - break; - } - $isMatching = $this->testOperator($operator, $contextValue, $targetingValue); - if (!$isMatching) { - break; - } + if ($notOperator) { + return $this->array_all( + $targetingValue, + $checkOperator(...) + ); } - return $isMatching; + return $this->array_any( + $targetingValue, + $checkOperator(...) + ); } - /** - * @param $operator - * @return bool - */ - private function isANDListOperator($operator): bool + private function matchesTargetingCriteria(TargetingsDTO $targeting, VisitorAbstract $visitor): bool { - return in_array($operator, ['NOT_EQUALS', 'NOT_CONTAINS']); - } + if ($targeting->getKey() === FlagshipField::FS_ALL_USERS) { + return true; + } - /** - * @param string $operator - * @param mixed $contextValue - * @param array $targetingValue - * @param bool $initialCheck - * @return bool|mixed - */ - private function testListOperatorLoop( - string $operator, - mixed $contextValue, - array $targetingValue, - bool $initialCheck - ): mixed { - $check = $initialCheck; - foreach ($targetingValue as $value) { - $check = $this->testOperator($operator, $contextValue, $value); - if ($check !== $initialCheck) { - break; - } + if (is_array($targeting->getValue())) { + return $this->matchesArrayTargeting($targeting, $visitor); } - return $check; - } - /** - * @param string $operator - * @param mixed $contextValue - * @param array $targetingValue - * @return bool - */ - private function testListOperator(string $operator, mixed $contextValue, array $targetingValue): bool - { - $andOperator = $this->isANDListOperator($operator); - if ($andOperator) { - $check = $this->testListOperatorLoop($operator, $contextValue, $targetingValue, true); - } else { - $check = $this->testListOperatorLoop($operator, $contextValue, $targetingValue, false); + $visitorValue = $targeting->getKey() === FlagshipField::FS_USERS + ? $visitor->getVisitorId() + : $visitor->getContext()[$targeting->getKey()] ?? null; + + if ($visitorValue === null) { + return $targeting->getOperator() === TargetingOperator::NOT_EXISTS; } - return $check; + + return $this->evaluateOperator( + $targeting->getOperator(), + $visitorValue, + $targeting->getValue() + ); } /** - * @param string $operator - * @param mixed $contextValue + * + * @param TargetingOperator $operator + * @param scalar $visitorValue * @param mixed $targetingValue * @return bool */ - private function testOperator(string $operator, mixed $contextValue, mixed $targetingValue): bool - { + private function evaluateOperator( + TargetingOperator $operator, + float|int|string|bool|null $visitorValue, + mixed $targetingValue + ): bool { + + $targetingValueStr = is_scalar($targetingValue) ? strval($targetingValue) : ''; - if (is_array($targetingValue)) { - return $this->testListOperator($operator, $contextValue, $targetingValue); - } return match ($operator) { - "EQUALS" => $contextValue === $targetingValue, - "NOT_EQUALS" => $contextValue !== $targetingValue, - "CONTAINS" => str_contains(strval($contextValue), strval($targetingValue)), - "NOT_CONTAINS" => !str_contains(strval($contextValue), strval($targetingValue)), - "GREATER_THAN" => $contextValue > $targetingValue, - "LOWER_THAN" => $contextValue < $targetingValue, - "GREATER_THAN_OR_EQUALS" => $contextValue >= $targetingValue, - "LOWER_THAN_OR_EQUALS" => $contextValue <= $targetingValue, - "STARTS_WITH" => (bool)preg_match("/^$targetingValue/i", $contextValue), - "ENDS_WITH" => (bool)preg_match("/$targetingValue$/i", $contextValue), + TargetingOperator::EQUALS => $visitorValue === $targetingValue, + TargetingOperator::NOT_EQUALS => $visitorValue !== $targetingValue, + TargetingOperator::EXISTS => $visitorValue !== null, + TargetingOperator::CONTAINS => str_contains(strval($visitorValue), $targetingValueStr), + TargetingOperator::NOT_CONTAINS => !str_contains(strval($visitorValue), $targetingValueStr), + TargetingOperator::GREATER_THAN => $visitorValue > $targetingValue, + TargetingOperator::LOWER_THAN => $visitorValue < $targetingValue, + TargetingOperator::GREATER_THAN_OR_EQUALS => $visitorValue >= $targetingValue, + TargetingOperator::LOWER_THAN_OR_EQUALS => $visitorValue <= $targetingValue, + TargetingOperator::STARTS_WITH => (bool)preg_match("/^" . preg_quote($targetingValueStr, '/') . "/i", strval($visitorValue)), + TargetingOperator::ENDS_WITH => (bool)preg_match("/" . preg_quote($targetingValueStr, '/') . "$/i", strval($visitorValue)), default => false, }; } diff --git a/src/Decision/DecisionManagerAbstract.php b/src/Decision/DecisionManagerAbstract.php index 4fcc82d6..697e41d5 100644 --- a/src/Decision/DecisionManagerAbstract.php +++ b/src/Decision/DecisionManagerAbstract.php @@ -6,6 +6,7 @@ use Flagship\Config\FlagshipConfig; use Flagship\Enum\FlagshipField; use Flagship\Enum\FSSdkStatus; +use Flagship\Model\CampaignDTO; use Flagship\Model\FlagDTO; use Flagship\Model\TroubleshootingData; use Flagship\Traits\BuildApiTrait; @@ -29,9 +30,9 @@ abstract class DecisionManagerAbstract implements DecisionManagerInterface */ protected HttpClientInterface $httpClient; /** - * @var callable + * @var ?callable */ - private $statusChangedCallback; + private $statusChangedCallback = null; /** * @var FlagshipConfig @@ -76,7 +77,7 @@ public function getTrackingManager(): TrackingManagerInterface * @param TrackingManagerInterface $trackingManager * @return DecisionManagerAbstract */ - public function setTrackingManager(TrackingManagerInterface $trackingManager): static + public function setTrackingManager(TrackingManagerInterface $trackingManager): self { $this->trackingManager = $trackingManager; return $this; @@ -94,7 +95,7 @@ public function getFlagshipInstanceId(): ?string * @param ?string $flagshipInstanceId * @return DecisionManagerAbstract */ - public function setFlagshipInstanceId(?string $flagshipInstanceId): static + public function setFlagshipInstanceId(?string $flagshipInstanceId): self { $this->flagshipInstanceId = $flagshipInstanceId; return $this; @@ -119,7 +120,7 @@ public function getIsPanicMode(): bool * @param bool $isPanicMode * @return DecisionManagerAbstract */ - public function setIsPanicMode(bool $isPanicMode): static + public function setIsPanicMode(bool $isPanicMode): self { $status = $isPanicMode ? FSSdkStatus::SDK_PANIC : FSSdkStatus::SDK_INITIALIZED; $this->updateFlagshipStatus($status); @@ -133,11 +134,9 @@ public function setIsPanicMode(bool $isPanicMode): static * @param callable $statusChangedCallback callback * @return DecisionManagerAbstract */ - public function setStatusChangedCallback(callable $statusChangedCallback): static + public function setStatusChangedCallback(callable $statusChangedCallback): self { - if (is_callable($statusChangedCallback)) { - $this->statusChangedCallback = $statusChangedCallback; - } + $this->statusChangedCallback = $statusChangedCallback; return $this; } @@ -153,7 +152,7 @@ public function getConfig(): FlagshipConfig * @param FlagshipConfig $config * @return DecisionManagerAbstract */ - public function setConfig(FlagshipConfig $config): static + public function setConfig(FlagshipConfig $config): self { $this->config = $config; return $this; @@ -171,129 +170,94 @@ protected function updateFlagshipStatus(FSSdkStatus $newStatus): void } } - /** - * @param FlagDTO[] $flags - * @param string $key - * @return FlagDTO|null - */ - protected function checkFlagKeyExist(array $flags, string $key): ?FlagDTO - { - foreach ($flags as $flag) { - if ($flag->getKey() === $key) { - return $flag; - } - } - return null; - } + /** * Return an array of flags from all campaigns * - * @param $campaigns + * @param CampaignDTO[] $campaigns * @return FlagDTO[] Return an array of flags */ - public function getFlagsData($campaigns): array + public function getFlagsData(array $campaigns): array { + /** @var array $existingFlags*/ + $existingFlags = []; - $flags = []; foreach ($campaigns as $campaign) { - if ( - !isset($campaign[FlagshipField::FIELD_VARIATION]) - || !isset($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_MODIFICATIONS]) - || !isset($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_MODIFICATIONS] - [FlagshipField::FIELD_VALUE]) - ) { + + if (empty($campaign->getVariationGroupId())) { continue; } - $flagsValue = $campaign[FlagshipField::FIELD_VARIATION] - [FlagshipField::FIELD_MODIFICATIONS][FlagshipField::FIELD_VALUE]; - - $flags = $this->getFlagsValue($flagsValue, $campaign, $flags); + $existingFlags = $this->extractFlagsFromCampaign($campaign, $existingFlags); } - return $flags; + return array_values($existingFlags); } /** * Return modification of a campaign * - * @param array $flagsValue - * @param array $campaign - * @param array $flagsDTO - * @return array + * @param CampaignDTO $campaign + * @param array $existingFlags + * @return array */ - protected function getFlagsValue(array $flagsValue, array $campaign, array $flagsDTO): array + protected function extractFlagsFromCampaign(CampaignDTO $campaign, array $existingFlags): array { - $localFlags = []; + $flagsValue = $campaign->getVariation()->getModifications()->getValue(); + foreach ($flagsValue as $key => $flagValue) { - if (!$this->isKeyValid($key)) { + if (!is_string($key) || empty($key)) { continue; } - //check if the key is already used - $flagDTO = $this->checkFlagKeyExist($flagsDTO, $key); - $isKeyUsed = true; - - if (is_null($flagDTO)) { - $flagDTO = new FlagDTO(); - $isKeyUsed = false; - } + $flagDTO = $existingFlags[$key] ?? new FlagDTO(); $flagDTO->setKey($key); $flagDTO->setValue($flagValue); - if (isset($campaign[FlagshipField::FIELD_ID])) { - $flagDTO->setCampaignId($campaign[FlagshipField::FIELD_ID]); - } + $flagDTO->setCampaignId($campaign->getId()); - if (isset($campaign[FlagshipField::FIELD_CAMPAIGNS_NAME])) { - $flagDTO->setCampaignName($campaign[FlagshipField::FIELD_CAMPAIGNS_NAME]); + if (!empty($campaign->getName())) { + $flagDTO->setCampaignName($campaign->getName()); } - if (isset($campaign[FlagshipField::FIELD_CAMPAIGN_TYPE])) { - $flagDTO->setCampaignType($campaign[FlagshipField::FIELD_CAMPAIGN_TYPE]); + if (!empty($campaign->getType())) { + $flagDTO->setCampaignType($campaign->getType()); } - if (isset($campaign[FlagshipField::FIELD_VARIATION_GROUP_ID])) { - $flagDTO->setVariationGroupId($campaign[FlagshipField::FIELD_VARIATION_GROUP_ID]); + if (!empty($campaign->getVariationGroupId())) { + $flagDTO->setVariationGroupId($campaign->getVariationGroupId()); } - if (isset($campaign[FlagshipField::FIELD_VARIATION_GROUP_NAME])) { - $flagDTO->setVariationGroupName($campaign[FlagshipField::FIELD_VARIATION_GROUP_NAME]); + + if (!empty($campaign->getVariationGroupName())) { + $flagDTO->setVariationGroupName($campaign->getVariationGroupName()); } - if (isset($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_ID])) { + if (!empty($campaign->getVariation()->getId())) { $flagDTO->setVariationId( - $campaign[FlagshipField::FIELD_VARIATION] - [FlagshipField::FIELD_ID] + $campaign->getVariation()->getId() ); } - if (isset($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_NANE])) { - $flagDTO->setVariationName($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_NANE]); - } - - if (isset($campaign[FlagshipField::FIELD_VARIATION][FlagshipField::FIELD_REFERENCE])) { - $flagDTO->setIsReference( - $campaign[FlagshipField::FIELD_VARIATION] - [FlagshipField::FIELD_REFERENCE] - ); + if (!empty($campaign->getVariation()->getName())) { + $flagDTO->setVariationName($campaign->getVariation()->getName()); } - if (isset($campaign[FlagshipField::FIELD_SLUG])) { - $flagDTO->setSlug($campaign[FlagshipField::FIELD_SLUG]); - } + $flagDTO->setIsReference( + $campaign->getVariation()->getReference() ?? false + ); - if (!$isKeyUsed) { - $localFlags[] = $flagDTO; + if (!empty($campaign->getSlug())) { + $flagDTO->setSlug($campaign->getSlug()); } + $existingFlags[$key] = $flagDTO; } - return array_merge($flagsDTO, $localFlags); + return $existingFlags; } /** - * @param VisitorAbstract $visitor - * @return array|null + * @inheritDoc */ abstract public function getCampaigns(VisitorAbstract $visitor): array|null; @@ -303,6 +267,9 @@ abstract public function getCampaigns(VisitorAbstract $visitor): array|null; public function getCampaignFlags(VisitorAbstract $visitor): array { $campaigns = $this->getCampaigns($visitor); + if (is_null($campaigns) || empty($campaigns)) { + return []; + } return $this->getFlagsData($campaigns); } diff --git a/src/Decision/DecisionManagerInterface.php b/src/Decision/DecisionManagerInterface.php index e280d505..c376375a 100644 --- a/src/Decision/DecisionManagerInterface.php +++ b/src/Decision/DecisionManagerInterface.php @@ -3,8 +3,9 @@ namespace Flagship\Decision; use Flagship\Model\FlagDTO; -use Flagship\Model\TroubleshootingData; +use Flagship\Model\CampaignDTO; use Flagship\Visitor\VisitorAbstract; +use Flagship\Model\TroubleshootingData; interface DecisionManagerInterface { @@ -18,15 +19,15 @@ public function getCampaignFlags(VisitorAbstract $visitor): array; /** * @param VisitorAbstract $visitor - * @return array|null + * @return CampaignDTO[]|null */ public function getCampaigns(VisitorAbstract $visitor): array|null; /** - * @param $campaigns + * @param CampaignDTO[] $campaigns * @return FlagDTO[] */ - public function getFlagsData($campaigns): array; + public function getFlagsData(array $campaigns): array; public function getTroubleshootingData(): ?TroubleshootingData; } diff --git a/src/Decision/Types.php b/src/Decision/Types.php new file mode 100644 index 00000000..05a31624 --- /dev/null +++ b/src/Decision/Types.php @@ -0,0 +1,15 @@ + */ private static array $predefinedContext = [ - self::DEVICE_LOCALE => "string", - self::DEVICE_TYPE => "string", - self::DEVICE_MODEL => "string", - self::LOCATION_CITY => "string", - self::LOCATION_REGION => "string", - self::LOCATION_COUNTRY => "string", - self::LOCATION_LAT => "float", - self::LOCATION_LONG => "float", - self::IP => "string", - self::OS_NAME => "string", - self::OS_VERSION_NAME => "string", - self::OS_VERSION_CODE => "float", - self::CARRIER_NAME => "string", - self::INTERNET_CONNECTION => "string", - self::APP_VERSION_NAME => "string", - self::APP_VERSION_CODE => "float", - self::INTERFACE_NAME => "string", - self::FLAGSHIP_CLIENT => "string", - self::FLAGSHIP_VERSION => "string", - self::FLAGSHIP_VISITOR => "string", - ]; + self::DEVICE_LOCALE => "string", + self::DEVICE_TYPE => "string", + self::DEVICE_MODEL => "string", + self::LOCATION_CITY => "string", + self::LOCATION_REGION => "string", + self::LOCATION_COUNTRY => "string", + self::LOCATION_LAT => "float", + self::LOCATION_LONG => "float", + self::IP => "string", + self::OS_NAME => "string", + self::OS_VERSION_NAME => "string", + self::OS_VERSION_CODE => "float", + self::CARRIER_NAME => "string", + self::INTERNET_CONNECTION => "string", + self::APP_VERSION_NAME => "string", + self::APP_VERSION_CODE => "float", + self::INTERFACE_NAME => "string", + self::FLAGSHIP_CLIENT => "string", + self::FLAGSHIP_VERSION => "string", + self::FLAGSHIP_VISITOR => "string", + ]; /** * @param $context string diff --git a/src/Enum/FlagshipField.php b/src/Enum/FlagshipField.php index 426a4a0e..5a80f120 100644 --- a/src/Enum/FlagshipField.php +++ b/src/Enum/FlagshipField.php @@ -48,4 +48,17 @@ class FlagshipField public const TRAFFIC = "traffic"; public const FIELD_CAMPAIGN_NAME = "campaignName"; public const FIELD_VARIATION_NAME = "variationName"; + + public const FIELD_ACTIVATED = "activated"; + + public const FIELD_FLAGS = "flags"; + + public const FIELD_OPERATOR = 'operator'; + + public const FS_ALL_USERS = 'fs_all_users'; + public const FS_USERS = 'fs_users'; + + public const ENABLED_XPC = 'enabledXPC'; + public const EAI_COLLECT_ENABLED = 'eaiCollectEnabled'; + public const EAI_ACTIVATION_ENABLED = 'eaiActivationEnabled'; } diff --git a/src/Enum/TargetingOperator.php b/src/Enum/TargetingOperator.php new file mode 100644 index 00000000..ef6e9702 --- /dev/null +++ b/src/Enum/TargetingOperator.php @@ -0,0 +1,22 @@ +|bool|float|int|string|null $defaultValue */ private string|array|bool|int|null|float $defaultValue; @@ -36,7 +42,7 @@ class FSFlag implements FSFlagInterface */ public function __construct( string $key, - VisitorAbstract $visitorDelegate = null + ?VisitorAbstract $visitorDelegate = null ) { $this->key = $key; $this->visitorDelegate = $visitorDelegate; @@ -65,6 +71,9 @@ protected function findFlagDTO(string $key): ?FlagDTO /** * @inheritDoc + * @phpstan-template T of scalar|array|null + * @param T $defaultValue + * @return T */ public function getValue( float|array|bool|int|string|null $defaultValue, diff --git a/src/Flag/FSFlagCollection.php b/src/Flag/FSFlagCollection.php index eda75305..3ccb77bb 100644 --- a/src/Flag/FSFlagCollection.php +++ b/src/Flag/FSFlagCollection.php @@ -27,13 +27,13 @@ class FSFlagCollection implements FSFlagCollectionInterface */ private array $flags; - private int $index = 0; + private int $index = 0; /** * @param VisitorAbstract|null $visitor * @param array $flags */ - public function __construct(VisitorAbstract $visitor = null, array $flags = []) + public function __construct(?VisitorAbstract $visitor = null, array $flags = []) { $this->visitor = $visitor; $this->flags = $flags; @@ -56,17 +56,20 @@ public function getSize(): int return count($this->keys); } + /** + * @inheritDoc + */ public function get(string $key): FSFlagInterface { if (!isset($this->flags[$key])) { $this->logWarningSprintf( - $this->visitor->getConfig(), + $this->visitor?->getConfig(), FlagshipConstant::GET_FLAG, FlagshipConstant::GET_FLAG_NOT_FOUND, [ - $this->visitor->getVisitorId(), - $key, - ] + $this->visitor?->getVisitorId(), + $key, + ] ); return new FSFlag($key); } @@ -124,20 +127,21 @@ public function toJSON(): string foreach ($this->flags as $key => $flag) { $metadata = $flag->getMetadata(); $serializedData[] = [ - 'key' => $key, - 'campaignId' => $metadata->getCampaignId(), - 'campaignName' => $metadata->getCampaignName(), - 'variationGroupId' => $metadata->getVariationGroupId(), - 'variationGroupName' => $metadata->getVariationGroupName(), - 'variationId' => $metadata->getVariationId(), - 'variationName' => $metadata->getVariationName(), - 'isReference' => $metadata->isReference(), - 'campaignType' => $metadata->getCampaignType(), - 'slug' => $metadata->getSlug(), - 'hex' => $this->valueToHex(['v' => $flag->getValue(null, false)]), - ]; + 'key' => $key, + 'campaignId' => $metadata->getCampaignId(), + 'campaignName' => $metadata->getCampaignName(), + 'variationGroupId' => $metadata->getVariationGroupId(), + 'variationGroupName' => $metadata->getVariationGroupName(), + 'variationId' => $metadata->getVariationId(), + 'variationName' => $metadata->getVariationName(), + 'isReference' => $metadata->isReference(), + 'campaignType' => $metadata->getCampaignType(), + 'slug' => $metadata->getSlug(), + 'hex' => $this->valueToHex(['v' => $flag->getValue(null, false)]), + ]; } - return json_encode($serializedData); + $json = json_encode($serializedData); + return $json !== false ? $json : '[]'; } /** diff --git a/src/Flag/FSFlagCollectionInterface.php b/src/Flag/FSFlagCollectionInterface.php index 635c3b92..5f47288b 100644 --- a/src/Flag/FSFlagCollectionInterface.php +++ b/src/Flag/FSFlagCollectionInterface.php @@ -4,6 +4,9 @@ use Iterator; +/** + * @extends Iterator + */ interface FSFlagCollectionInterface extends Iterator { /** @@ -74,5 +77,5 @@ public function toJSON(): string; * 2. string $key - The key of the current flag. * 3. FSFlagCollectionInterface $collection - The collection the flag belongs to. */ - public function each(callable $callbackFn); + public function each(callable $callbackFn):void; } diff --git a/src/Flag/FSFlagInterface.php b/src/Flag/FSFlagInterface.php index 0453e81c..c0102896 100644 --- a/src/Flag/FSFlagInterface.php +++ b/src/Flag/FSFlagInterface.php @@ -4,15 +4,17 @@ use Flagship\Enum\FSFlagStatus; + interface FSFlagInterface { - /** - * Returns the value from the assigned campaign variation or the Flag default value if the Flag does not exist, - * or if types are different. - * @param array|bool|string|numeric|null $defaultValue - * @param boolean $visitorExposed - * @return bool|numeric|string|array|null - */ + /** + * Returns the value from the assigned campaign variation or the Flag default value if the Flag does not exist, + * or if types are different. + * @phpstan-template T of scalar|array|null + * @param T $defaultValue + * @param boolean $visitorExposed + * @return T + */ public function getValue( float|array|bool|int|string|null $defaultValue, bool $visitorExposed = true diff --git a/src/Flagship.php b/src/Flagship.php index 8502875d..fbddf482 100644 --- a/src/Flagship.php +++ b/src/Flagship.php @@ -4,7 +4,7 @@ use Exception; use Flagship\Traits\Guid; -use Flagship\Utils\FlagshipLogManager8; +use Flagship\Utils\FlagshipLogManager; use Flagship\Utils\HttpClient; use Psr\Log\LoggerInterface; use Flagship\Traits\LogTrait; @@ -44,10 +44,11 @@ class Flagship * @var Container */ private Container $container; + /** * @var FlagshipConfig|null */ - private ?FlagshipConfig $config; + private ?FlagshipConfig $config = null; /** * @var ?ConfigManager @@ -128,9 +129,9 @@ public static function start(string $envId, string $apiKey, ?FlagshipConfig $con $decisionManager = $container->get( BucketingManager::class, [ - $httpClient, - $config, - $murmurHash, + $httpClient, + $config, + $murmurHash, ] ); } else { @@ -144,9 +145,9 @@ public static function start(string $envId, string $apiKey, ?FlagshipConfig $con $trackingManager = $container->get( TrackingManager::class, [ - $config, - $httpClient, - $flagship->flagshipInstanceId, + $config, + $httpClient, + $flagship->flagshipInstanceId, ] ); @@ -200,7 +201,7 @@ private function containerInitialization(): Container ); $newContainer->bind( LoggerInterface::class, - FlagshipLogManager8::class + FlagshipLogManager::class ); return $newContainer; @@ -218,7 +219,7 @@ protected function getConfigManager(): ?ConfigManager * @param ConfigManager $configManager * @return Flagship */ - protected function setConfigManager(ConfigManager $configManager): static + protected function setConfigManager(ConfigManager $configManager): self { $this->configManager = $configManager; return $this; @@ -228,9 +229,9 @@ protected function setConfigManager(ConfigManager $configManager): static /** * Return the current config set by the customer and used by the SDK. * - * @return FlagshipConfig + * @return ?FlagshipConfig */ - public static function getConfig(): FlagshipConfig + public static function getConfig(): ?FlagshipConfig { return self::getInstance()->config; } @@ -239,7 +240,7 @@ public static function getConfig(): FlagshipConfig * @param FlagshipConfig $config * @return Flagship */ - protected function setConfig(FlagshipConfig $config): static + protected function setConfig(FlagshipConfig $config): self { $this->config = $config; return $this; @@ -260,7 +261,7 @@ public static function getStatus(): FSSdkStatus * @param FSSdkStatus $status FSSdkStatus * @return Flagship */ - public function setStatus(FSSdkStatus $status): static + public function setStatus(FSSdkStatus $status): self { $onSdkStatusChanged = $this->config?->getOnSdkStatusChanged(); if ($onSdkStatusChanged && $this->status !== $status) { @@ -291,6 +292,48 @@ public static function close(): void $instance->getConfigManager()?->getTrackingManager()?->sendBatch(); } + private function defaultConfigManagerInitialization(?ConfigManager $configManager): ConfigManager + { + if ($configManager) { + return $configManager; + } + $instance = self::getInstance(); + $container = $instance->getContainer(); + + $instance->flagshipInstanceId = $instance->newGuid(); + + $config = $container->get(DecisionApiConfig::class, ['', '']); + $logManager = $container->get(LoggerInterface::class); + $config->setLogManager($logManager); + + $config = $container->get(DecisionApiConfig::class, ['', '']); + $httpClient = $container->get(HttpClientInterface::class); + $decisionManager = $container->get( + ApiManager::class, + [$httpClient, $config] + ); + $trackingManager = $container->get( + TrackingManager::class, + [ + $config, + $httpClient, + $instance->flagshipInstanceId, + ] + ); + $configManager = $container->get( + ConfigManager::class, + [$config, $decisionManager, $trackingManager], + true + ); + + $instance->logWarning( + $config, + FlagshipConstant::NEW_VISITOR_WARNING, + [FlagshipConstant::TAG => FlagshipConstant::TAG_NEW_VISITOR] + ); + return $configManager; + } + /** * Initialize the builder and return a \Flagship\Visitor\VisitorBuilder. * @@ -301,10 +344,12 @@ public static function close(): void public static function newVisitor(?string $visitorId, bool $hasConsented): VisitorBuilder { $instance = self::getInstance(); + $configManager = $instance->defaultConfigManagerInitialization($instance->getConfigManager()); + return VisitorBuilder::builder( $visitorId, $hasConsented, - $instance->getConfigManager(), + $configManager, $instance->getContainer(), $instance->flagshipInstanceId ); diff --git a/src/Hit/Activate.php b/src/Hit/Activate.php index 47cff6de..8a1df329 100644 --- a/src/Hit/Activate.php +++ b/src/Hit/Activate.php @@ -21,34 +21,31 @@ class Activate extends HitAbstract private string $variationId; /** - * @var ?string + * @var string */ - private ?string $flagKey = null; + private string $flagKey; /** - * @var bool|numeric|string|array|null + * @var array|scalar|null */ private string|array|bool|int|float|null $flagValue = null; /** - * @var ?array + * @var ?array */ private ?array $visitorContext = null; /** - * @var ?FSFlagMetadataInterface + * @var FSFlagMetadataInterface */ - private ?FSFlagMetadataInterface $flagMetadata = null; + private FSFlagMetadataInterface $flagMetadata; /** - * @var bool|numeric|string|array|null + * @var scalar|array|null */ private string|array|bool|int|float|null $flagDefaultValue = null; - public static function getClassName(): string - { - return __CLASS__; - } + @@ -56,11 +53,17 @@ public static function getClassName(): string * @param string $variationGroupId * @param string $variationId */ - public function __construct(string $variationGroupId, string $variationId) - { + public function __construct( + string $variationGroupId, + string $variationId, + string $flagKey, + FSFlagMetadataInterface $flagMetadata + ) { parent::__construct(HitType::ACTIVATE); $this->variationGroupId = $variationGroupId; $this->variationId = $variationId; + $this->flagKey = $flagKey; + $this->flagMetadata = $flagMetadata; } /** @@ -75,7 +78,7 @@ public function getVariationGroupId(): string * @param string $variationGroupId * @return Activate */ - public function setVariationGroupId(string $variationGroupId): static + public function setVariationGroupId(string $variationGroupId): self { $this->variationGroupId = $variationGroupId; return $this; @@ -93,32 +96,32 @@ public function getVariationId(): string * @param string $variationId * @return Activate */ - public function setVariationId(string $variationId): static + public function setVariationId(string $variationId): self { $this->variationId = $variationId; return $this; } /** - * @return string + * @return ?string */ - public function getFlagKey(): string + public function getFlagKey(): ?string { return $this->flagKey; } /** - * @param ?string $flagKey + * @param string $flagKey * @return Activate */ - public function setFlagKey(?string $flagKey): static + public function setFlagKey(string $flagKey): self { $this->flagKey = $flagKey; return $this; } /** - * @return bool|numeric|string|array|null + * @return array|scalar|null */ public function getFlagValue(): float|array|bool|int|string|null { @@ -126,28 +129,28 @@ public function getFlagValue(): float|array|bool|int|string|null } /** - * @param array|bool|string|numeric|null $flagValue + * @param array|scalar|null $flagValue * @return Activate */ - public function setFlagValue(float|array|bool|int|string|null $flagValue): static + public function setFlagValue(float|array|bool|int|string|null $flagValue): self { $this->flagValue = $flagValue; return $this; } /** - * @return array + * @return ?array */ - public function getVisitorContext(): array + public function getVisitorContext(): ?array { return $this->visitorContext; } /** - * @param ?array $visitorContext + * @param ?array $visitorContext * @return Activate */ - public function setVisitorContext(?array $visitorContext): static + public function setVisitorContext(?array $visitorContext): self { $this->visitorContext = $visitorContext; return $this; @@ -162,17 +165,17 @@ public function getFlagMetadata(): FSFlagMetadataInterface } /** - * @param ?FSFlagMetadataInterface $flagMetadata + * @param FSFlagMetadataInterface $flagMetadata * @return Activate */ - public function setFlagMetadata(?FSFlagMetadataInterface $flagMetadata): static + public function setFlagMetadata(FSFlagMetadataInterface $flagMetadata): self { $this->flagMetadata = $flagMetadata; return $this; } /** - * @return bool|numeric|string|array|null + * @return array|scalar|null */ public function getFlagDefaultValue(): float|array|bool|int|string|null { @@ -180,10 +183,10 @@ public function getFlagDefaultValue(): float|array|bool|int|string|null } /** - * @param array|bool|string|numeric|null $flagDefaultValue + * @param array|scalar|null $flagDefaultValue * @return Activate */ - public function setFlagDefaultValue(float|array|bool|int|string|null $flagDefaultValue): static + public function setFlagDefaultValue(float|array|bool|int|string|null $flagDefaultValue): self { $this->flagDefaultValue = $flagDefaultValue; return $this; @@ -198,13 +201,13 @@ public function setFlagDefaultValue(float|array|bool|int|string|null $flagDefaul public function toApiKeys(): array { $apiKeys = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $this->getVisitorId(), - FlagshipConstant::VARIATION_ID_API_ITEM => $this->getVariationId(), - FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $this->getVariationGroupId(), - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->config->getEnvId(), - FlagshipConstant::ANONYMOUS_ID => null, - FlagshipConstant::QT_API_ITEM => round(microtime(true) * 1000) - $this->createdAt, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $this->getVisitorId(), + FlagshipConstant::VARIATION_ID_API_ITEM => $this->getVariationId(), + FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $this->getVariationGroupId(), + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->config?->getEnvId() ?? '', + FlagshipConstant::ANONYMOUS_ID => null, + FlagshipConstant::QT_API_ITEM => $this->getNow() - $this->createdAt, + ]; if ($this->getVisitorId() && $this->getAnonymousId()) { $apiKeys[FlagshipConstant::VISITOR_ID_API_ITEM] = $this->getVisitorId(); diff --git a/src/Hit/ActivateBatch.php b/src/Hit/ActivateBatch.php index d60bec1d..db3a83b4 100644 --- a/src/Hit/ActivateBatch.php +++ b/src/Hit/ActivateBatch.php @@ -7,8 +7,8 @@ class ActivateBatch { - /*** - * @var Activate[] + /** + * @var Activate[] $hits */ protected array $hits; @@ -19,7 +19,7 @@ class ActivateBatch /** * @param FlagshipConfig $config - * @param array $hits + * @param Activate[] $hits */ public function __construct(FlagshipConfig $config, array $hits) { @@ -28,7 +28,7 @@ public function __construct(FlagshipConfig $config, array $hits) } /** - * @return array + * @return array */ public function toApiKeys(): array { @@ -39,8 +39,8 @@ public function toApiKeys(): array $activates[] = $apiKeys; } return [ - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->config->getEnvId(), - FlagshipConstant::BATCH => $activates, - ]; + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->config->getEnvId(), + FlagshipConstant::BATCH => $activates, + ]; } } diff --git a/src/Hit/Diagnostic.php b/src/Hit/Diagnostic.php index e83f0b70..dc236bdd 100644 --- a/src/Hit/Diagnostic.php +++ b/src/Hit/Diagnostic.php @@ -93,9 +93,9 @@ class Diagnostic extends HitAbstract private ?bool $sdkConfigStatusListener = null; /** - * @var int|float|null + * @var int|float|null|string */ - private int|float|null $sdkConfigTimeout = null; + private int|float|null|string $sdkConfigTimeout = null; /** @@ -139,7 +139,7 @@ class Diagnostic extends HitAbstract private ?string $httpRequestMethod = null; /** - * @var ?array + * @var ?array */ private ?array $httpRequestHeaders = null; @@ -159,14 +159,14 @@ class Diagnostic extends HitAbstract private ?string $httpResponseMethod = null; /** - * @var ?array + * @var ?array */ private ?array $httpResponseHeaders = null; /** - * @var ?int + * @var int|string|null */ - private ?int $httpResponseCode = null; + private string|int|null $httpResponseCode = null; /** * @var mixed @@ -176,10 +176,10 @@ class Diagnostic extends HitAbstract /** * @var ?int */ - private ?int $httpResponseTime = null; + private int|float|null $httpResponseTime = null; /** - * @var ?array + * @var ?array */ private ?array $visitorContext = null; @@ -189,7 +189,7 @@ class Diagnostic extends HitAbstract private ?bool $visitorConsent = null; /** - * @var ?array + * @var ?array */ private ?array $visitorAssignmentHistory = null; @@ -199,7 +199,7 @@ class Diagnostic extends HitAbstract private ?array $visitorFlags = null; /** - * @var ?array + * @var ?array */ private ?array $visitorCampaigns = null; @@ -275,9 +275,9 @@ class Diagnostic extends HitAbstract /** - * @var mixed + * @var array */ - private mixed $hitContent = null; + private ?array $hitContent = null; /** * @var ?string @@ -312,7 +312,7 @@ public function getFlagshipInstanceId(): ?string * @param ?string $flagshipInstanceId * @return Diagnostic */ - public function setFlagshipInstanceId(?string $flagshipInstanceId): static + public function setFlagshipInstanceId(?string $flagshipInstanceId): self { $this->flagshipInstanceId = $flagshipInstanceId; return $this; @@ -330,7 +330,7 @@ public function getVisitorSessionId(): ?string * @param string $visitorSessionId * @return Diagnostic */ - public function setVisitorSessionId(string $visitorSessionId): static + public function setVisitorSessionId(string $visitorSessionId): self { $this->visitorSessionId = $visitorSessionId; return $this; @@ -348,7 +348,7 @@ public function getTraffic(): float|int|null * @param float|int|null $traffic * @return Diagnostic */ - public function setTraffic(float|int|null $traffic): static + public function setTraffic(float|int|null $traffic): self { $this->traffic = $traffic; return $this; @@ -366,7 +366,7 @@ public function getVersion(): string * @param string $version * @return Diagnostic */ - public function setVersion(string $version): static + public function setVersion(string $version): self { $this->version = $version; return $this; @@ -384,7 +384,7 @@ public function getLogLevel(): LogLevel * @param LogLevel $logLevel * @return Diagnostic */ - public function setLogLevel(LogLevel $logLevel): static + public function setLogLevel(LogLevel $logLevel): self { $this->logLevel = $logLevel; return $this; @@ -402,7 +402,7 @@ public function getTimestamp(): string * @param string $timestamp * @return Diagnostic */ - public function setTimestamp(string $timestamp): static + public function setTimestamp(string $timestamp): self { $this->timestamp = $timestamp; return $this; @@ -420,7 +420,7 @@ public function getTimeZone(): string * @param string $timeZone * @return Diagnostic */ - public function setTimeZone(string $timeZone): static + public function setTimeZone(string $timeZone): self { $this->timeZone = $timeZone; return $this; @@ -438,7 +438,7 @@ public function getLabel(): TroubleshootingLabel * @param TroubleshootingLabel $label * @return Diagnostic */ - public function setLabel(TroubleshootingLabel $label): static + public function setLabel(TroubleshootingLabel $label): self { $this->label = $label; return $this; @@ -456,7 +456,7 @@ public function getStackType(): string * @param string $stackType * @return Diagnostic */ - public function setStackType(string $stackType): static + public function setStackType(string $stackType): self { $this->stackType = $stackType; return $this; @@ -474,7 +474,7 @@ public function getStackName(): string * @param string $stackName * @return Diagnostic */ - public function setStackName(string $stackName): static + public function setStackName(string $stackName): self { $this->stackName = $stackName; return $this; @@ -492,7 +492,7 @@ public function getStackVersion(): string * @param string $stackVersion * @return Diagnostic */ - public function setStackVersion(string $stackVersion): static + public function setStackVersion(string $stackVersion): self { $this->stackVersion = $stackVersion; return $this; @@ -510,7 +510,7 @@ public function getStackOriginName(): ?string * @param string $stackOriginName * @return Diagnostic */ - public function setStackOriginName(string $stackOriginName): static + public function setStackOriginName(string $stackOriginName): self { $this->stackOriginName = $stackOriginName; return $this; @@ -528,7 +528,7 @@ public function getStackOriginVersion(): ?string * @param string $stackOriginVersion * @return Diagnostic */ - public function setStackOriginVersion(string $stackOriginVersion): static + public function setStackOriginVersion(string $stackOriginVersion): self { $this->stackOriginVersion = $stackOriginVersion; return $this; @@ -546,7 +546,7 @@ public function getSdkStatus(): ?FSSdkStatus * @param ?FSSdkStatus $sdkStatus * @return Diagnostic */ - public function setSdkStatus(?FSSdkStatus $sdkStatus): static + public function setSdkStatus(?FSSdkStatus $sdkStatus): self { $this->sdkStatus = $sdkStatus; return $this; @@ -564,7 +564,7 @@ public function getSdkConfigMode(): ?DecisionMode * @param DecisionMode $sdkConfigMode * @return Diagnostic */ - public function setSdkConfigMode(DecisionMode $sdkConfigMode): static + public function setSdkConfigMode(DecisionMode $sdkConfigMode): self { $this->sdkConfigMode = $sdkConfigMode; return $this; @@ -582,7 +582,7 @@ public function getSdkConfigCustomLogManager(): ?bool * @param bool $sdkConfigCustomLogManager * @return Diagnostic */ - public function setSdkConfigCustomLogManager(bool $sdkConfigCustomLogManager): static + public function setSdkConfigCustomLogManager(bool $sdkConfigCustomLogManager): self { $this->sdkConfigCustomLogManager = $sdkConfigCustomLogManager; return $this; @@ -600,7 +600,7 @@ public function getSdkConfigCustomCacheManager(): ?bool * @param bool $sdkConfigCustomCacheManager * @return Diagnostic */ - public function setSdkConfigCustomCacheManager(bool $sdkConfigCustomCacheManager): static + public function setSdkConfigCustomCacheManager(bool $sdkConfigCustomCacheManager): self { $this->sdkConfigCustomCacheManager = $sdkConfigCustomCacheManager; return $this; @@ -618,7 +618,7 @@ public function getSdkConfigStatusListener(): ?bool * @param bool $sdkConfigStatusListener * @return Diagnostic */ - public function setSdkConfigStatusListener(bool $sdkConfigStatusListener): static + public function setSdkConfigStatusListener(bool $sdkConfigStatusListener): self { $this->sdkConfigStatusListener = $sdkConfigStatusListener; return $this; @@ -636,7 +636,7 @@ public function getSdkConfigBucketingUrl(): ?string * @param ?string $sdkConfigBucketingUrl * @return Diagnostic */ - public function setSdkConfigBucketingUrl(?string $sdkConfigBucketingUrl): static + public function setSdkConfigBucketingUrl(?string $sdkConfigBucketingUrl): self { $this->sdkConfigBucketingUrl = $sdkConfigBucketingUrl; return $this; @@ -651,10 +651,10 @@ public function getSdkConfigFetchThirdPartyData(): ?bool } /** - * @param ?string $sdkConfigFetchThirdPartyData + * @param ?bool $sdkConfigFetchThirdPartyData * @return Diagnostic */ - public function setSdkConfigFetchThirdPartyData(?string $sdkConfigFetchThirdPartyData): static + public function setSdkConfigFetchThirdPartyData(?bool $sdkConfigFetchThirdPartyData): self { $this->sdkConfigFetchThirdPartyData = $sdkConfigFetchThirdPartyData; return $this; @@ -672,7 +672,7 @@ public function getSdkConfigTimeout(): float|int|string|null * @param numeric $sdkConfigTimeout * @return Diagnostic */ - public function setSdkConfigTimeout(float|int|string $sdkConfigTimeout): static + public function setSdkConfigTimeout(float|int|string $sdkConfigTimeout): self { $this->sdkConfigTimeout = $sdkConfigTimeout; return $this; @@ -693,7 +693,7 @@ public function getSdkConfigTrackingManagerConfigStrategy(): ?CacheStrategy */ public function setSdkConfigTrackingManagerConfigStrategy( CacheStrategy $sdkConfigTrackingManagerConfigStrategy - ): static { + ): self { $this->sdkConfigTrackingManagerConfigStrategy = $sdkConfigTrackingManagerConfigStrategy; return $this; } @@ -710,7 +710,7 @@ public function isSdkConfigUsingCustomHitCache(): ?bool * @param bool $sdkConfigUsingCustomHitCache * @return Diagnostic */ - public function setSdkConfigUsingCustomHitCache(bool $sdkConfigUsingCustomHitCache): static + public function setSdkConfigUsingCustomHitCache(bool $sdkConfigUsingCustomHitCache): self { $this->sdkConfigUsingCustomHitCache = $sdkConfigUsingCustomHitCache; return $this; @@ -728,7 +728,7 @@ public function isSdkConfigUsingCustomVisitorCache(): ?bool * @param bool $sdkConfigUsingCustomVisitorCache * @return Diagnostic */ - public function setSdkConfigUsingCustomVisitorCache(bool $sdkConfigUsingCustomVisitorCache): static + public function setSdkConfigUsingCustomVisitorCache(bool $sdkConfigUsingCustomVisitorCache): self { $this->sdkConfigUsingCustomVisitorCache = $sdkConfigUsingCustomVisitorCache; return $this; @@ -746,7 +746,7 @@ public function isSdkConfigUsingOnVisitorExposed(): ?bool * @param bool $sdkConfigUsingOnVisitorExposed * @return Diagnostic */ - public function setSdkConfigUsingOnVisitorExposed(bool $sdkConfigUsingOnVisitorExposed): static + public function setSdkConfigUsingOnVisitorExposed(bool $sdkConfigUsingOnVisitorExposed): self { $this->sdkConfigUsingOnVisitorExposed = $sdkConfigUsingOnVisitorExposed; return $this; @@ -764,7 +764,7 @@ public function getHttpRequestUrl(): ?string * @param string $httpRequestUrl * @return Diagnostic */ - public function setHttpRequestUrl(string $httpRequestUrl): static + public function setHttpRequestUrl(string $httpRequestUrl): self { $this->httpRequestUrl = $httpRequestUrl; return $this; @@ -782,14 +782,14 @@ public function getHttpRequestMethod(): ?string * @param string $httpRequestMethod * @return Diagnostic */ - public function setHttpRequestMethod(string $httpRequestMethod): static + public function setHttpRequestMethod(string $httpRequestMethod): self { $this->httpRequestMethod = $httpRequestMethod; return $this; } /** - * @return array|null + * @return array|null */ public function getHttpRequestHeaders(): ?array { @@ -797,10 +797,10 @@ public function getHttpRequestHeaders(): ?array } /** - * @param array $httpRequestHeaders + * @param array $httpRequestHeaders * @return Diagnostic */ - public function setHttpRequestHeaders(array $httpRequestHeaders): static + public function setHttpRequestHeaders(array $httpRequestHeaders): self { $this->httpRequestHeaders = $httpRequestHeaders; return $this; @@ -818,7 +818,7 @@ public function getHttpRequestBody(): mixed * @param mixed $httpRequestBody * @return Diagnostic */ - public function setHttpRequestBody(mixed $httpRequestBody): static + public function setHttpRequestBody(mixed $httpRequestBody): self { $this->httpRequestBody = $httpRequestBody; return $this; @@ -836,7 +836,7 @@ public function getHttpResponseUrl(): ?string * @param string $httpResponseUrl * @return Diagnostic */ - public function setHttpResponseUrl(string $httpResponseUrl): static + public function setHttpResponseUrl(string $httpResponseUrl): self { $this->httpResponseUrl = $httpResponseUrl; return $this; @@ -854,14 +854,14 @@ public function getHttpResponseMethod(): ?string * @param string $httpResponseMethod * @return Diagnostic */ - public function setHttpResponseMethod(string $httpResponseMethod): static + public function setHttpResponseMethod(string $httpResponseMethod): self { $this->httpResponseMethod = $httpResponseMethod; return $this; } /** - * @return array|null + * @return array|null */ public function getHttpResponseHeaders(): ?array { @@ -869,28 +869,28 @@ public function getHttpResponseHeaders(): ?array } /** - * @param array $httpResponseHeaders + * @param array $httpResponseHeaders * @return Diagnostic */ - public function setHttpResponseHeaders(array $httpResponseHeaders): static + public function setHttpResponseHeaders(array $httpResponseHeaders): self { $this->httpResponseHeaders = $httpResponseHeaders; return $this; } /** - * @return int|null + * @return int|null|string */ - public function getHttpResponseCode(): ?int + public function getHttpResponseCode(): int|null|string { return $this->httpResponseCode; } /** - * @param int $httpResponseCode + * @param int|null|string $httpResponseCode * @return Diagnostic */ - public function setHttpResponseCode(int $httpResponseCode): static + public function setHttpResponseCode(int|null|string $httpResponseCode): self { $this->httpResponseCode = $httpResponseCode; return $this; @@ -908,32 +908,32 @@ public function getHttpResponseBody(): mixed * @param mixed $httpResponseBody * @return Diagnostic */ - public function setHttpResponseBody(mixed $httpResponseBody): static + public function setHttpResponseBody(mixed $httpResponseBody): self { $this->httpResponseBody = $httpResponseBody; return $this; } /** - * @return int|null + * @return int|float|string|null */ - public function getHttpResponseTime(): ?int + public function getHttpResponseTime(): int|float|null|string { return $this->httpResponseTime; } /** - * @param int $httpResponseTime + * @param int|float $httpResponseTime * @return Diagnostic */ - public function setHttpResponseTime(int $httpResponseTime): static + public function setHttpResponseTime(int|float $httpResponseTime): self { $this->httpResponseTime = $httpResponseTime; return $this; } /** - * @return array|null + * @return array|null */ public function getVisitorContext(): ?array { @@ -941,10 +941,10 @@ public function getVisitorContext(): ?array } /** - * @param array $visitorContext + * @param array $visitorContext * @return Diagnostic */ - public function setVisitorContext(array $visitorContext): static + public function setVisitorContext(array $visitorContext): self { $this->visitorContext = $visitorContext; return $this; @@ -962,14 +962,14 @@ public function isVisitorConsent(): ?bool * @param bool $visitorConsent * @return Diagnostic */ - public function setVisitorConsent(bool $visitorConsent): static + public function setVisitorConsent(bool $visitorConsent): self { $this->visitorConsent = $visitorConsent; return $this; } /** - * @return array|null + * @return array|null */ public function getVisitorAssignmentHistory(): ?array { @@ -977,17 +977,17 @@ public function getVisitorAssignmentHistory(): ?array } /** - * @param array $visitorAssignmentHistory + * @param array $visitorAssignmentHistory * @return Diagnostic */ - public function setVisitorAssignmentHistory(array $visitorAssignmentHistory): static + public function setVisitorAssignmentHistory(array $visitorAssignmentHistory): self { $this->visitorAssignmentHistory = $visitorAssignmentHistory; return $this; } /** - * @return array|null + * @return FlagDTO[]|null */ public function getVisitorFlags(): ?array { @@ -998,14 +998,14 @@ public function getVisitorFlags(): ?array * @param FlagDTO[] $visitorFlags * @return Diagnostic */ - public function setVisitorFlags(array $visitorFlags): static + public function setVisitorFlags(array $visitorFlags): self { $this->visitorFlags = $visitorFlags; return $this; } /** - * @return array|null + * @return array|null */ public function getVisitorCampaigns(): ?array { @@ -1013,10 +1013,10 @@ public function getVisitorCampaigns(): ?array } /** - * @param array $visitorCampaigns + * @param array $visitorCampaigns * @return Diagnostic */ - public function setVisitorCampaigns(array $visitorCampaigns): static + public function setVisitorCampaigns(array $visitorCampaigns): self { $this->visitorCampaigns = $visitorCampaigns; return $this; @@ -1034,7 +1034,7 @@ public function isVisitorIsAuthenticated(): ?bool * @param bool $visitorIsAuthenticated * @return Diagnostic */ - public function setVisitorIsAuthenticated(bool $visitorIsAuthenticated): static + public function setVisitorIsAuthenticated(bool $visitorIsAuthenticated): self { $this->visitorIsAuthenticated = $visitorIsAuthenticated; return $this; @@ -1052,7 +1052,7 @@ public function getFlagKey(): ?string * @param string $flagKey * @return Diagnostic */ - public function setFlagKey(string $flagKey): static + public function setFlagKey(string $flagKey): self { $this->flagKey = $flagKey; return $this; @@ -1070,7 +1070,7 @@ public function getFlagValue(): mixed * @param mixed $flagValue * @return Diagnostic */ - public function setFlagValue(mixed $flagValue): static + public function setFlagValue(mixed $flagValue): self { $this->flagValue = $flagValue; return $this; @@ -1088,7 +1088,7 @@ public function getFlagDefault(): mixed * @param mixed $flagDefault * @return Diagnostic */ - public function setFlagDefault(mixed $flagDefault): static + public function setFlagDefault(mixed $flagDefault): self { $this->flagDefault = $flagDefault; return $this; @@ -1106,7 +1106,7 @@ public function isVisitorExposed(): ?bool * @param bool $visitorExposed * @return Diagnostic */ - public function setVisitorExposed(bool $visitorExposed): static + public function setVisitorExposed(bool $visitorExposed): self { $this->visitorExposed = $visitorExposed; return $this; @@ -1124,7 +1124,7 @@ public function getFlagMetadataCampaignId(): ?string * @param string $flagMetadataCampaignId * @return Diagnostic */ - public function setFlagMetadataCampaignId(string $flagMetadataCampaignId): static + public function setFlagMetadataCampaignId(string $flagMetadataCampaignId): self { $this->flagMetadataCampaignId = $flagMetadataCampaignId; return $this; @@ -1142,7 +1142,7 @@ public function getFlagMetadataVariationGroupId(): ?string * @param string $flagMetadataVariationGroupId * @return Diagnostic */ - public function setFlagMetadataVariationGroupId(string $flagMetadataVariationGroupId): static + public function setFlagMetadataVariationGroupId(string $flagMetadataVariationGroupId): self { $this->flagMetadataVariationGroupId = $flagMetadataVariationGroupId; return $this; @@ -1160,7 +1160,7 @@ public function getFlagMetadataVariationId(): ?string * @param string $flagMetadataVariationId * @return Diagnostic */ - public function setFlagMetadataVariationId(string $flagMetadataVariationId): static + public function setFlagMetadataVariationId(string $flagMetadataVariationId): self { $this->flagMetadataVariationId = $flagMetadataVariationId; return $this; @@ -1178,7 +1178,7 @@ public function getFlagMetadataCampaignSlug(): ?string * @param string $flagMetadataCampaignSlug * @return Diagnostic */ - public function setFlagMetadataCampaignSlug(string $flagMetadataCampaignSlug): static + public function setFlagMetadataCampaignSlug(string $flagMetadataCampaignSlug): self { $this->flagMetadataCampaignSlug = $flagMetadataCampaignSlug; return $this; @@ -1196,7 +1196,7 @@ public function getFlagMetadataCampaignType(): ?string * @param string $flagMetadataCampaignType * @return Diagnostic */ - public function setFlagMetadataCampaignType(string $flagMetadataCampaignType): static + public function setFlagMetadataCampaignType(string $flagMetadataCampaignType): self { $this->flagMetadataCampaignType = $flagMetadataCampaignType; return $this; @@ -1214,7 +1214,7 @@ public function isFlagMetadataCampaignIsReference(): ?bool * @param bool $flagMetadataCampaignIsReference * @return Diagnostic */ - public function setFlagMetadataCampaignIsReference(bool $flagMetadataCampaignIsReference): static + public function setFlagMetadataCampaignIsReference(bool $flagMetadataCampaignIsReference): self { $this->flagMetadataCampaignIsReference = $flagMetadataCampaignIsReference; return $this; @@ -1232,7 +1232,7 @@ public function getFlagMetadataCampaignName(): ?string * @param string $flagMetadataCampaignName * @return Diagnostic */ - public function setFlagMetadataCampaignName(string $flagMetadataCampaignName): static + public function setFlagMetadataCampaignName(string $flagMetadataCampaignName): self { $this->flagMetadataCampaignName = $flagMetadataCampaignName; return $this; @@ -1250,7 +1250,7 @@ public function getFlagMetadataVariationGroupName(): ?string * @param string $flagMetadataVariationGroupName * @return Diagnostic */ - public function setFlagMetadataVariationGroupName(string $flagMetadataVariationGroupName): static + public function setFlagMetadataVariationGroupName(string $flagMetadataVariationGroupName): self { $this->flagMetadataVariationGroupName = $flagMetadataVariationGroupName; return $this; @@ -1268,7 +1268,7 @@ public function getFlagMetadataVariationName(): ?string * @param string $flagMetadataVariationName * @return Diagnostic */ - public function setFlagMetadataVariationName(string $flagMetadataVariationName): static + public function setFlagMetadataVariationName(string $flagMetadataVariationName): self { $this->flagMetadataVariationName = $flagMetadataVariationName; return $this; @@ -1276,18 +1276,18 @@ public function setFlagMetadataVariationName(string $flagMetadataVariationName): /** - * @return mixed + * @return array|null */ - public function getHitContent(): mixed + public function getHitContent(): ?array { return $this->hitContent; } /** - * @param mixed $hitContent + * @param array $hitContent * @return Diagnostic */ - public function setHitContent(mixed $hitContent): static + public function setHitContent(array $hitContent): self { $this->hitContent = $hitContent; return $this; @@ -1305,7 +1305,7 @@ public function getSdkConfigLogLeve(): ?LogLevel * @param LogLevel $sdkConfigLogLeve * @return Diagnostic */ - public function setSdkConfigLogLevel(LogLevel $sdkConfigLogLeve): static + public function setSdkConfigLogLevel(LogLevel $sdkConfigLogLeve): self { $this->sdkConfigLogLeve = $sdkConfigLogLeve; return $this; @@ -1313,20 +1313,24 @@ public function setSdkConfigLogLevel(LogLevel $sdkConfigLogLeve): static + /** + * + * @return array + */ public function toApiKeys(): array { $customVariable = [ - 'version' => $this->getVersion(), - 'logLevel' => $this->getLogLevel()->name, - 'envId' => $this->getConfig()->getEnvId(), - "timestamp" => $this->getTimestamp(), - 'timeZone' => $this->getTimeZone(), - 'label' => $this->getLabel()->value, - 'stack.type' => $this->getStackType(), - 'stack.name' => $this->getStackName(), - 'stack.version' => $this->getStackVersion(), - ]; - if ($this->getVisitorId() !== null) { + 'version' => $this->getVersion(), + 'logLevel' => $this->getLogLevel()->name, + 'envId' => $this->getConfig()?->getEnvId(), + "timestamp" => $this->getTimestamp(), + 'timeZone' => $this->getTimeZone(), + 'label' => $this->getLabel()->value, + 'stack.type' => $this->getStackType(), + 'stack.name' => $this->getStackName(), + 'stack.version' => $this->getStackVersion(), + ]; + if (!empty($this->getVisitorId())) { $customVariable["visitor.visitorId"] = $this->getVisitorId(); } @@ -1522,12 +1526,12 @@ public function toApiKeys(): array } return [ - FlagshipConstant::VISITOR_ID_API_ITEM => $this->visitorId, - FlagshipConstant::DS_API_ITEM => $this->getDs(), - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()->getEnvId(), - FlagshipConstant::T_API_ITEM => $this->getType()->value, - 'cv' => $customVariable, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $this->visitorId, + FlagshipConstant::DS_API_ITEM => $this->getDs(), + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()?->getEnvId(), + FlagshipConstant::T_API_ITEM => $this->getType()->value, + 'cv' => $customVariable, + ]; } /** diff --git a/src/Hit/Event.php b/src/Hit/Event.php index 55e0617d..a4a74a3d 100644 --- a/src/Hit/Event.php +++ b/src/Hit/Event.php @@ -17,10 +17,7 @@ class Event extends HitAbstract public const CATEGORY_ERROR = "The category value must be either EventCategory::ACTION_TRACKING or EventCategory::ACTION_TRACKING"; public const VALUE_FIELD_ERROR = 'value must be an integer and be >= 0'; - public static function getClassName(): string - { - return __CLASS__; - } + /** * @var EventCategory @@ -71,7 +68,7 @@ public function getCategory(): EventCategory * @param EventCategory $category * @return Event */ - public function setCategory(EventCategory $category): static + public function setCategory(EventCategory $category): self { $this->category = $category; return $this; @@ -94,7 +91,7 @@ public function getAction(): string * @param string $action : Event name. * @return Event */ - public function setAction(string $action): static + public function setAction(string $action): self { $this->action = $action; return $this; @@ -116,7 +113,7 @@ public function getLabel(): ?string * @param ?string $label : event label. * @return Event */ - public function setLabel(?string $label): static + public function setLabel(?string $label): self { $this->label = $label; return $this; @@ -140,7 +137,7 @@ public function getValue(): ?float * @param float|null $value : event value * @return Event */ - public function setValue(?float $value): static + public function setValue(?float $value): self { if (is_numeric($value) && $value < 0) { $this->logError( @@ -179,7 +176,8 @@ public function toApiKeys(): array */ public function isReady(): bool { - return parent::isReady() && $this->getCategory() && $this->getAction(); + return parent::isReady() && + $this->getAction(); } /** diff --git a/src/Hit/HitAbstract.php b/src/Hit/HitAbstract.php index f1766a39..8b18325d 100644 --- a/src/Hit/HitAbstract.php +++ b/src/Hit/HitAbstract.php @@ -76,9 +76,9 @@ abstract class HitAbstract protected string $key; /** - * @var int + * @var float */ - protected int $createdAt; + protected float $createdAt; /** * @var bool @@ -115,9 +115,9 @@ public function getVisitorId(): string * Specifies visitor unique identifier provided by developer at visitor creation * * @param string $visitorId - * @return HitAbstract + * @return self */ - public function setVisitorId(string $visitorId): static + public function setVisitorId(string $visitorId): self { $this->visitorId = $visitorId; return $this; @@ -133,9 +133,9 @@ public function getDs(): string /** * @param string $ds - * @return HitAbstract + * @return self */ - public function setDs(string $ds): static + public function setDs(string $ds): self { $this->ds = $ds; return $this; @@ -151,10 +151,10 @@ public function getType(): HitType return $this->type; } - protected function setType(HitType $type): static + protected function setType(HitType $type): self { - $this->type = $type; - return $this; + $this->type = $type; + return $this; } /** @@ -167,9 +167,9 @@ public function getConfig(): ?FlagshipConfig /** * @param FlagshipConfig $config - * @return HitAbstract + * @return self */ - public function setConfig(FlagshipConfig $config): static + public function setConfig(FlagshipConfig $config): self { $this->config = $config; return $this; @@ -185,9 +185,9 @@ public function getAnonymousId(): ?string /** * @param string|null $anonymousId - * @return HitAbstract + * @return self */ - public function setAnonymousId(?string $anonymousId): static + public function setAnonymousId(?string $anonymousId): self { $this->anonymousId = $anonymousId; return $this; @@ -205,9 +205,9 @@ public function getUserIP(): ?string /** * Define the User IP address * @param string|null $userIP - * @return HitAbstract + * @return self */ - public function setUserIP(?string $userIP): static + public function setUserIP(?string $userIP): self { $this->userIP = $userIP; return $this; @@ -225,9 +225,9 @@ public function getScreenResolution(): ?string /** * Screen Resolution * @param string|null $screenResolution - * @return HitAbstract + * @return self */ - public function setScreenResolution(?string $screenResolution): static + public function setScreenResolution(?string $screenResolution): self { $this->screenResolution = $screenResolution; return $this; @@ -245,9 +245,9 @@ public function getLocale(): ?string /** * Define User language * @param string|null $locale - * @return HitAbstract + * @return self */ - public function setLocale(?string $locale): static + public function setLocale(?string $locale): self { $this->locale = $locale; return $this; @@ -265,9 +265,9 @@ public function getSessionNumber(): float|int|string|null /** * Define Session number. Number of sessions the current visitor has logged, including the current session * @param float|int|string|null $sessionNumber - * @return HitAbstract + * @return self */ - public function setSessionNumber(float|int|string|null $sessionNumber): static + public function setSessionNumber(float|int|string|null $sessionNumber): self { $this->sessionNumber = $sessionNumber; return $this; @@ -283,27 +283,27 @@ public function getKey(): string /** * @param string $key - * @return HitAbstract + * @return self */ - public function setKey(string $key): static + public function setKey(string $key): self { $this->key = $key; return $this; } /** - * @return int + * @return float */ - public function getCreatedAt(): int + public function getCreatedAt(): float { return $this->createdAt; } /** - * @param int $createdAt - * @return HitAbstract + * @param float $createdAt + * @return self */ - public function setCreatedAt(int $createdAt): static + public function setCreatedAt(float $createdAt): self { $this->createdAt = $createdAt; return $this; @@ -319,9 +319,9 @@ public function getIsFromCache(): bool /** * @param bool $isFromCache - * @return HitAbstract + * @return self */ - protected function setIsFromCache(bool $isFromCache): static + protected function setIsFromCache(bool $isFromCache): self { $this->isFromCache = $isFromCache; return $this; @@ -330,18 +330,18 @@ protected function setIsFromCache(bool $isFromCache): static /** * Return an associative array of the class with Api parameters as keys * - * @return array + * @return array */ public function toApiKeys(): array { $data = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $this->visitorId ?: $this->anonymousId, - FlagshipConstant::DS_API_ITEM => $this->getDs(), - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()->getEnvId(), - FlagshipConstant::T_API_ITEM => $this->getType()->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => $this->getNow() - $this->createdAt, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $this->visitorId ?: $this->anonymousId, + FlagshipConstant::DS_API_ITEM => $this->getDs(), + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()?->getEnvId(), + FlagshipConstant::T_API_ITEM => $this->getType()->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => $this->getNow() - $this->createdAt, + ]; if ($this->getUserIP() !== null) { $data[FlagshipConstant::USER_IP_API_ITEM] = $this->getUserIP(); @@ -367,30 +367,34 @@ public function toApiKeys(): array } /** - * @param $class - * @param $data - * @return object + * @template T of object + * @param class-string $class + * @param array $data + * @return T * @throws ReflectionException */ - public static function hydrate($class, $data): object + public static function hydrate(string $class, array $data): object { + $reflector = new ReflectionClass($class); $objet = $reflector->newInstanceWithoutConstructor(); foreach ($data as $key => $value) { $method = 'set' . ucwords($key); if (is_callable(array($objet, $method))) { - if ($key === "type") { + if ($key === "type" && is_string($value)) { $value = HitType::from($value); } $objet->$method($value); } } - $objet->setIsFromCache(true); + if ($objet instanceof HitAbstract) { + $objet->setIsFromCache(true); + } return $objet; } /** - * @return array + * @return array */ public function toArray(): array { @@ -401,7 +405,6 @@ public function toArray(): array if ($property->getName() === 'config' || $property->getName() === 'isFromCache') { continue; } - $property->setAccessible(true); $value = $property->getValue($this); if ($value instanceof HitType) { $value = $value->value; @@ -420,7 +423,7 @@ public function toArray(): array public function isReady(): bool { return $this->getVisitorId() && $this->getDs() && $this->getConfig() && - $this->getConfig()->getEnvId() && $this->getType(); + $this->getConfig()->getEnvId(); } /** diff --git a/src/Hit/HitBatch.php b/src/Hit/HitBatch.php index 7bfac945..337e92ab 100644 --- a/src/Hit/HitBatch.php +++ b/src/Hit/HitBatch.php @@ -32,13 +32,13 @@ public function __construct(FlagshipConfig $config, array $hits) } /** - * @return array + * @return array */ public function toApiKeys(): array { $data = [ FlagshipConstant::DS_API_ITEM => $this->getDs(), - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()->getEnvId(), + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $this->getConfig()?->getEnvId(), FlagshipConstant::T_API_ITEM => $this->getType()->value, FlagshipConstant::QT_API_ITEM => $this->getNow() - $this->createdAt, FlagshipConstant::H_API_ITEM => [], diff --git a/src/Hit/Item.php b/src/Hit/Item.php index 727c9833..2d2cdb15 100644 --- a/src/Hit/Item.php +++ b/src/Hit/Item.php @@ -14,10 +14,7 @@ class Item extends HitAbstract { public const ERROR_MESSAGE = 'Transaction Id, Item name and item code are required'; - public static function getClassName(): string - { - return __CLASS__; - } + /** * @var string @@ -81,7 +78,7 @@ public function getTransactionId(): string * @param string $transactionId : Transaction unique identifier. * @return Item */ - public function setTransactionId(string $transactionId): static + public function setTransactionId(string $transactionId): self { $this->transactionId = $transactionId; return $this; @@ -103,7 +100,7 @@ public function getProductName(): string * @param string $productName : Name of the item product. * @return Item */ - public function setProductName(string $productName): static + public function setProductName(string $productName): self { $this->productName = $productName; return $this; @@ -125,7 +122,7 @@ public function getProductSku(): string * @param string $productSku * @return Item */ - public function setProductSku(string $productSku): static + public function setProductSku(string $productSku): self { $this->productSku = $productSku; return $this; @@ -147,7 +144,7 @@ public function getItemPrice(): ?float * @param ?float $itemPrice * @return Item */ - public function setItemPrice(?float $itemPrice): static + public function setItemPrice(?float $itemPrice): self { $this->itemPrice = $itemPrice; return $this; @@ -169,7 +166,7 @@ public function getItemQuantity(): ?int * @param ?int $itemQuantity * @return Item */ - public function setItemQuantity(?int $itemQuantity): static + public function setItemQuantity(?int $itemQuantity): self { $this->itemQuantity = $itemQuantity; return $this; @@ -191,7 +188,7 @@ public function getItemCategory(): ?string * @param ?string $itemCategory * @return Item */ - public function setItemCategory(?string $itemCategory): static + public function setItemCategory(?string $itemCategory): self { $this->itemCategory = $itemCategory; return $this; diff --git a/src/Hit/Page.php b/src/Hit/Page.php index 96c3af6a..ecd751ff 100644 --- a/src/Hit/Page.php +++ b/src/Hit/Page.php @@ -14,10 +14,7 @@ class Page extends HitAbstract { public const ERROR_MESSAGE = 'Page url is required'; - public static function getClassName(): string - { - return __CLASS__; - } + /** * @var string @@ -49,7 +46,7 @@ public function getPageUrl(): string * @param string $pageUrl * @return Page */ - public function setPageUrl(string $pageUrl): static + public function setPageUrl(string $pageUrl): self { $this->pageUrl = $pageUrl; return $this; diff --git a/src/Hit/Screen.php b/src/Hit/Screen.php index c551358c..b7bcdf76 100644 --- a/src/Hit/Screen.php +++ b/src/Hit/Screen.php @@ -14,13 +14,7 @@ class Screen extends HitAbstract { public const ERROR_MESSAGE = 'Screen name is required'; - /** - * @return string - */ - public static function getClassName(): string - { - return __CLASS__; - } + /** * @var string @@ -55,7 +49,7 @@ public function getScreenName(): string * @param string $screenName : Interface seen. * @return Screen */ - public function setScreenName(string $screenName): static + public function setScreenName(string $screenName): self { $this->screenName = $screenName; return $this; diff --git a/src/Hit/Segment.php b/src/Hit/Segment.php index a6748279..ba17d0a5 100644 --- a/src/Hit/Segment.php +++ b/src/Hit/Segment.php @@ -11,11 +11,6 @@ class Segment extends HitAbstract public const SL_MESSAGE_ERROR = "Sl value must be an associative array"; public const ERROR_MESSAGE = 'sl is required'; - public static function getClassName(): string - { - return __CLASS__; - } - /** * @var array */ @@ -30,10 +25,10 @@ public function getSl(): array } /** - * @param array $sl + * @param array $sl * @return Segment */ - public function setSl(array $sl): static + public function setSl(array $sl): self { if (!$this->isAssoc($sl)) { $this->logError($this->getConfig(), self::SL_MESSAGE_ERROR, [FlagshipConstant::TAG => __FUNCTION__]); @@ -44,7 +39,7 @@ public function setSl(array $sl): static } /** - * @param array $sl + * @param array $sl */ public function __construct(array $sl, FlagshipConfig $config) { @@ -54,7 +49,7 @@ public function __construct(array $sl, FlagshipConfig $config) } /** - * @param array $array + * @param array $array * @return bool */ protected function isAssoc(array $array): bool @@ -75,7 +70,10 @@ public function toApiKeys(): array if (is_bool($value)) { return $value ? 'true' : 'false'; } - return strval($value); + if (is_scalar($value)) { + return strval($value); + } + return ''; }, $this->getSl()); $arrayParent[FlagshipConstant::SL_API_ITEM] = $apiContext; @@ -87,7 +85,7 @@ public function toApiKeys(): array */ public function isReady(): bool { - return parent::isReady() && $this->getSl() && count($this->getSl()) > 0; + return parent::isReady() && !empty($this->getSl()); } /** diff --git a/src/Hit/Transaction.php b/src/Hit/Transaction.php index b7e69331..b8c9a517 100644 --- a/src/Hit/Transaction.php +++ b/src/Hit/Transaction.php @@ -15,13 +15,7 @@ class Transaction extends HitAbstract public const CURRENCY_ERROR = "'%s' must be a string and have exactly 3 letters"; public const ERROR_MESSAGE = 'Transaction Id and Transaction affiliation are required'; - /** - * @return string - */ - public static function getClassName(): string - { - return __CLASS__; - } + /** * @var string @@ -93,7 +87,7 @@ public function getTransactionId(): string * @param string $transactionId * @return Transaction */ - public function setTransactionId(string $transactionId): static + public function setTransactionId(string $transactionId): self { $this->transactionId = $transactionId; return $this; @@ -115,7 +109,7 @@ public function getAffiliation(): string * @param string $affiliation * @return Transaction */ - public function setAffiliation(string $affiliation): static + public function setAffiliation(string $affiliation): self { $this->affiliation = $affiliation; return $this; @@ -137,7 +131,7 @@ public function getTaxes(): ?float * @param ?float $taxes * @return Transaction */ - public function setTaxes(?float $taxes): static + public function setTaxes(?float $taxes): self { $this->taxes = $taxes; return $this; @@ -158,7 +152,7 @@ public function getCurrency(): ?string * @param ?string $currency * @return Transaction */ - public function setCurrency(?string $currency): static + public function setCurrency(?string $currency): self { $this->currency = $currency; return $this; @@ -180,7 +174,7 @@ public function getCouponCode(): ?string * @param ?string $couponCode * @return Transaction */ - public function setCouponCode(?string $couponCode): static + public function setCouponCode(?string $couponCode): self { $this->couponCode = $couponCode; return $this; @@ -202,7 +196,7 @@ public function getItemCount(): ?int * @param ?integer $itemsCount * @return Transaction */ - public function setItemCount(?int $itemsCount): static + public function setItemCount(?int $itemsCount): self { $this->itemCount = $itemsCount; return $this; @@ -224,7 +218,7 @@ public function getShippingMethod(): ?string * @param ?string $shippingMethod * @return Transaction */ - public function setShippingMethod(?string $shippingMethod): static + public function setShippingMethod(?string $shippingMethod): self { $this->shippingMethod = $shippingMethod; return $this; @@ -246,7 +240,7 @@ public function getPaymentMethod(): ?string * @param ?string $paymentMethod * @return Transaction */ - public function setPaymentMethod(?string $paymentMethod): static + public function setPaymentMethod(?string $paymentMethod): self { $this->paymentMethod = $paymentMethod; return $this; @@ -269,7 +263,7 @@ public function getTotalRevenue(): ?float * @param ?float $totalRevenue * @return Transaction */ - public function setTotalRevenue(?float $totalRevenue): static + public function setTotalRevenue(?float $totalRevenue): self { $this->totalRevenue = $totalRevenue; return $this; @@ -291,7 +285,7 @@ public function getShippingCosts(): ?float * @param ?float $shippingCosts * @return Transaction */ - public function setShippingCosts(?float $shippingCosts): static + public function setShippingCosts(?float $shippingCosts): self { $this->shippingCosts = $shippingCosts; return $this; diff --git a/src/Model/AccountSettingsDTO.php b/src/Model/AccountSettingsDTO.php new file mode 100644 index 00000000..17ba30c2 --- /dev/null +++ b/src/Model/AccountSettingsDTO.php @@ -0,0 +1,130 @@ +enabledXPC; + } + + public function setEnabledXPC(?bool $enabledXPC): self + { + $this->enabledXPC = $enabledXPC; + return $this; + } + + public function getTroubleshooting(): ?TroubleshootingDTO + { + return $this->troubleshooting; + } + + public function setTroubleshooting(?TroubleshootingDTO $troubleshooting): self + { + $this->troubleshooting = $troubleshooting; + return $this; + } + + public function getEaiCollectEnabled(): ?bool + { + return $this->eaiCollectEnabled; + } + + public function setEaiCollectEnabled(?bool $eaiCollectEnabled): self + { + $this->eaiCollectEnabled = $eaiCollectEnabled; + return $this; + } + + public function getEaiActivationEnabled(): ?bool + { + return $this->eaiActivationEnabled; + } + + public function setEaiActivationEnabled(?bool $eaiActivationEnabled): self + { + $this->eaiActivationEnabled = $eaiActivationEnabled; + return $this; + } + + /** + * Check if troubleshooting is enabled and valid + * + * @return bool + */ + public function isTroubleshootingEnabled(): bool + { + return $this->troubleshooting !== null; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $instance = new self(); + + if (isset($data[FlagshipField::ENABLED_XPC]) && is_bool($data[FlagshipField::ENABLED_XPC])) { + $instance->setEnabledXPC($data[FlagshipField::ENABLED_XPC]); + } + + if (isset($data[FlagshipField::TROUBLESHOOTING]) && is_array($data[FlagshipField::TROUBLESHOOTING])) { + /** @var array $troubleshootingData */ + $troubleshootingData = $data[FlagshipField::TROUBLESHOOTING]; + $instance->setTroubleshooting( + TroubleshootingDTO::fromArray($troubleshootingData) + ); + } + + if (isset($data[FlagshipField::EAI_COLLECT_ENABLED]) && is_bool($data[FlagshipField::EAI_COLLECT_ENABLED])) { + $instance->setEaiCollectEnabled($data[FlagshipField::EAI_COLLECT_ENABLED]); + } + + if (isset($data[FlagshipField::EAI_ACTIVATION_ENABLED]) && is_bool($data[FlagshipField::EAI_ACTIVATION_ENABLED])) { + $instance->setEaiActivationEnabled($data[FlagshipField::EAI_ACTIVATION_ENABLED]); + } + + return $instance; + } + + /** + * @return AccountSettingsArray + */ + public function toArray(): array + { + $result = []; + + if ($this->enabledXPC !== null) { + $result[FlagshipField::ENABLED_XPC] = $this->enabledXPC; + } + + if ($this->troubleshooting !== null) { + $result[FlagshipField::TROUBLESHOOTING] = $this->troubleshooting->toArray(); + } + + if ($this->eaiCollectEnabled !== null) { + $result[FlagshipField::EAI_COLLECT_ENABLED] = $this->eaiCollectEnabled; + } + + if ($this->eaiActivationEnabled !== null) { + $result[FlagshipField::EAI_ACTIVATION_ENABLED] = $this->eaiActivationEnabled; + } + + return $result; + } +} diff --git a/src/Model/BucketingCampaignDTO.php b/src/Model/BucketingCampaignDTO.php new file mode 100644 index 00000000..8d7f0374 --- /dev/null +++ b/src/Model/BucketingCampaignDTO.php @@ -0,0 +1,160 @@ + */ + private array $variationGroups; + + /** + * @param string $id + * @param string $type + * @param array $variationGroups + */ + public function __construct(string $id, string $type, array $variationGroups) + { + $this->id = $id; + $this->type = $type; + $this->variationGroups = $variationGroups; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): self + { + $this->slug = $slug; + return $this; + } + + /** + * @return array + */ + public function getVariationGroups(): array + { + return $this->variationGroups; + } + + /** + * @param array $variationGroups + */ + public function setVariationGroups(array $variationGroups): self + { + $this->variationGroups = $variationGroups; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** + * @var array>|null $variationGroupsData + */ + $variationGroupsData = $data[FlagshipField::FIELD_VARIATION_GROUPS] ?? null; + $variationGroups = []; + + if (is_array($variationGroupsData)) { + $variationGroups = array_map( + VariationGroupDTO::fromArray(...), + $variationGroupsData + ); + } + + $id = $data[FlagshipField::FIELD_ID] ?? ''; + $type = $data[FlagshipField::FIELD_CAMPAIGN_TYPE] ?? ''; + + $instance = new self( + is_string($id) ? $id : '', + is_string($type) ? $type : '', + $variationGroups + ); + + if (isset($data[FlagshipField::FIELD_NANE]) && is_string($data[FlagshipField::FIELD_NANE])) { + $instance->setName($data[FlagshipField::FIELD_NANE]); + } + + if (array_key_exists(FlagshipField::FIELD_SLUG, $data)) { + $slug = $data[FlagshipField::FIELD_SLUG]; + $instance->setSlug(is_string($slug) ? $slug : null); + } + + return $instance; + } + + /** + * @return BucketingCampaignArray + */ + public function toArray(): array + { + $result = [ + FlagshipField::FIELD_ID => $this->id, + FlagshipField::FIELD_CAMPAIGN_TYPE => $this->type, + FlagshipField::FIELD_VARIATION_GROUPS => array_map( + fn(VariationGroupDTO $group) => $group->toArray(), + $this->variationGroups + ), + ]; + + if ($this->name !== null) { + $result[FlagshipField::FIELD_NANE] = $this->name; + } + + if ($this->slug !== null) { + $result[FlagshipField::FIELD_SLUG] = $this->slug; + } + + return $result; + } +} diff --git a/src/Model/BucketingDTO.php b/src/Model/BucketingDTO.php new file mode 100644 index 00000000..06333f87 --- /dev/null +++ b/src/Model/BucketingDTO.php @@ -0,0 +1,115 @@ +|null */ + private ?array $campaigns = null; + + private ?AccountSettingsDTO $accountSettings = null; + + public function getPanic(): ?bool + { + return $this->panic; + } + + public function setPanic(?bool $panic): self + { + $this->panic = $panic; + return $this; + } + + /** + * @return array|null + */ + public function getCampaigns(): ?array + { + return $this->campaigns; + } + + /** + * @param array|null $campaigns + */ + public function setCampaigns(?array $campaigns): self + { + $this->campaigns = $campaigns; + return $this; + } + + public function getAccountSettings(): ?AccountSettingsDTO + { + return $this->accountSettings; + } + + public function setAccountSettings(?AccountSettingsDTO $accountSettings): self + { + $this->accountSettings = $accountSettings; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $instance = new self(); + + if (isset($data[FlagshipField::FIELD_PANIC]) && is_bool($data[FlagshipField::FIELD_PANIC])) { + $instance->setPanic($data[FlagshipField::FIELD_PANIC]); + } + + if (isset($data[FlagshipField::FIELD_CAMPAIGNS]) && is_array($data[FlagshipField::FIELD_CAMPAIGNS])) { + /** @var array> $campaignsData */ + $campaignsData = $data[FlagshipField::FIELD_CAMPAIGNS]; + $campaigns = array_map( + BucketingCampaignDTO::fromArray(...), + $campaignsData + ); + $instance->setCampaigns($campaigns); + } + + if (isset($data[FlagshipField::ACCOUNT_SETTINGS]) && is_array($data[FlagshipField::ACCOUNT_SETTINGS])) { + /** @var array $accountSettingsData */ + $accountSettingsData = $data[FlagshipField::ACCOUNT_SETTINGS]; + $instance->setAccountSettings( + AccountSettingsDTO::fromArray($accountSettingsData) + ); + } + + return $instance; + } + + /** + * @return BucketingArray + */ + public function toArray(): array + { + $result = []; + + if ($this->panic !== null) { + $result[FlagshipField::FIELD_PANIC] = $this->panic; + } + + if ($this->campaigns !== null) { + $result[FlagshipField::FIELD_CAMPAIGNS] = array_map( + fn(BucketingCampaignDTO $campaign) => $campaign->toArray(), + $this->campaigns + ); + } + + if ($this->accountSettings !== null) { + $result[FlagshipField::ACCOUNT_SETTINGS] = $this->accountSettings->toArray(); + } + + return $result; + } +} diff --git a/src/Model/BucketingVariationDTO.php b/src/Model/BucketingVariationDTO.php new file mode 100644 index 00000000..10883acb --- /dev/null +++ b/src/Model/BucketingVariationDTO.php @@ -0,0 +1,68 @@ +allocation; + } + + public function setAllocation(?float $allocation): self + { + $this->allocation = $allocation; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + + $modificationsData = $data[FlagshipField::FIELD_MODIFICATIONS] ?? null; + $modifications = is_array($modificationsData) + ? ModificationsDTO::fromArray($modificationsData) + : new ModificationsDTO('', []); + + $id = $data[FlagshipField::FIELD_ID] ?? ''; + + $instance = new self(is_string($id) ? $id : '', $modifications); + + if (isset($data[FlagshipField::FIELD_NANE]) && is_string($data[FlagshipField::FIELD_NANE])) { + $instance->setName($data[FlagshipField::FIELD_NANE]); + } + + if (isset($data[FlagshipField::FIELD_REFERENCE]) && is_bool($data[FlagshipField::FIELD_REFERENCE])) { + $instance->setReference($data[FlagshipField::FIELD_REFERENCE]); + } + + if (isset($data[FlagshipField::FIELD_ALLOCATION])) { + $allocation = $data[FlagshipField::FIELD_ALLOCATION]; + $instance->setAllocation(is_numeric($allocation) ? (float)$allocation : null); + } + + return $instance; + } + + /** + * @return BucketingVariationArray + */ + public function toArray(): array + { + $result = parent::toArray(); + + $result[FlagshipField::FIELD_ALLOCATION] = $this->allocation; + + return $result; + } +} diff --git a/src/Model/CampaignCacheDTO.php b/src/Model/CampaignCacheDTO.php new file mode 100644 index 00000000..666fab01 --- /dev/null +++ b/src/Model/CampaignCacheDTO.php @@ -0,0 +1,228 @@ +campaignId = $campaignId; + $this->variationGroupId = $variationGroupId; + $this->variationId = $variationId; + $this->flags = $flags; + } + + public function getCampaignId(): string + { + return $this->campaignId; + } + + public function setCampaignId(string $campaignId): self + { + $this->campaignId = $campaignId; + return $this; + } + + public function getVariationGroupId(): string + { + return $this->variationGroupId; + } + + public function setVariationGroupId(string $variationGroupId): self + { + $this->variationGroupId = $variationGroupId; + return $this; + } + + public function getVariationId(): string + { + return $this->variationId; + } + + public function setVariationId(string $variationId): self + { + $this->variationId = $variationId; + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): self + { + $this->type = $type; + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): self + { + $this->slug = $slug; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + public function getIsReference(): ?bool + { + return $this->isReference; + } + + public function setIsReference(?bool $isReference): self + { + $this->isReference = $isReference; + return $this; + } + + public function getActivated(): ?bool + { + return $this->activated; + } + + public function setActivated(?bool $activated): self + { + $this->activated = $activated; + return $this; + } + + /** + * @return ModificationsDTO + */ + public function getFlags(): ModificationsDTO + { + return $this->flags; + } + + /** + * @param ModificationsDTO $flags + */ + public function setFlags(ModificationsDTO $flags): self + { + $this->flags = $flags; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $campaignId = $data[StrategyAbstract::CAMPAIGN_ID] ?? ''; + $variationGroupId = $data[StrategyAbstract::VARIATION_GROUP_ID] ?? ''; + $variationId = $data[StrategyAbstract::VARIATION_ID] ?? ''; + + $flags = null; + + if (isset($data[FlagshipField::FIELD_FLAGS]) && is_array($data[FlagshipField::FIELD_FLAGS])) { + $flags = ModificationsDTO::fromArray($data[FlagshipField::FIELD_FLAGS]); + } + + $instance = new self( + is_string($campaignId) ? $campaignId : '', + is_string($variationGroupId) ? $variationGroupId : '', + is_string($variationId) ? $variationId : '', + $flags ?? new ModificationsDTO('', []) + ); + + if (isset($data[FlagshipField::FIELD_CAMPAIGN_TYPE]) && is_string($data[FlagshipField::FIELD_CAMPAIGN_TYPE])) { + $instance->setType($data[FlagshipField::FIELD_CAMPAIGN_TYPE]); + } + + if (isset($data[FlagshipField::FIELD_SLUG]) && is_string($data[FlagshipField::FIELD_SLUG])) { + $instance->setSlug(FlagshipField::FIELD_SLUG); + } + + if (isset($data[FlagshipField::FIELD_CAMPAIGN_NAME]) && is_string($data[FlagshipField::FIELD_CAMPAIGN_NAME])) { + $instance->setName($data[FlagshipField::FIELD_CAMPAIGN_NAME]); + } + + if (isset($data[FlagshipField::FIELD_IS_REFERENCE]) && is_bool($data[FlagshipField::FIELD_IS_REFERENCE])) { + $instance->setIsReference($data[FlagshipField::FIELD_IS_REFERENCE]); + } + + if (isset($data[FlagshipField::FIELD_ACTIVATED]) && is_bool($data[FlagshipField::FIELD_ACTIVATED])) { + $instance->setActivated($data[FlagshipField::FIELD_ACTIVATED]); + } + + + + return $instance; + } + + /** + * @return CampaignCacheArray + */ + public function toArray(): array + { + $result = [ + StrategyAbstract::CAMPAIGN_ID => $this->campaignId, + StrategyAbstract::VARIATION_GROUP_ID => $this->variationGroupId, + StrategyAbstract::VARIATION_ID => $this->variationId, + FlagshipField::FIELD_CAMPAIGN_TYPE => $this->type, + FlagshipField::FIELD_FLAGS => $this->flags->toArray(), + ]; + + if ($this->slug !== null) { + $result[FlagshipField::FIELD_SLUG] = $this->slug; + } + + if ($this->name !== null) { + $result[FlagshipField::FIELD_CAMPAIGN_NAME] = $this->name; + } + + if ($this->isReference !== null) { + $result[FlagshipField::FIELD_IS_REFERENCE] = $this->isReference; + } + + if ($this->activated !== null) { + $result[FlagshipField::FIELD_ACTIVATED] = $this->activated; + } + + return $result; + } +} diff --git a/src/Model/CampaignDTO.php b/src/Model/CampaignDTO.php new file mode 100644 index 00000000..1e0b6974 --- /dev/null +++ b/src/Model/CampaignDTO.php @@ -0,0 +1,174 @@ +id = $id; + $this->variationGroupId = $variationGroupId; + $this->variation = $variation; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): self + { + $this->slug = $slug; + return $this; + } + + public function getVariationGroupId(): string + { + return $this->variationGroupId; + } + + public function setVariationGroupId(string $variationGroupId): self + { + $this->variationGroupId = $variationGroupId; + return $this; + } + + public function getVariationGroupName(): ?string + { + return $this->variationGroupName; + } + + public function setVariationGroupName(?string $variationGroupName): self + { + $this->variationGroupName = $variationGroupName; + return $this; + } + + public function getVariation(): VariationDTO + { + return $this->variation; + } + + public function setVariation(VariationDTO $variation): self + { + $this->variation = $variation; + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(?string $type): self + { + $this->type = $type; + return $this; + } + + /** + * @param array $data + */ + public static function fromArray(array $data): self + { + $variationData = $data[FlagshipField::FIELD_VARIATION] ?? []; + $variation = is_array($variationData) + ? VariationDTO::fromArray($variationData) + : new VariationDTO('', + new ModificationsDTO('', [])); + + $id = $data[FlagshipField::FIELD_ID] ?? ''; + $variationGroupId = $data[FlagshipField::FIELD_VARIATION_GROUP_ID] ?? ''; + + $instance = new self( + is_string($id) ? $id : '', + is_string($variationGroupId) ? $variationGroupId : '', + $variation + ); + + if (isset($data[FlagshipField::FIELD_NANE]) && is_string($data[FlagshipField::FIELD_NANE])) { + $instance->setName($data[FlagshipField::FIELD_NANE]); + } + + if (isset($data[FlagshipField::FIELD_SLUG]) && is_string($data[FlagshipField::FIELD_SLUG])) { + $instance->setSlug($data[FlagshipField::FIELD_SLUG]); + } + + if (isset($data[FlagshipField::FIELD_VARIATION_GROUP_NAME]) && is_string($data[FlagshipField::FIELD_VARIATION_GROUP_NAME])) { + $instance->setVariationGroupName($data[FlagshipField::FIELD_VARIATION_GROUP_NAME]); + } + + if (isset($data[FlagshipField::FIELD_CAMPAIGN_TYPE]) && is_string($data[FlagshipField::FIELD_CAMPAIGN_TYPE])) { + $instance->setType($data[FlagshipField::FIELD_CAMPAIGN_TYPE]); + } + + return $instance; + } + + /** + * @return CampaignArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_ID => $this->id, + FlagshipField::FIELD_NANE => $this->name, + FlagshipField::FIELD_SLUG => $this->slug, + FlagshipField::FIELD_VARIATION_GROUP_ID => $this->variationGroupId, + FlagshipField::FIELD_VARIATION_GROUP_NAME => $this->variationGroupName, + FlagshipField::FIELD_VARIATION => $this->variation->toArray(), + FlagshipField::FIELD_CAMPAIGN_TYPE => $this->type, + ]; + } + + public function jsonSerialize(): mixed + { + return $this->toArray(); + } +} diff --git a/src/Model/ExposedFlag.php b/src/Model/ExposedFlag.php index 239c7b10..3eb0f098 100644 --- a/src/Model/ExposedFlag.php +++ b/src/Model/ExposedFlag.php @@ -12,7 +12,7 @@ class ExposedFlag implements ExposedFlagInterface private string $key; /** - * @var bool|numeric|string|array + * @var scalar|array|null */ private string|array|bool|int|float|null $value; @@ -22,14 +22,14 @@ class ExposedFlag implements ExposedFlagInterface private FSFlagMetadataInterface $metadata; /** - * @var bool|numeric|string|array + * @var scalar|array|null */ private string|array|bool|int|float|null $defaultValue; /** * @param string $key - * @param array|bool|string|numeric $value - * @param array|bool|string|numeric $defaultValue + * @param scalar|array|null $value + * @param scalar|array|null $defaultValue * @param FSFlagMetadataInterface $metadata */ public function __construct( @@ -53,9 +53,9 @@ public function getKey(): string } /** - * @return bool|numeric|string|array + * @inheritDoc */ - public function getValue(): float|int|bool|array|string + public function getValue(): float|int|bool|array|string|null { return $this->value; } @@ -71,7 +71,7 @@ public function getMetadata(): FSFlagMetadataInterface /** * @inheritDoc */ - public function getDefaultValue(): float|array|bool|int|string + public function getDefaultValue(): float|array|bool|int|string|null { return $this->defaultValue; } diff --git a/src/Model/ExposedFlagInterface.php b/src/Model/ExposedFlagInterface.php index 7ac4b83b..065c525d 100644 --- a/src/Model/ExposedFlagInterface.php +++ b/src/Model/ExposedFlagInterface.php @@ -14,7 +14,7 @@ public function getKey(): string; /** * Return the value of flag - * @return float|array|bool|int|string|null + * @return scalar|array|null */ public function getValue(): float|array|bool|int|string|null; @@ -26,7 +26,7 @@ public function getMetadata(): FSFlagMetadataInterface; /** * Return the default value of flag - * @return float|array|bool|int|string|null + * @return scalar|array|null */ public function getDefaultValue(): float|array|bool|int|string|null; } diff --git a/src/Model/ExposedVisitor.php b/src/Model/ExposedVisitor.php index 65e7e340..7ded2244 100644 --- a/src/Model/ExposedVisitor.php +++ b/src/Model/ExposedVisitor.php @@ -18,14 +18,14 @@ class ExposedVisitor implements ExposedVisitorInterface private ?string $anonymousId; /** - * @var array + * @var array */ private array $context; /** * @param string $id * @param ?string $anonymousId - * @param array $context + * @param array $context */ public function __construct(string $id, ?string $anonymousId, array $context) { @@ -51,7 +51,7 @@ public function getAnonymousId(): ?string } /** - * @return array + * @return array */ public function getContext(): array { diff --git a/src/Model/FlagDTO.php b/src/Model/FlagDTO.php index 6b833d4d..ac01f17c 100644 --- a/src/Model/FlagDTO.php +++ b/src/Model/FlagDTO.php @@ -48,8 +48,9 @@ class FlagDTO implements JsonSerializable * @var bool */ private bool $isReference; + /** - * @var string|bool|numeric + * @var scalar|array|null */ private string|int|bool|float|null|array $value; @@ -75,7 +76,7 @@ public function getKey(): string * @param string $key * @return FlagDTO */ - public function setKey(string $key): static + public function setKey(string $key): self { $this->key = $key; return $this; @@ -93,7 +94,7 @@ public function getCampaignId(): string * @param string $campaignId * @return FlagDTO */ - public function setCampaignId(string $campaignId): static + public function setCampaignId(string $campaignId): self { $this->campaignId = $campaignId; return $this; @@ -111,7 +112,7 @@ public function getVariationGroupId(): string * @param string $variationGroupId * @return FlagDTO */ - public function setVariationGroupId(string $variationGroupId): static + public function setVariationGroupId(string $variationGroupId): self { $this->variationGroupId = $variationGroupId; return $this; @@ -129,7 +130,7 @@ public function getVariationId(): string * @param string $variationId * @return FlagDTO */ - public function setVariationId(string $variationId): static + public function setVariationId(string $variationId): self { $this->variationId = $variationId; return $this; @@ -147,14 +148,14 @@ public function getIsReference(): bool * @param bool $isReference * @return FlagDTO */ - public function setIsReference(bool $isReference): static + public function setIsReference(bool $isReference): self { $this->isReference = $isReference; return $this; } /** - * @return float|bool|int|string|array|null + * @return scalar|array|null $value */ public function getValue(): float|bool|int|string|null|array { @@ -162,10 +163,10 @@ public function getValue(): float|bool|int|string|null|array } /** - * @param bool|string|numeric $value + * @param scalar|array|null $value * @return FlagDTO */ - public function setValue(float|bool|int|string|null|array $value): static + public function setValue(float|bool|int|string|null|array $value): self { $this->value = $value; return $this; @@ -183,7 +184,7 @@ public function getCampaignType(): string * @param string $campaignType * @return FlagDTO */ - public function setCampaignType(string $campaignType): static + public function setCampaignType(string $campaignType): self { $this->campaignType = $campaignType; return $this; @@ -201,7 +202,7 @@ public function getSlug(): ?string * @param string $slug * @return FlagDTO */ - public function setSlug(string $slug): static + public function setSlug(string $slug): self { $this->slug = $slug; return $this; @@ -219,7 +220,7 @@ public function getCampaignName(): ?string * @param string $campaignName * @return FlagDTO */ - public function setCampaignName(string $campaignName): static + public function setCampaignName(string $campaignName): self { $this->campaignName = $campaignName; return $this; @@ -237,7 +238,7 @@ public function getVariationGroupName(): ?string * @param string $variationGroupName * @return FlagDTO */ - public function setVariationGroupName(string $variationGroupName): static + public function setVariationGroupName(string $variationGroupName): self { $this->variationGroupName = $variationGroupName; return $this; @@ -255,7 +256,7 @@ public function getVariationName(): ?string * @param string $variationName * @return FlagDTO */ - public function setVariationName(string $variationName): static + public function setVariationName(string $variationName): self { $this->variationName = $variationName; return $this; @@ -264,16 +265,16 @@ public function setVariationName(string $variationName): static public function jsonSerialize(): mixed { return [ - FlagshipField::FIELD_KEY => $this->getKey(), - FlagshipField::FIELD_CAMPAIGN_ID => $this->getCampaignId(), - FlagshipField::FIELD_CAMPAIGN_NAME => $this->getCampaignName(), - FlagshipField::FIELD_VARIATION_GROUP_ID => $this->getVariationGroupId(), - FlagshipField::FIELD_VARIATION_GROUP_NAME => $this->getVariationGroupName(), - FlagshipField::FIELD_VARIATION_ID => $this->getVariationId(), - FlagshipField::FIELD_VARIATION_NAME => $this->getVariationName(), - FlagshipField::FIELD_IS_REFERENCE => $this->getIsReference(), - FlagshipField::FIELD_VALUE => $this->getValue(), - FlagshipField::FIELD_SLUG => $this->getSlug(), - ]; + FlagshipField::FIELD_KEY => $this->getKey(), + FlagshipField::FIELD_CAMPAIGN_ID => $this->getCampaignId(), + FlagshipField::FIELD_CAMPAIGN_NAME => $this->getCampaignName(), + FlagshipField::FIELD_VARIATION_GROUP_ID => $this->getVariationGroupId(), + FlagshipField::FIELD_VARIATION_GROUP_NAME => $this->getVariationGroupName(), + FlagshipField::FIELD_VARIATION_ID => $this->getVariationId(), + FlagshipField::FIELD_VARIATION_NAME => $this->getVariationName(), + FlagshipField::FIELD_IS_REFERENCE => $this->getIsReference(), + FlagshipField::FIELD_VALUE => $this->getValue(), + FlagshipField::FIELD_SLUG => $this->getSlug(), + ]; } } diff --git a/src/Model/HttpResponse.php b/src/Model/HttpResponse.php index 0471dd76..ce6d1e50 100644 --- a/src/Model/HttpResponse.php +++ b/src/Model/HttpResponse.php @@ -11,7 +11,7 @@ class HttpResponse private string|int $statusCode; private mixed $body; /** - * @var array + * @var array $headers */ private array $headers; @@ -19,7 +19,7 @@ class HttpResponse * HttpResponse constructor. * @param string|int $statusCode * @param mixed $body - * @param array $headers + * @param array $headers */ public function __construct( string|int $statusCode, @@ -40,10 +40,10 @@ public function getStatusCode(): string|int } /** - * @param mixed $statusCode + * @param string|int $statusCode * @return HttpResponse */ - public function setStatusCode(mixed $statusCode): static + public function setStatusCode(string|int $statusCode): self { $this->statusCode = $statusCode; return $this; @@ -61,14 +61,14 @@ public function getBody(): mixed * @param mixed $body * @return HttpResponse */ - public function setBody(mixed $body): static + public function setBody(mixed $body): self { $this->body = $body; return $this; } /** - * @return array + * @return array */ public function getHeaders(): array { diff --git a/src/Model/ModificationsDTO.php b/src/Model/ModificationsDTO.php new file mode 100644 index 00000000..b5a982ca --- /dev/null +++ b/src/Model/ModificationsDTO.php @@ -0,0 +1,90 @@ +type = $type; + $this->value = $value; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + + /** + * + * @return FlagValue + */ + public function getValue(): array + { + return $this->value; + } + + /** + * + * @param FlagValue $value + * @return self + */ + public function setValue(array $value): self + { + $this->value = $value; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $type = $data[FlagshipField::FIELD_CAMPAIGN_TYPE] ?? ''; + + /** @var FlagValue|null $value */ + $value = $data[FlagshipField::FIELD_VALUE] ?? null; + + return new self( + is_string($type) ? $type : '', + is_array($value) ? $value : [] + ); + } + + /** + * @return ModificationsArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_CAMPAIGN_TYPE => $this->type, + FlagshipField::FIELD_VALUE => $this->value, + ]; + } +} diff --git a/src/Model/TargetingDTO.php b/src/Model/TargetingDTO.php new file mode 100644 index 00000000..fcf80bf8 --- /dev/null +++ b/src/Model/TargetingDTO.php @@ -0,0 +1,75 @@ + */ + private array $targetingGroups; + + /** + * @param array $targetingGroups + */ + public function __construct(array $targetingGroups) + { + $this->targetingGroups = $targetingGroups; + } + + /** + * @return array + */ + public function getTargetingGroups(): array + { + return $this->targetingGroups; + } + + /** + * @param array $targetingGroups + */ + public function setTargetingGroups(array $targetingGroups): self + { + $this->targetingGroups = $targetingGroups; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** + * @var array> | null $targetingGroupsData + */ + $targetingGroupsData = $data[FlagshipField::FIELD_TARGETING_GROUPS] ?? []; + + $targetingGroups = []; + + if (is_array($targetingGroupsData)) { + $targetingGroups = array_map( + TargetingGroupDTO::fromArray(...), + $targetingGroupsData + ); + } + + return new self(array_values($targetingGroups)); + } + + /** + * @return TargetingArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_TARGETING_GROUPS => array_map( + fn(TargetingGroupDTO $group) => $group->toArray(), + $this->targetingGroups + ), + ]; + } +} diff --git a/src/Model/TargetingGroupDTO.php b/src/Model/TargetingGroupDTO.php new file mode 100644 index 00000000..db3522dc --- /dev/null +++ b/src/Model/TargetingGroupDTO.php @@ -0,0 +1,74 @@ + */ + private array $targetings; + + /** + * @param array $targetings + */ + public function __construct(array $targetings) + { + $this->targetings = $targetings; + } + + /** + * @return array + */ + public function getTargetings(): array + { + return $this->targetings; + } + + /** + * @param array $targetings + */ + public function setTargetings(array $targetings): self + { + $this->targetings = $targetings; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** + * @var array> | null $targetingsData + */ + $targetingsData = $data[FlagshipField::FIELD_TARGETINGS] ?? []; + $targetings = []; + + if (is_array($targetingsData)) { + $targetings = array_map( + fn($targeting) => TargetingsDTO::fromArray($targeting), + $targetingsData + ); + } + + return new self($targetings); + } + + /** + * @return TargetingGroupArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_TARGETINGS => array_map( + fn(TargetingsDTO $targeting) => $targeting->toArray(), + $this->targetings + ), + ]; + } +} diff --git a/src/Model/TargetingsDTO.php b/src/Model/TargetingsDTO.php new file mode 100644 index 00000000..50425e67 --- /dev/null +++ b/src/Model/TargetingsDTO.php @@ -0,0 +1,110 @@ +operator; + } + + public function setOperator(TargetingOperator $operator): self + { + $this->operator = $operator; + return $this; + } + + public function getKey(): string + { + return $this->key; + } + + public function setKey(string $key): self + { + $this->key = $key; + return $this; + } + + /** + * + * @return mixed + */ + public function getValue(): mixed + { + return $this->value; + } + + /** + * + * @param mixed $value + * @return self + */ + public function setValue(mixed $value): self + { + $this->value = $value; + return $this; + } + + /** + * + * @param TargetingOperator $operator + * @param string $key + * @param mixed $value + */ + public function __construct(TargetingOperator $operator, string $key, mixed $value) + { + $this->operator = $operator; + $this->key = $key; + $this->value = $value; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $operatorValue = $data[FlagshipField::FIELD_OPERATOR] ?? ''; + $operator = is_string($operatorValue) + ? (TargetingOperator::tryFrom($operatorValue) ?? TargetingOperator::EQUALS) + : TargetingOperator::EQUALS; + + $key = $data[FlagshipField::FIELD_KEY] ?? ''; + + /** @var mixed|null $value */ + $value = $data[FlagshipField::FIELD_VALUE] ?? null; + + return new self( + $operator, + is_string($key) ? $key : '', + $value, + ); + } + + /** + * @return TargetingsArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_OPERATOR => $this->operator->value, + FlagshipField::FIELD_KEY => $this->key, + FlagshipField::FIELD_VALUE => $this->value, + ]; + } +} diff --git a/src/Model/TroubleshootingDTO.php b/src/Model/TroubleshootingDTO.php new file mode 100644 index 00000000..5995f133 --- /dev/null +++ b/src/Model/TroubleshootingDTO.php @@ -0,0 +1,160 @@ +startDate = $startDate; + $this->endDate = $endDate; + $this->traffic = $traffic; + $this->timezone = $timezone; + } + + public function getStartDate(): string + { + return $this->startDate; + } + + public function setStartDate(string $startDate): self + { + $this->startDate = $startDate; + return $this; + } + + /** + * Get start date as DateTime object + * + * @return DateTime|null + */ + public function getStartDateAsDateTime(): ?DateTime + { + try { + return new DateTime($this->startDate); + } catch (Exception $e) { + return null; + } + } + + public function getEndDate(): string + { + return $this->endDate; + } + + public function setEndDate(string $endDate): self + { + $this->endDate = $endDate; + return $this; + } + + /** + * Get end date as DateTime object + * + * @return DateTime|null + */ + public function getEndDateAsDateTime(): ?DateTime + { + try { + return new DateTime($this->endDate); + } catch (Exception $e) { + return null; + } + } + + public function getTraffic(): float + { + return $this->traffic; + } + + public function setTraffic(float $traffic): self + { + $this->traffic = $traffic; + return $this; + } + + public function getTimezone(): string + { + return $this->timezone; + } + + public function setTimezone(string $timezone): self + { + $this->timezone = $timezone; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $startDate = $data[FlagshipField::START_DATE] ?? ''; + $endDate = $data[FlagshipField::END_DATE] ?? ''; + $traffic = $data[FlagshipField::TRAFFIC] ?? 0; + $timezone = $data[FlagshipField::TIMEZONE] ?? ''; + + return new self( + is_string($startDate) ? $startDate : '', + is_string($endDate) ? $endDate : '', + is_numeric($traffic) ? (float)$traffic : 0.0, + is_string($timezone) ? $timezone : '' + ); + } + + /** + * @return TroubleshootingArray + */ + public function toArray(): array + { + return [ + FlagshipField::START_DATE => $this->startDate, + FlagshipField::END_DATE => $this->endDate, + FlagshipField::TRAFFIC => $this->traffic, + FlagshipField::TIMEZONE => $this->timezone, + ]; + } + + /** + * Convert to legacy TroubleshootingData format + * + * @return TroubleshootingData|null + */ + public function toTroubleshootingData(): ?TroubleshootingData + { + $startDateTime = $this->getStartDateAsDateTime(); + $endDateTime = $this->getEndDateAsDateTime(); + + if (!$startDateTime || !$endDateTime) { + return null; + } + + $data = new TroubleshootingData(); + $data->setStartDate($startDateTime) + ->setEndDate($endDateTime) + ->setTraffic($this->traffic) + ->setTimezone($this->timezone); + + return $data; + } +} diff --git a/src/Model/TroubleshootingData.php b/src/Model/TroubleshootingData.php index 46e6f4d6..b4668006 100644 --- a/src/Model/TroubleshootingData.php +++ b/src/Model/TroubleshootingData.php @@ -17,9 +17,9 @@ class TroubleshootingData private DateTime $endDate; /** - * @var numeric + * @var int|float $traffic */ - private string|int|float $traffic; + private int|float $traffic; /** * @var string @@ -38,7 +38,7 @@ public function getStartDate(): DateTime * @param DateTime $startDate * @return TroubleshootingData */ - public function setStartDate(DateTime $startDate): static + public function setStartDate(DateTime $startDate): self { $this->startDate = $startDate; return $this; @@ -56,7 +56,7 @@ public function getEndDate(): DateTime * @param DateTime $endDate * @return TroubleshootingData */ - public function setEndDate(DateTime $endDate): static + public function setEndDate(DateTime $endDate): self { $this->endDate = $endDate; return $this; @@ -74,7 +74,7 @@ public function getTraffic(): float|int * @param float|int $traffic * @return TroubleshootingData */ - public function setTraffic(float|int $traffic): static + public function setTraffic(float|int $traffic): self { $this->traffic = $traffic; return $this; @@ -92,7 +92,7 @@ public function getTimezone(): string * @param string $timezone * @return TroubleshootingData */ - public function setTimezone(string $timezone): static + public function setTimezone(string $timezone): self { $this->timezone = $timezone; return $this; diff --git a/src/Model/Types.php b/src/Model/Types.php new file mode 100644 index 00000000..b06e7d8a --- /dev/null +++ b/src/Model/Types.php @@ -0,0 +1,33 @@ +, assignmentsHistory?: array, campaigns?: array} + * @phpstan-type VisitorCacheArray array{version: int, data: VisitorCacheDataArray} + * @phpstan-type FlagValue array|bool|float|int|string|null> + * @phpstan-type TargetingsArray array{operator: string, key: string, value: mixed} + * @phpstan-type TargetingGroupArray array{targetings: array} + * @phpstan-type TargetingArray array{targetingGroups: array} + * @phpstan-type BucketingVariationArray array{id: string, name: string|null, modifications: ModificationsArray, allocation: float|null, reference: bool|null} + * @phpstan-type VariationGroupArray array{id: string, name?: string, targeting: TargetingArray, variations: array} + * @phpstan-type TroubleshootingArray array{startDate: string, endDate: string, traffic: float, timezone: string} + * @phpstan-type AccountSettingsArray array{enabledXPC?: bool, troubleshooting?: TroubleshootingArray, eaiCollectEnabled?: bool, eaiActivationEnabled?: bool} + * @phpstan-type BucketingCampaignArray array{id: string, name?: string, type: string, slug?: string|null, variationGroups: array} + * @phpstan-type BucketingArray array{panic?: bool, campaigns?: array, accountSettings?: AccountSettingsArray} + * @phpstan-type HitCacheDataArray array{visitorId: string, anonymousId?: string|null, type: string, time: float|null, content: array} + * @phpstan-type HitCacheArray array{version: int, data: HitCacheDataArray} + */ +final class Types +{ + private function __construct() {} +} diff --git a/src/Model/VariationDTO.php b/src/Model/VariationDTO.php new file mode 100644 index 00000000..583addef --- /dev/null +++ b/src/Model/VariationDTO.php @@ -0,0 +1,107 @@ +id = $id; + $this->modifications = $modifications; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + public function getReference(): ?bool + { + return $this->reference; + } + + public function setReference(?bool $reference): self + { + $this->reference = $reference; + return $this; + } + + public function getModifications(): ModificationsDTO + { + return $this->modifications; + } + + public function setModifications(ModificationsDTO $modifications): self + { + $this->modifications = $modifications; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $modificationsData = $data[FlagshipField::FIELD_MODIFICATIONS]?? null; + $modifications = is_array($modificationsData) + ? ModificationsDTO::fromArray($modificationsData) + : new ModificationsDTO('', []); + + $id = $data[FlagshipField::FIELD_ID] ?? ''; + $instance = new self(is_string($id) ? $id : '', $modifications); + + if (isset($data[FlagshipField::FIELD_NANE]) && is_string($data[FlagshipField::FIELD_NANE])) { + $instance->setName($data[FlagshipField::FIELD_NANE]); + } + + if (isset($data[FlagshipField::FIELD_REFERENCE]) && is_bool($data[FlagshipField::FIELD_REFERENCE])) { + $instance->setReference($data[FlagshipField::FIELD_REFERENCE]); + } + + return $instance; + } + + /** + * @return VariationArray + */ + public function toArray(): array + { + return [ + FlagshipField::FIELD_ID => $this->id, + FlagshipField::FIELD_NANE => $this->name, + FlagshipField::FIELD_REFERENCE => $this->reference, + FlagshipField::FIELD_MODIFICATIONS => $this->modifications->toArray(), + ]; + } +} diff --git a/src/Model/VariationGroupDTO.php b/src/Model/VariationGroupDTO.php new file mode 100644 index 00000000..115af712 --- /dev/null +++ b/src/Model/VariationGroupDTO.php @@ -0,0 +1,144 @@ + */ + private array $variations; + + /** + * @param string $id + * @param TargetingDTO $targeting + * @param array $variations + */ + public function __construct(string $id, TargetingDTO $targeting, array $variations) + { + $this->id = $id; + $this->targeting = $targeting; + $this->variations = $variations; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + public function getTargeting(): TargetingDTO + { + return $this->targeting; + } + + public function setTargeting(TargetingDTO $targeting): self + { + $this->targeting = $targeting; + return $this; + } + + /** + * @return array + */ + public function getVariations(): array + { + return $this->variations; + } + + /** + * @param array $variations + */ + public function setVariations(array $variations): self + { + $this->variations = $variations; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + /** + * @var array| null $targetingData + */ + $targetingData = $data[FlagshipField::FIELD_TARGETING] ?? null; + + $targeting = is_array($targetingData) + ? TargetingDTO::fromArray($targetingData) + : new TargetingDTO([]); + + /** + * @var array>|null $variationsData + */ + $variationsData = $data[FlagshipField::FIELD_VARIATIONS] ?? null; + $variations = []; + if (is_array($variationsData)) { + $variations = array_map( + BucketingVariationDTO::fromArray(...), + $variationsData + ); + } + + $id = $data[FlagshipField::FIELD_ID] ?? ''; + $instance = new self( + is_string($id) ? $id : '', + $targeting, + $variations + ); + + if (isset($data[FlagshipField::FIELD_NANE]) && is_string($data[FlagshipField::FIELD_NANE])) { + $instance->setName($data[FlagshipField::FIELD_NANE]); + } + + return $instance; + } + + /** + * @return VariationGroupArray + */ + public function toArray(): array + { + $result = [ + FlagshipField::FIELD_ID => $this->id, + FlagshipField::FIELD_TARGETING => $this->targeting->toArray(), + FlagshipField::FIELD_VARIATIONS => array_map( + fn(BucketingVariationDTO $variation) => $variation->toArray(), + $this->variations + ), + ]; + + if ($this->name !== null) { + $result[FlagshipField::FIELD_NANE] = $this->name; + } + + return $result; + } +} diff --git a/src/Model/VisitorCacheDTO.php b/src/Model/VisitorCacheDTO.php new file mode 100644 index 00000000..c24fe3d1 --- /dev/null +++ b/src/Model/VisitorCacheDTO.php @@ -0,0 +1,71 @@ +version = $version; + $this->data = $data; + } + + public function getVersion(): int + { + return $this->version; + } + + public function setVersion(int $version): self + { + $this->version = $version; + return $this; + } + + public function getData(): VisitorCacheDataDTO + { + return $this->data; + } + + public function setData(VisitorCacheDataDTO $data): self + { + $this->data = $data; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $version = $data[StrategyAbstract::VERSION] ?? StrategyAbstract::CURRENT_VERSION; + $dataArray = $data[StrategyAbstract::DATA] ?? []; + + return new self( + is_int($version) ? $version : StrategyAbstract::CURRENT_VERSION, + is_array($dataArray) ? VisitorCacheDataDTO::fromArray($dataArray) : new VisitorCacheDataDTO('', null) + ); + } + + /** + * @return VisitorCacheArray + */ + public function toArray(): array + { + return [ + StrategyAbstract::VERSION => $this->version, + StrategyAbstract::DATA => $this->data->toArray(), + ]; + } +} diff --git a/src/Model/VisitorCacheDataDTO.php b/src/Model/VisitorCacheDataDTO.php new file mode 100644 index 00000000..5b9a2dbb --- /dev/null +++ b/src/Model/VisitorCacheDataDTO.php @@ -0,0 +1,199 @@ +|null */ + private ?array $context = null; + + /** @var array|null */ + private ?array $assignmentsHistory = null; + + /** @var array|null */ + private ?array $campaigns = null; + + public function __construct(string $visitorId, ?string $anonymousId = null) + { + $this->visitorId = $visitorId; + $this->anonymousId = $anonymousId; + } + + public function getVisitorId(): string + { + return $this->visitorId; + } + + public function setVisitorId(string $visitorId): self + { + $this->visitorId = $visitorId; + return $this; + } + + public function getAnonymousId(): ?string + { + return $this->anonymousId; + } + + public function setAnonymousId(?string $anonymousId): self + { + $this->anonymousId = $anonymousId; + return $this; + } + + public function getConsent(): ?bool + { + return $this->consent; + } + + public function setConsent(?bool $consent): self + { + $this->consent = $consent; + return $this; + } + + /** + * @return array|null + */ + public function getContext(): ?array + { + return $this->context; + } + + /** + * @param array|null $context + */ + public function setContext(?array $context): self + { + $this->context = $context; + return $this; + } + + /** + * @return array|null + */ + public function getAssignmentsHistory(): ?array + { + return $this->assignmentsHistory; + } + + /** + * @param array|null $assignmentsHistory + */ + public function setAssignmentsHistory(?array $assignmentsHistory): self + { + $this->assignmentsHistory = $assignmentsHistory; + return $this; + } + + /** + * @return array|null + */ + public function getCampaigns(): ?array + { + return $this->campaigns; + } + + /** + * @param array|null $campaigns + */ + public function setCampaigns(?array $campaigns): self + { + $this->campaigns = $campaigns; + return $this; + } + + /** + * @param array $data + * @return self + */ + public static function fromArray(array $data): self + { + $visitorId = $data[StrategyAbstract::VISITOR_ID] ?? ''; + $anonymousId = $data[StrategyAbstract::ANONYMOUS_ID] ?? null; + + $instance = new self( + is_string($visitorId) ? $visitorId : '', + is_string($anonymousId) ? $anonymousId : null + ); + + if (isset($data[StrategyAbstract::CONSENT]) && is_bool($data[StrategyAbstract::CONSENT])) { + $instance->setConsent($data[StrategyAbstract::CONSENT]); + } + + if (isset($data[StrategyAbstract::CONTEXT]) && is_array($data[StrategyAbstract::CONTEXT])) { + $context = array_filter( + $data[StrategyAbstract::CONTEXT], + fn($value, $key) => is_string($key) && is_scalar($value), + ARRAY_FILTER_USE_BOTH + ); + $instance->setContext($context ?: null); + } + + $assignmentsHistory = []; + + if (isset($data[StrategyAbstract::ASSIGNMENTS_HISTORY]) && is_array($data[StrategyAbstract::ASSIGNMENTS_HISTORY])) { + $assignmentsHistory = array_filter( + $data[StrategyAbstract::ASSIGNMENTS_HISTORY], + fn($value, $key) => is_string($key) && is_string($value), + ARRAY_FILTER_USE_BOTH + ); + } + $instance->setAssignmentsHistory($assignmentsHistory); + + $campaigns = []; + if (isset($data[StrategyAbstract::CAMPAIGNS]) && is_array($data[StrategyAbstract::CAMPAIGNS])) { + $campaigns = array_map( + fn($campaign) => is_array($campaign) ? CampaignCacheDTO::fromArray($campaign) : null, + $data[StrategyAbstract::CAMPAIGNS] + ); + $campaigns = array_filter($campaigns); + } + $instance->setCampaigns($campaigns ); + + return $instance; + } + + /** + * @return VisitorCacheDataArray + */ + public function toArray(): array + { + $result = [ + StrategyAbstract::VISITOR_ID => $this->visitorId, + StrategyAbstract::ANONYMOUS_ID => $this->anonymousId, + ]; + + if ($this->consent !== null) { + $result[StrategyAbstract::CONSENT] = $this->consent; + } + + if ($this->context !== null) { + $result[StrategyAbstract::CONTEXT] = $this->context; + } + + if ($this->assignmentsHistory !== null) { + $result[StrategyAbstract::ASSIGNMENTS_HISTORY] = $this->assignmentsHistory; + } + + if ($this->campaigns !== null) { + $result[StrategyAbstract::CAMPAIGNS] = array_map( + fn(CampaignCacheDTO $campaign) => $campaign->toArray(), + $this->campaigns + ); + } + + return $result; + } +} diff --git a/src/Traits/BuildApiTrait.php b/src/Traits/BuildApiTrait.php index 7b64c679..02bd11e0 100644 --- a/src/Traits/BuildApiTrait.php +++ b/src/Traits/BuildApiTrait.php @@ -4,22 +4,25 @@ use Flagship\Enum\FlagshipConstant; +/** + * @phpstan-import-type BuildHeaderArray from Types + */ trait BuildApiTrait { /** * Build http request header * * @param string $apiKey - * @return array + * @return BuildHeaderArray */ - protected function buildHeader(string $apiKey): array + protected function buildHeader(?string $apiKey): array { return [ - FlagshipConstant::HEADER_X_API_KEY => $apiKey, - FlagshipConstant::HEADER_X_SDK_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::HEADER_CONTENT_TYPE => FlagshipConstant::HEADER_APPLICATION_JSON, - FlagshipConstant::HEADER_X_SDK_CLIENT => FlagshipConstant::SDK_LANGUAGE, - ]; + FlagshipConstant::HEADER_X_API_KEY => $apiKey??'', + FlagshipConstant::HEADER_X_SDK_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::HEADER_CONTENT_TYPE => FlagshipConstant::HEADER_APPLICATION_JSON, + FlagshipConstant::HEADER_X_SDK_CLIENT => FlagshipConstant::SDK_LANGUAGE, + ]; } /** diff --git a/src/Traits/CommonLogManagerTrait.php b/src/Traits/CommonLogManagerTrait.php index 80263181..90bb2579 100644 --- a/src/Traits/CommonLogManagerTrait.php +++ b/src/Traits/CommonLogManagerTrait.php @@ -14,22 +14,25 @@ public function getDateTime(): string } /** - * @param $level + * @param mixed $level * @param string $message - * @param array $context + * @param array $context * @return void */ - public function customLog($level, string $message, array $context = []): void + public function customLog(mixed $level, string $message, array $context = []): void { $flagshipSdk = FlagshipConstant::FLAGSHIP_SDK; $formatDate = $this->getDateTime(); - $customMessage = "[$formatDate] [$flagshipSdk] [$level] "; + $levelStr = is_scalar($level) || (is_object($level) && method_exists($level, '__toString')) + ? (string) $level + : gettype($level); + $customMessage = "[$formatDate] [$flagshipSdk] [$levelStr] "; $contextString = $this->parseContextToString($context); error_log($customMessage . $contextString . " " . $message); } /** - * @param array $context + * @param array $context * @return string */ private function parseContextToString(array $context): string @@ -40,7 +43,10 @@ private function parseContextToString(array $context): string $contextToString .= '['; } foreach ($context as $key => $item) { - $contextToString .= "$key => $item, "; + $itemStr = is_scalar($item) || (is_object($item) && method_exists($item, '__toString')) + ? (string) $item + : json_encode($item); + $contextToString .= "$key => $itemStr, "; } $contextToString = substr($contextToString, 0, -2); diff --git a/src/Traits/Guid.php b/src/Traits/Guid.php index 284dbb06..b3a50381 100644 --- a/src/Traits/Guid.php +++ b/src/Traits/Guid.php @@ -9,7 +9,7 @@ trait Guid */ protected function newGuid(): string { - $rand = function ($min, $max) { + $rand = function (int $min, int $max): int { return rand($min, $max); }; diff --git a/src/Traits/Helper.php b/src/Traits/Helper.php index 7ccbe3c0..8926fdab 100644 --- a/src/Traits/Helper.php +++ b/src/Traits/Helper.php @@ -27,6 +27,9 @@ public function getCurrentDateTime(): DateTime public function valueToHex(mixed $value): string { $jsonString = json_encode($value); + if ($jsonString === false) { + return ''; + } $hex = ''; for ($i = 0; $i < strlen($jsonString); $i++) { $hex .= dechex(ord($jsonString[$i])); @@ -34,6 +37,13 @@ public function valueToHex(mixed $value): string return $hex; } + /** + * Compare two arrays for equality, including nested arrays. + * + * @param array $array1 + * @param array $array2 + * @return bool + */ function arraysAreEqual(array $array1, array $array2): bool { if (count($array1) !== count($array2)) { @@ -58,4 +68,52 @@ function arraysAreEqual(array $array1, array $array2): bool return true; } + + /** + * + * @param array $array + * @param callable $callback + * @return bool + */ + protected function array_all(array $array, callable $callback): bool + { + foreach ($array as $key => $value) { + if (!$callback($value, $key)) { + return false; + } + } + return true; + } + + /** + * + * @param array $array + * @param callable $callback + * @return bool + */ + protected function array_any(array $array, callable $callback): bool + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return true; + } + } + return false; + } + + /** + * @phpstan-template T of mixed + * @param array $array + * @param callable $callback + * @return T|null + */ + protected function array_find(array $array, callable $callback): mixed + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + return null; + } } diff --git a/src/Traits/LogTrait.php b/src/Traits/LogTrait.php index 92080430..70be4086 100644 --- a/src/Traits/LogTrait.php +++ b/src/Traits/LogTrait.php @@ -9,31 +9,122 @@ trait LogTrait { /** - * @param array $args - * @return array + * Format arguments for logging by converting them to scalar values + * + * @param array $args Arguments to format + * @return array Formatted arguments safe for logging */ - protected function formatArgs($args = []): array + protected function formatArgs(array $args = []): array { - $formatArgs = []; + $formatted = []; + foreach ($args as $arg) { - if (is_array($arg)) { - $arg = json_encode($arg); - } - $formatArgs[] = $arg; + $formatted[] = $this->convertToScalar($arg); } - return $formatArgs; + + return $formatted; } /** - * @param FlagshipConfig $config + * Convert any value to a scalar or null for safe logging + * + * @param mixed $value The value to convert + * @return scalar|null Scalar representation of the value + */ + private function convertToScalar(mixed $value): string|int|float|bool|null + { + + return match (true) { + $value === null => null, + is_bool($value) => $value, + is_int($value) => $value, + is_float($value) => $value, + is_string($value) => $value, + is_array($value) => $this->serializeToJson($value), + is_object($value) => $this->convertObjectToString($value), + is_resource($value) => $this->formatResource($value), + default => $this->serializeToJson($value), + }; + } + + /** + * Convert an object to its string representation + * + * @param object $object The object to convert + * @return string String representation + */ + private function convertObjectToString(object $object): string + { + + + if (method_exists($object, '__toString')) { + return (string) $object; + } + + if ($object instanceof \JsonSerializable) { + return $this->serializeToJson($object); + } + + + return get_class($object); + } + + /** + * Serialize a value to JSON with error handling + * + * @param mixed $value Value to serialize + * @return string JSON string or error message + */ + private function serializeToJson(mixed $value): string + { + try { + $json = json_encode( + $value + ); + return $json ?: '{}'; + // @phpstan-ignore catch.neverThrown + } catch (\JsonException $e) { + return sprintf('[JSON Error: %s]', $e->getMessage()); + } + } + + /** + * Format a resource for logging + * + * @param resource $resource The resource to format + * @return string Resource description + */ + private function formatResource($resource): string + { + $type = get_resource_type($resource); + return sprintf('[Resource: %s]', $type); + } + + /** + * Format error when __toString fails + * + * @param object $object The object that failed + * @return string Error description + */ + private function formatToStringError(object $object, \Throwable $error): string + { + $class = get_class($object); + return sprintf('[%s __toString error: %s]', $class, $error->getMessage()); + } + + /** + * @param ?FlagshipConfig $config * @param string $tag * @param string $message - * @param array $args + * @param array $args * @return void */ - protected function logDebugSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void + protected function logDebugSprintf(?FlagshipConfig $config, string $tag, string $message, array $args = []): void { - if ($config->getLogLevel()->value < LogLevel::DEBUG->value || is_null($config->getLogManager())) { + if ( + $config?->getLogLevel()->value < LogLevel::DEBUG->value || + is_null($config->getLogManager()) + ) { return; } $customMessage = vsprintf($message, $this->formatArgs($args)); @@ -43,7 +134,7 @@ protected function logDebugSprintf(FlagshipConfig $config, string $tag, string $ /** * @param FlagshipConfig $config * @param string $message - * @param array $context + * @param array $context *@return void */ protected function logDebug(FlagshipConfig $config, string $message, array $context = []): void @@ -58,7 +149,7 @@ protected function logDebug(FlagshipConfig $config, string $message, array $cont * @param FlagshipConfig $config * @param string $tag * @param string $message - * @param array ...$args + * @param array $args * @return void */ protected function logErrorSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void @@ -70,14 +161,14 @@ protected function logErrorSprintf(FlagshipConfig $config, string $tag, string $ $this->logError($config, $customMessage, [FlagshipConstant::TAG => $tag]); } /** - * @param FlagshipConfig $config + * @param ?FlagshipConfig $config * @param string $message - * @param array $context + * @param array $context * @return void */ - protected function logError(FlagshipConfig $config, string $message, array $context = []): void + protected function logError(?FlagshipConfig $config, string $message, array $context = []): void { - if ($config->getLogLevel()->value < LogLevel::ERROR->value || is_null($config->getLogManager())) { + if ($config?->getLogLevel()->value < LogLevel::ERROR->value || is_null($config->getLogManager())) { return; } $config->getLogManager()->error($message, $context); @@ -87,7 +178,7 @@ protected function logError(FlagshipConfig $config, string $message, array $cont * @param FlagshipConfig $config * @param string $tag * @param string $message - * @param array $args + * @param array $args * @return void */ protected function logInfoSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void @@ -101,7 +192,7 @@ protected function logInfoSprintf(FlagshipConfig $config, string $tag, string $m /** * @param FlagshipConfig $config * @param string $message - * @param array $context + * @param array $context * @return void */ protected function logInfo(FlagshipConfig $config, string $message, array $context = []): void @@ -113,15 +204,15 @@ protected function logInfo(FlagshipConfig $config, string $message, array $conte } /** - * @param FlagshipConfig $config + * @param FlagshipConfig|null $config * @param string $tag * @param string $message - * @param array $args + * @param array< mixed> $args * @return void */ - protected function logWarningSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void + protected function logWarningSprintf(FlagshipConfig|null $config, string $tag, string $message, array $args = []): void { - if ($config->getLogLevel()->value < LogLevel::WARNING->value || is_null($config->getLogManager())) { + if ($config?->getLogLevel()->value < LogLevel::WARNING->value || is_null($config->getLogManager())) { return; } $customMessage = vsprintf($message, $this->formatArgs($args)); @@ -131,7 +222,7 @@ protected function logWarningSprintf(FlagshipConfig $config, string $tag, string /** * @param FlagshipConfig $config * @param string $message - * @param array $context + * @param array $context * @return void */ protected function logWarning(FlagshipConfig $config, string $message, array $context = []): void @@ -143,22 +234,22 @@ protected function logWarning(FlagshipConfig $config, string $message, array $co } /** - * @param string $message + * @param string|null $message * @param string $url - * @param array $requestBody - * @param array $headers - * @param string $duration + * @param ?array $requestBody + * @param ?array $headers + * @param float|int|null $duration * @param mixed $responseHeader * @param mixed $responseBody * @param mixed $responseStatus - * @return array + * @return array */ protected function getLogFormat( - $message, - $url, - $requestBody, - $headers, - $duration, + string|null $message, + string $url, + ?array $requestBody, + ?array $headers, + float|int|null $duration, $responseHeader = null, $responseBody = null, $responseStatus = null diff --git a/src/Traits/Types.php b/src/Traits/Types.php new file mode 100644 index 00000000..e1e27f7f --- /dev/null +++ b/src/Traits/Types.php @@ -0,0 +1,15 @@ +logError( - $config, - sprintf(FlagshipConstant::TYPE_ERROR, $itemName, 'numeric') - ); - return false; - } - return true; - } + } } diff --git a/src/Utils/ConfigManager.php b/src/Utils/ConfigManager.php index bde0e3cc..9df7d864 100644 --- a/src/Utils/ConfigManager.php +++ b/src/Utils/ConfigManager.php @@ -44,7 +44,7 @@ public function getConfig(): FlagshipConfig * @param FlagshipConfig $config * @return ConfigManager */ - public function setConfig(FlagshipConfig $config): static + public function setConfig(FlagshipConfig $config): self { $this->config = $config; return $this; @@ -62,7 +62,7 @@ public function getDecisionManager(): DecisionManagerAbstract * @param DecisionManagerAbstract $decisionManager * @return ConfigManager */ - public function setDecisionManager(DecisionManagerAbstract $decisionManager): static + public function setDecisionManager(DecisionManagerAbstract $decisionManager): self { $this->decisionManager = $decisionManager; return $this; @@ -80,7 +80,7 @@ public function getTrackingManager(): TrackingManagerAbstract * @param TrackingManagerAbstract $trackerManager * @return ConfigManager */ - public function setTrackingManager(TrackingManagerAbstract $trackerManager): static + public function setTrackingManager(TrackingManagerAbstract $trackerManager): self { $this->trackingManager = $trackerManager; return $this; diff --git a/src/Utils/Container.php b/src/Utils/Container.php index 30c0f14a..55b1345f 100644 --- a/src/Utils/Container.php +++ b/src/Utils/Container.php @@ -7,129 +7,514 @@ use ReflectionException; use ReflectionNamedType; use ReflectionParameter; +use ReflectionUnionType; +/** + * Dependency Injection Container + * + * Provides automatic dependency resolution and singleton management + * for class instantiation throughout the SDK. + */ class Container implements ContainerInterface { + /** + * Singleton instances cache + * @var array + */ private array $instances = []; + + /** + * Interface to implementation bindings + * @var array + */ private array $bindings = []; /** - * @param string $alias - * @param string $className - * @return $this - * @throws Exception + * Factory callbacks for custom instantiation + * @var array */ - public function bind(string $alias, string $className): static + private array $factories = []; + + /** + * Bind an alias to a concrete class implementation + * + * @template T of object + * @param class-string $alias The interface or abstract class + * @param class-string $className The concrete implementation + * @return self + * @throws ContainerException If alias already exists + */ + public function bind(string $alias, string $className): self { if (isset($this->bindings[$alias])) { - throw new Exception('alias ' . $alias . ' already exist'); + throw new ContainerException( + sprintf('Alias "%s" is already bound to "%s"', $alias, $this->bindings[$alias]) + ); + } + + if (!class_exists($className) && !interface_exists($className)) { + throw new ContainerException( + sprintf('Class "%s" does not exist', $className) + ); } + $this->bindings[$alias] = $className; return $this; } /** - * @param string $id - * @param null $args - * @param bool $isFactory - * @return mixed|object|null + * Register a factory callback for custom instantiation + * + * @template T of object + * @param class-string $id The class identifier + * @param callable(self, array|null): T $factory Factory function that returns the instance + * @return self + */ + public function factory(string $id, callable $factory): self + { + $this->factories[$id] = $factory; + return $this; + } + + /** + * Bind a singleton instance directly + * + * @template T of object + * @param class-string $id The class identifier + * @param T $instance The instance to register + * @return self + */ + public function instance(string $id, object $instance): self + { + $this->instances[$id] = $instance; + return $this; + } + + /** + * Check if a binding exists + * + * @param class-string $id + * @return bool + */ + public function has(string $id): bool + { + return isset($this->instances[$id]) + || isset($this->bindings[$id]) + || isset($this->factories[$id]) + || class_exists($id); + } + + /** + * Resolve and return an instance + * + * @template T of object + * @param class-string $id The class identifier + * @param array|null $args Constructor arguments (optional) + * @param bool $isFactory Whether to create a new instance each time + * @return T + * @throws ContainerException * @throws ReflectionException */ - public function get(string $id, $args = null, bool $isFactory = false): mixed + public function get(string $id, ?array $args = null, bool $isFactory = false): object { + // Always create new instance if factory mode if ($isFactory) { return $this->resolve($id, $args); } + + // Return existing singleton if available if (isset($this->instances[$id])) { + /** @var T */ return $this->instances[$id]; } - return $this->instances[$id] = $this->resolve($id, $args); + + // Create and cache new singleton + $instance = $this->resolve($id, $args); + $this->instances[$id] = $instance; + + /** @var T */ + return $instance; } /** - * @param string $id - * @param array|null $args - * @return object|null + * Create a new instance (alias for factory mode) + * + * @template T of object + * @param class-string $id + * @param array|null $args + * @return T + * @throws ContainerException * @throws ReflectionException - * @throws Exception */ - private function resolve(string $id, array $args = null): ?object + public function make(string $id, ?array $args = null): object { - $className = $id; - if (isset($this->bindings[$id])) { - $className = $this->bindings[$id]; + return $this->resolve($id, $args); + } + + /** + * Resolve a class to an instance + * + * @template T of object + * @param class-string $id The class identifier + * @param array|null $args Constructor arguments + * @return T + * @throws ContainerException + * @throws ReflectionException + */ + private function resolve(string $id, ?array $args = null): object + { + // Use factory if registered + if (isset($this->factories[$id])) { + return $this->invokeFactory($id, $args); } - $reflectedClass = new ReflectionClass($className); - if (!$reflectedClass->isInstantiable()) { - throw new Exception($className . "not an instantiable Class"); + // Resolve bound implementation + $className = $this->getConcreteClassName($id); + + // Create reflection + $reflectionClass = $this->createReflection($className); + + // Validate instantiability + $this->validateInstantiable($reflectionClass); + + // Get constructor + $constructor = $reflectionClass->getConstructor(); + + // No constructor - simple instantiation + if ($constructor === null) { + /** @var T */ + return $reflectionClass->newInstance(); } - $constructor = $reflectedClass->getConstructor(); + // Resolve constructor parameters + $parameters = $this->resolveConstructorParameters($constructor, $args); + + /** @var T */ + return $reflectionClass->newInstanceArgs($parameters); + } + + /** + * Invoke a registered factory + * + * @template T of object + * @param class-string $id + * @param array|null $args + * @return T + * @throws ContainerException + */ + private function invokeFactory(string $id, ?array $args = null): object + { + $factory = $this->factories[$id]; + $instance = $factory($this, $args); - if (!$constructor) { - return $reflectedClass->newInstance(); + if (!is_object($instance)) { + throw new ContainerException( + sprintf('Factory for "%s" must return an object', $id) + ); } - if (is_array($args)) { - $constructorParameters = $args; - } else { - $parameters = $constructor->getParameters(); - $constructorParameters = $this->extractConstructorParam($parameters); + /** @var T */ + return $instance; + } + + /** + * Get the concrete class name from binding or use the id + * + * @template T of object + * @param class-string $id + * @return class-string + */ + private function getConcreteClassName(string $id): string + { + /** @var class-string */ + return $this->bindings[$id] ?? $id; + } + + /** + * Create a reflection class with error handling + * + * @template T of object + * @param class-string $className + * @return ReflectionClass + * @throws ContainerException + * @phpstan-ignore throws.unusedType + */ + private function createReflection(string $className): ReflectionClass + { + try { + return new ReflectionClass($className); + // @phpstan-ignore catch.neverThrown + } catch (ReflectionException $e) { + throw new ContainerException( + sprintf('Class "%s" does not exist', $className), + 0, + $e + ); } - return $reflectedClass->newInstanceArgs($constructorParameters); } - private function getDefaultForType(string $typeName): float|bool|array|int|string|null + /** + * Validate that a class is instantiable + * + * @param ReflectionClass $reflection + * @return void + * @throws ContainerException + */ + private function validateInstantiable(ReflectionClass $reflection): void { - return match ($typeName) { - 'string' => '', - 'int' => 0, - 'bool' => false, - 'float' => 0.0, - 'array' => [], - default => null, - }; + if (!$reflection->isInstantiable()) { + throw new ContainerException( + sprintf( + 'Class "%s" is not instantiable (abstract, interface, or trait)', + $reflection->getName() + ) + ); + } } + /** + * Resolve constructor parameters + * + * @param \ReflectionMethod $constructor + * @param array|null $args + * @return array + * @throws ReflectionException + * @throws ContainerException + */ + private function resolveConstructorParameters( + \ReflectionMethod $constructor, + ?array $args = null + ): array { + // Use provided args if available + if (is_array($args) && !empty($args)) { + return $args; + } - private function resolveBuiltInType(string $typeName, bool $allowsNull): float|bool|array|int|string|null + // Auto-resolve dependencies + $parameters = $constructor->getParameters(); + return $this->buildParameterList($parameters); + } + + /** + * Build parameter list from reflection parameters + * + * @param array $parameters + * @return array + * @throws ReflectionException + * @throws ContainerException + */ + private function buildParameterList(array $parameters): array { - return $allowsNull ? null : $this->getDefaultForType($typeName); + $resolved = []; + + foreach ($parameters as $parameter) { + $resolved[] = $this->resolveParameter($parameter); + } + + return $resolved; } /** + * Resolve a single parameter + * + * @param ReflectionParameter $parameter + * @return mixed * @throws ReflectionException + * @throws ContainerException */ - private function resolveParameter(ReflectionParameter $parameter) + private function resolveParameter(ReflectionParameter $parameter): mixed { + // Use default value if available if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } + // Get parameter type $type = $parameter->getType(); - if (!$type) { + + // No type hint - return null if nullable + if ($type === null) { + return null; + } + + // Handle union types (PHP 8.0+) + if ($type instanceof ReflectionUnionType) { + return $this->resolveUnionType($type, $parameter); + } + + // Handle named types + if ($type instanceof ReflectionNamedType) { + return $this->resolveNamedType($type, $parameter); + } + + return null; + } + + /** + * Resolve a union type parameter + * + * @param ReflectionUnionType $type + * @param ReflectionParameter $parameter + * @return mixed + * @throws ReflectionException + * @throws ContainerException + */ + private function resolveUnionType(ReflectionUnionType $type, ReflectionParameter $parameter): mixed + { + $types = $type->getTypes(); + + // Try to resolve the first non-builtin type + foreach ($types as $unionType) { + if ($unionType instanceof ReflectionNamedType && !$unionType->isBuiltin()) { + $className = $unionType->getName(); + /** @var class-string $className */ + return $this->get($className); + } + } + + // Fallback to null if allowed + if ($type->allowsNull()) { return null; } - $typeName = $type instanceof ReflectionNamedType ? $type->getName() : (string)$type; + // Try to get default for first type + $defaultValue = $this->getDefaultForFirstType($types); + + if ($defaultValue !== null) { + return $defaultValue; + } + + // If we can't resolve any type, throw exception + throw new ContainerException( + sprintf( + 'Cannot resolve union type parameter "$%s" in class "%s" - no resolvable types found', + $parameter->getName(), + $parameter->getDeclaringClass()?->getName() ?? 'unknown' + ) + ); + } + + /** + * Resolve a named type parameter + * + * @param ReflectionNamedType $type + * @param ReflectionParameter $parameter + * @return mixed + * @throws ReflectionException + * @throws ContainerException + */ + private function resolveNamedType(ReflectionNamedType $type, ReflectionParameter $parameter): mixed + { + $typeName = $type->getName(); $allowsNull = $type->allowsNull(); + // Built-in types (string, int, bool, etc.) if ($type->isBuiltin()) { return $this->resolveBuiltInType($typeName, $allowsNull); } - return $this->get($typeName); + + // Try to resolve from container + try { + /** @var class-string $typeName */ + return $this->get($typeName); + } catch (ContainerException $e) { + if ($allowsNull) { + return null; + } + throw new ContainerException( + sprintf( + 'Cannot resolve parameter "$%s" of type "%s": %s', + $parameter->getName(), + $typeName, + $e->getMessage() + ), + 0, + $e + ); + } } /** - * @throws ReflectionException + * Resolve built-in type with default value + * + * @param string $typeName + * @param bool $allowsNull + * @return scalar|array|null */ - private function extractConstructorParam(array $parameters): array + private function resolveBuiltInType(string $typeName, bool $allowsNull): float|bool|array|int|string|null { - $constructorParameters = []; - foreach ($parameters as $parameter) { - $constructorParameters[] = $this->resolveParameter($parameter); + if ($allowsNull) { + return null; } - return $constructorParameters; + + return $this->getDefaultForType($typeName); + } + + /** + * Get default value for a built-in type + * + * @param string $typeName + * @return scalar|array + */ + private function getDefaultForType(string $typeName): float|bool|array|int|string + { + return match ($typeName) { + 'string' => '', + 'int' => 0, + 'bool' => false, + 'float' => 0.0, + 'array' => [], + default => '', + }; + } + + /** + * Get default for first type in union + * + * @param array<\ReflectionType> $types + * @return mixed + */ + private function getDefaultForFirstType(array $types): mixed + { + $firstType = $types[0] ?? null; + + if ($firstType instanceof ReflectionNamedType && $firstType->isBuiltin()) { + return $this->getDefaultForType($firstType->getName()); + } + + return null; + } + + /** + * Clear all cached instances + * + * @return self + */ + public function flush(): self + { + $this->instances = []; + return $this; + } + + /** + * Clear specific cached instance + * + * @param class-string $id + * @return self + */ + public function forget(string $id): self + { + unset($this->instances[$id]); + return $this; + } + + /** + * Get all registered bindings + * + * @return array + */ + public function getBindings(): array + { + return $this->bindings; } } diff --git a/src/Utils/ContainerException.php b/src/Utils/ContainerException.php new file mode 100644 index 00000000..2e1415bf --- /dev/null +++ b/src/Utils/ContainerException.php @@ -0,0 +1,48 @@ + $chain + * @return self + */ + public static function circularDependency(array $chain): self + { + return new self( + sprintf('Circular dependency detected: %s', implode(' -> ', $chain)) + ); + } + + /** + * Create exception for non-instantiable class + * + * @param class-string $className + * @return self + */ + public static function notInstantiable(string $className): self + { + return new self( + sprintf('Class "%s" is not instantiable', $className) + ); + } +} \ No newline at end of file diff --git a/src/Utils/ContainerInterface.php b/src/Utils/ContainerInterface.php index f0550ab7..4283532b 100644 --- a/src/Utils/ContainerInterface.php +++ b/src/Utils/ContainerInterface.php @@ -2,13 +2,64 @@ namespace Flagship\Utils; +use ReflectionException; + +/** + * Dependency Injection Container Interface + */ interface ContainerInterface { /** - * @param string $id - * @param array|null $args + * Bind an alias to a concrete class + * + * @param class-string $alias + * @param class-string $className + * @return self + */ + public function bind(string $alias, string $className): self; + + /** + * Resolve and return an instance + * + * @param class-string $id + * @param array|null $args * @param bool $isFactory - * @return mixed|object|null + * @return object + * @throws ContainerException + * @throws ReflectionException + */ + public function get(string $id, ?array $args = null, bool $isFactory = false): object; + + /** + * Check if a binding exists + * + * @param class-string $id + * @return bool + */ + public function has(string $id): bool; + + /** + * Register a factory callback + * + * @param class-string $id + * @param callable $factory + * @return self + */ + public function factory(string $id, callable $factory): self; + + /** + * Bind a singleton instance + * + * @param class-string $id + * @param object $instance + * @return self + */ + public function instance(string $id, object $instance): self; + + /** + * Clear all cached instances + * + * @return self */ - public function get(string $id, array $args = null, bool $isFactory = false): mixed; -} + public function flush(): self; +} \ No newline at end of file diff --git a/src/Utils/FlagshipLogManager.php b/src/Utils/FlagshipLogManager.php index 6e070114..c04d54d8 100644 --- a/src/Utils/FlagshipLogManager.php +++ b/src/Utils/FlagshipLogManager.php @@ -5,89 +5,82 @@ use Flagship\Traits\CommonLogManagerTrait; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; -use Stringable; +/** + * Default log manager to use with php 8>= + */ class FlagshipLogManager implements LoggerInterface { use CommonLogManagerTrait; /** * @inheritDoc - * @return void */ - public function emergency(string|Stringable $message, array $context = []): void + public function emergency(mixed $message, array $context = []): void { $this->log(LogLevel::EMERGENCY, $message, $context); } /** * @inheritDoc - * @return void */ - public function alert(string|Stringable $message, array $context = []): void + public function alert(mixed $message, array $context = []): void { $this->log(LogLevel::ALERT, $message, $context); } /** * @inheritDoc - * @return void */ - public function critical(string|Stringable $message, array $context = []): void + public function critical(mixed $message, array $context = []): void { $this->log(LogLevel::CRITICAL, $message, $context); } /** * @inheritDoc - * @return void */ - public function error(string|Stringable $message, array $context = []): void + public function error(mixed $message, array $context = []): void { $this->log(LogLevel::ERROR, $message, $context); } /** * @inheritDoc - * @return void */ - public function warning(string|Stringable $message, array $context = []): void + public function warning(mixed $message, array $context = []): void { $this->log(LogLevel::WARNING, $message, $context); } /** * @inheritDoc - * @return void */ - public function notice(string|Stringable $message, array $context = []): void + public function notice(mixed $message, array $context = []): void { $this->log(LogLevel::NOTICE, $message, $context); } /** * @inheritDoc - * @return void */ - public function info(string|Stringable $message, array $context = []): void + public function info(mixed $message, array $context = []): void { $this->log(LogLevel::INFO, $message, $context); } /** * @inheritDoc - * @return void */ - public function debug(string|Stringable $message, array $context = []): void + public function debug(mixed $message, array $context = []): void { $this->log(LogLevel::DEBUG, $message, $context); } /** * @inheritDoc - * @return void */ - public function log($level, string|Stringable $message, array $context = []): void + public function log($level, mixed $message, array $context = []): void { $this->customLog($level, $message, $context); } diff --git a/src/Utils/FlagshipLogManager8.php b/src/Utils/FlagshipLogManager8.php deleted file mode 100644 index 79e137bc..00000000 --- a/src/Utils/FlagshipLogManager8.php +++ /dev/null @@ -1,87 +0,0 @@ -= - */ -class FlagshipLogManager8 implements LoggerInterface -{ - use CommonLogManagerTrait; - - /** - * @inheritDoc - */ - public function emergency(mixed $message, array $context = []): void - { - $this->log(LogLevel::EMERGENCY, $message, $context); - } - - /** - * @inheritDoc - */ - public function alert(mixed $message, array $context = []): void - { - $this->log(LogLevel::ALERT, $message, $context); - } - - /** - * @inheritDoc - */ - public function critical(mixed $message, array $context = []): void - { - $this->log(LogLevel::CRITICAL, $message, $context); - } - - /** - * @inheritDoc - */ - public function error(mixed $message, array $context = []): void - { - $this->log(LogLevel::ERROR, $message, $context); - } - - /** - * @inheritDoc - */ - public function warning(mixed $message, array $context = []): void - { - $this->log(LogLevel::WARNING, $message, $context); - } - - /** - * @inheritDoc - */ - public function notice(mixed $message, array $context = []): void - { - $this->log(LogLevel::NOTICE, $message, $context); - } - - /** - * @inheritDoc - */ - public function info(mixed $message, array $context = []): void - { - $this->log(LogLevel::INFO, $message, $context); - } - - /** - * @inheritDoc - */ - public function debug(mixed $message, array $context = []): void - { - $this->log(LogLevel::DEBUG, $message, $context); - } - - /** - * @inheritDoc - */ - public function log($level, mixed $message, array $context = []): void - { - $this->customLog($level, $message, $context); - } -} diff --git a/src/Utils/HttpClient.php b/src/Utils/HttpClient.php index 1154dce7..121e2e9f 100644 --- a/src/Utils/HttpClient.php +++ b/src/Utils/HttpClient.php @@ -2,25 +2,35 @@ namespace Flagship\Utils; +use CurlHandle; use ErrorException; use Exception; use Flagship\Enum\FlagshipConstant; use Flagship\Model\HttpResponse; +/** + * HTTP Client implementation using cURL + * + * Provides HTTP request functionality with automatic error handling, + * header management, and response parsing. + */ class HttpClient implements HttpClientInterface { /** - * @var ?array + * cURL handle + * @var CurlHandle|null */ - private mixed $curl = null; + private ?CurlHandle $curl = null; /** - * @var array + * cURL options cache + * @var array */ private array $options = []; /** - * @var array + * HTTP headers + * @var array */ private array $headers = []; @@ -37,66 +47,87 @@ public function __construct() } /** + * Initialize cURL handle with default options + * * @return void + * @throws ErrorException */ private function curlInit(): void { - $this->curl = curl_init(); + $curl = curl_init(); + + if (!$curl) { + throw new ErrorException('Failed to initialize cURL'); + } + + $this->curl = $curl; $this->setTimeout(); $this->setOpt(CURLOPT_RETURNTRANSFER, true); $this->setOpt(CURLOPT_FILETIME, true); } /** - * Set Opt + * Set cURL option * - * @param $option - * @param $value + * @param int $option cURL option constant + * @param mixed $value Option value * - * @return boolean + * @return bool Success status + * @throws ErrorException */ - public function setOpt($option, $value): bool + public function setOpt(int $option, mixed $value): bool { - if (!$this->curl) { + if ($this->curl === null) { $this->curlInit(); } - $success = curl_setopt($this->curl, $option, $value); + + /** @var CurlHandle $curl */ + $curl = $this->curl; + + $success = curl_setopt($curl, $option, $value); + if ($success) { $this->options[$option] = $value; } + return $success; } /** - * @return array + * Get all configured cURL options + * + * @return array */ public function getOptions(): array { return $this->options; } - /** * @inheritDoc */ public function setHeaders(array $headers): HttpClientInterface { foreach ($headers as $key => $value) { - $key = trim($key); - $value = trim($value); + $key = trim((string)$key); + $value = trim((string)$value); $this->headers[$key] = $value; } - $headers = []; + $formattedHeaders = []; foreach ($this->headers as $key => $value) { - $headers[] = $key . ': ' . $value; + $formattedHeaders[] = $key . ': ' . $value; } - $this->setOpt(CURLOPT_HTTPHEADER, $headers); + + $this->setOpt(CURLOPT_HTTPHEADER, $formattedHeaders); + return $this; } /** - * @return array + * Get all configured headers + * + * @return array */ public function getHeaders(): array { @@ -106,7 +137,7 @@ public function getHeaders(): array /** * @inheritDoc */ - public function setTimeout(int $seconds = FlagshipConstant::REQUEST_TIME_OUT): HttpClientInterface + public function setTimeout(float $seconds = FlagshipConstant::REQUEST_TIME_OUT): HttpClientInterface { $this->setOpt(CURLOPT_TIMEOUT, $seconds); $this->setOpt(CURLOPT_CONNECTTIMEOUT, $seconds); @@ -114,13 +145,13 @@ public function setTimeout(int $seconds = FlagshipConstant::REQUEST_TIME_OUT): H } /** - * Set Url + * Set request URL with optional query parameters * - * @param $url - * @param string|array $data + * @param string $url Base URL + * @param string|array $data Query parameters * @return HttpClientInterface */ - private function setUrl($url, string|array $data = ''): HttpClientInterface + private function setUrl(string $url, string|array $data = ''): HttpClientInterface { $builtUrl = $this->buildUrl($url, $data); $this->setOpt(CURLOPT_URL, $builtUrl); @@ -128,55 +159,75 @@ private function setUrl($url, string|array $data = ''): HttpClientInterface } /** - * Exec + * Execute cURL request and parse response * - * @return HttpResponse Returns the value provided by parseResponse. - * @throws Exception + * @return HttpResponse Parsed HTTP response + * @throws Exception On HTTP error or cURL error */ private function exec(): HttpResponse { + if ($this->curl === null) { + throw new Exception('cURL handle not initialized'); + } + $rawResponse = curl_exec($this->curl); $curlErrorCode = curl_errno($this->curl); $curlErrorMessage = curl_error($this->curl); + /** @var int $httpStatusCode */ $httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); - $httpError = in_array(floor($httpStatusCode / 100), [4, 5]); + + $httpError = in_array((int)floor($httpStatusCode / 100), [4, 5], true); + $httpContentType = $this->getInfo(CURLINFO_CONTENT_TYPE); $lastModified = $this->getInfo(CURLINFO_FILETIME); - curl_close($this->curl); - - $this->curl = null; + $this->closeCurl(); if ($httpError || $curlErrorCode) { - $message = [ - 'curlCode' => $curlErrorCode, - 'curlMessage' => $curlErrorMessage, - 'httpCode' => $httpStatusCode, - 'httpMessage' => $rawResponse, - ]; - throw new Exception(json_encode($message), $curlErrorCode); + $errorData = [ + 'curlCode' => $curlErrorCode, + 'curlMessage' => $curlErrorMessage, + 'httpCode' => $httpStatusCode, + 'httpMessage' => $rawResponse, + ]; + $errorMessage = json_encode($errorData) ?: 'Failed to encode error data'; + throw new Exception($errorMessage, $curlErrorCode); } + $response = $rawResponse; - if ($httpContentType == "application/json") { - $response = $this->parseResponse($rawResponse); + if ($httpContentType === 'application/json' && is_string($rawResponse)) { + $parsed = $this->parseResponse($rawResponse); + if ($parsed !== null) { + $response = $parsed; + } } - $responseHeaders = []; - if ($lastModified !== - 1) { - $responseHeaders["last-modified"] = date('Y-m-d H:i:s', $lastModified); + if (is_int($lastModified) && $lastModified !== -1) { + $responseHeaders['last-modified'] = date('Y-m-d H:i:s', $lastModified); } + return new HttpResponse($httpStatusCode, $response, $responseHeaders); } /** - * Get + * Close cURL handle + * + * @return void + */ + private function closeCurl(): void + { + $this->curl = null; + } + + /** + * Perform GET request * - * @param string $url - * @param array $params + * @param string $url Request URL + * @param array $params Query parameters * - * @return HttpResponse value provided by exec. + * @return HttpResponse HTTP response * @throws Exception */ public function get(string $url, array $params = []): HttpResponse @@ -188,10 +239,12 @@ public function get(string $url, array $params = []): HttpResponse } /** - * @param string $url - * @param array $query - * @param array $data - * @return HttpResponse + * Perform POST request + * + * @param string $url Request URL + * @param array $query Query parameters + * @param array $data POST body data + * @return HttpResponse HTTP response * @throws Exception */ public function post(string $url, array $query = [], array $data = []): HttpResponse @@ -204,31 +257,39 @@ public function post(string $url, array $query = [], array $data = []): HttpResp } /** - * Get Info + * Get cURL information * - * @access public - * @param $opt + * @param int|null $opt cURL info option * - * @return int|string + * @return mixed cURL info value */ - private function getInfo($opt = null): int|string + private function getInfo(?int $opt = null): mixed { + if ($this->curl === null) { + return null; + } + return curl_getinfo($this->curl, $opt); } - private function parseResponse($rawResponse): mixed + /** + * Parse JSON response + * + * @param string $rawResponse Raw response string + * @return mixed Parsed response or null on failure + */ + private function parseResponse(string $rawResponse): mixed { return json_decode($rawResponse, true); } /** - * Build Url + * Build URL with query parameters * - * @access public - * @param string $url - * @param array|string $data + * @param string $url Base URL + * @param array|string $data Query parameters * - * @return string + * @return string Complete URL with query string */ private function buildUrl(string $url, array|string $data = ''): string { @@ -240,10 +301,20 @@ private function buildUrl(string $url, array|string $data = ''): string $queryString = match (true) { is_string($data) => $queryMark . $data, - is_array($data) => $queryMark . http_build_query($data, '', '&'), - default => '' + default => $queryMark . http_build_query($data, '', '&'), }; return $url . $queryString; } + + /** + * Clean up cURL handle on destruction + * + * PHP 8.0+ automatically closes CurlHandle objects when they go out of scope. + * This destructor explicitly unsets the handle for clarity. + */ + public function __destruct() + { + $this->closeCurl(); + } } diff --git a/src/Utils/HttpClientInterface.php b/src/Utils/HttpClientInterface.php index 3c30c0de..317fcefc 100644 --- a/src/Utils/HttpClientInterface.php +++ b/src/Utils/HttpClientInterface.php @@ -15,7 +15,7 @@ interface HttpClientInterface /** * Add extra headers to include in the request. * - * @param array $headers : Collection key, value of http header + * @param array $headers : Collection key, value of http header * @return HttpClientInterface */ public function setHeaders(array $headers): HttpClientInterface; @@ -23,16 +23,16 @@ public function setHeaders(array $headers): HttpClientInterface; /** * set the Timeout * - * @param int $seconds + * @param float $seconds * @return HttpClientInterface */ - public function setTimeout(int $seconds = FlagshipConstant::REQUEST_TIME_OUT): HttpClientInterface; + public function setTimeout(float $seconds = FlagshipConstant::REQUEST_TIME_OUT): HttpClientInterface; /** * send a http get request * * @param string $url - * @param array $params Collection key, value of http params + * @param array $params Collection key, value of http params * @return HttpResponse */ public function get(string $url, array $params = []): HttpResponse; @@ -41,8 +41,8 @@ public function get(string $url, array $params = []): HttpResponse; * send a http post request * * @param string $url - * @param array $query Collection key, value of http params - * @param array $data Collection key, value of http post body + * @param array $query Collection key, value of http params + * @param array $data Collection key, value of http post body * @return HttpResponse */ public function post(string $url, array $query = [], array $data = []): HttpResponse; diff --git a/src/Utils/MurmurHash.php b/src/Utils/MurmurHash.php index 13bcccbc..57d471cb 100644 --- a/src/Utils/MurmurHash.php +++ b/src/Utils/MurmurHash.php @@ -2,78 +2,246 @@ namespace Flagship\Utils; +use InvalidArgumentException; + +/** + * MurmurHash3 32-bit implementation + * + * Provides a fast, non-cryptographic hash function for distributing values + * uniformly across a hash space. Used for bucketing and consistent hashing. + * + * @see https://en.wikipedia.org/wiki/MurmurHash + */ class MurmurHash { /** - * @param mixed $k1 - * @param mixed $constant - * @return int + * Multiply two 32-bit integers with overflow handling + * + * @param int $k1 First operand + * @param int $constant Second operand (multiplication constant) + * @return int Result masked to 32-bit unsigned integer + */ + private function multiply(int $k1, int $constant): int + { + $lowBits = ($k1 & 0xffff) * $constant; + $highBits = ($this->unsignedRightShift($k1, 16) * $constant & 0xffff) << 16; + + return ($lowBits + $highBits) & 0xffffffff; + } + + /** + * Rotate bits left by 15 positions + * + * @param int $k1 Value to rotate + * @return int Rotated value */ - private function multiply(mixed $k1, mixed $constant): int + private function rotateLeft(int $k1): int { - return ((($k1 & 0xffff) * $constant) + ((($k1 >= 0 ? $k1 >> 16 : (($k1 & 0x7fffffff) >> 16) | 0x8000) * $constant & 0xffff) << 16)) & 0xffffffff; + return ($k1 << 15) | $this->unsignedRightShift($k1, 17); } /** - * @param mixed $k1 - * @return int + * Perform unsigned right shift operation + * + * PHP doesn't have native unsigned right shift, so we implement it manually + * to handle the sign bit correctly. + * + * @param int $value Value to shift + * @param int $bits Number of bits to shift + * @return int Unsigned shifted result */ - private function rotateLeft(mixed $k1): int + private function unsignedRightShift(int $value, int $bits): int { - return $k1 << 15 | ($k1 >= 0 ? $k1 >> 17 : (($k1 & 0x7fffffff) >> 17) | 0x4000); + if ($value >= 0) { + return $value >> $bits; + } + + // Handle negative numbers (sign bit set) + return (($value & 0x7fffffff) >> $bits) | (0x40000000 >> ($bits - 1)); } /** - * @param mixed $value - * @param mixed $numberDecalBit - * @param mixed $constant - * @return int + * Rotate bits right with unsigned shift + * + * @param int $value Value to rotate + * @param int $bits Number of bits to shift + * @return int Rotated value */ - private function rotateRight(mixed $value, mixed $numberDecalBit, mixed $constant): int + private function rotateRight(int $value, int $bits): int { - return $value >= 0 ? $value >> $numberDecalBit : (($value & 0x7fffffff) >> $numberDecalBit) | $constant; + return $this->unsignedRightShift($value, $bits); } + /** - * @param string $source - * @return int + * Convert string to byte array + * + * @param string $source Input string + * @return array Array of unsigned bytes + * @throws InvalidArgumentException If unpack fails */ - public function murmurHash3Int32(string $source): int + private function stringToBytes(string $source): array { - $seed = 0; - $source = array_values(unpack('C*', $source)); - $keyLength = count($source); + /** + * @var array|false $unpacked + */ + $unpacked = unpack('C*', $source); + + if ($unpacked === false) { + throw new InvalidArgumentException('Failed to unpack string to bytes'); + } + + return array_values($unpacked); + } + + /** + * Compute MurmurHash3 32-bit hash for a string + * + * This implementation follows the MurmurHash3 algorithm specification + * for generating a 32-bit hash value from an input string. + * + * @param string $source Input string to hash + * @param int $seed Optional seed value (default: 0) + * @return int 32-bit hash value + * @throws InvalidArgumentException If string cannot be processed + */ + public function murmurHash3Int32(string $source, int $seed = 0): int + { + // Convert string to array of unsigned bytes + $bytes = $this->stringToBytes($source); + $length = count($bytes); + + // Initialize hash with seed $h1 = $seed < 0 ? -$seed : $seed; - $i = 0; - for ($bytes = $keyLength - ($remainder = $keyLength & 3); $i < $bytes;) { - $k1 = $source[$i] | ($source[++$i] << 8) | ($source[++$i] << 16) | ($source[++$i] << 24); - ++$i; - $k1 = $this->multiply($k1, 0xcc9e2d51); - $k1 = $this->rotateLeft($k1); - $k1 = $this->multiply($k1, 0x1b873593); + + // Process 4-byte chunks + $chunks = (int)floor($length / 4); + $remainder = $length % 4; + + for ($i = 0; $i < $chunks * 4; $i += 4) { + // Combine 4 bytes into 32-bit integer (little-endian) + $k1 = $bytes[$i] + | ($bytes[$i + 1] << 8) + | ($bytes[$i + 2] << 16) + | ($bytes[$i + 3] << 24); + + // Mix the chunk + $k1 = $this->mixChunk($k1); + + // Update hash + $h1 = $this->updateHash($h1, $k1); + } + + // Process remaining bytes + if ($remainder > 0) { + $k1 = $this->processRemainder($bytes, $chunks * 4, $remainder); $h1 ^= $k1; - $h1 = $h1 << 13 | ($h1 >= 0 ? $h1 >> 19 : (($h1 & 0x7fffffff) >> 19) | 0x1000); - $h1b = ((($h1 & 0xffff) * 5) + (((($h1 >= 0 ? $h1 >> 16 : (($h1 & 0x7fffffff) >> 16) | 0x8000) * 5) & 0xffff) << 16)) & 0xffffffff; - $h1 = ((($h1b & 0xffff) + 0x6b64) + (((($h1b >= 0 ? $h1b >> 16 : (($h1b & 0x7fffffff) >> 16) | 0x8000) + 0xe654) & 0xffff) << 16)); } + + // Finalize hash + return $this->finalize($h1, $length); + } + + /** + * Mix a 4-byte chunk using MurmurHash3 constants + * + * @param int $k1 Chunk to mix + * @return int Mixed chunk + */ + private function mixChunk(int $k1): int + { + $k1 = $this->multiply($k1, 0xcc9e2d51); + $k1 = $this->rotateLeft($k1); + $k1 = $this->multiply($k1, 0x1b873593); + + return $k1; + } + + /** + * Update hash with a mixed chunk + * + * @param int $h1 Current hash value + * @param int $k1 Mixed chunk + * @return int Updated hash value + */ + private function updateHash(int $h1, int $k1): int + { + $h1 ^= $k1; + $h1 = ($h1 << 13) | $this->unsignedRightShift($h1, 19); + + // h1 = h1 * 5 + 0xe6546b64 + $h1b = $this->multiply($h1, 5); + $h1 = ($h1b + 0xe6546b64) & 0xffffffff; + + return $h1; + } + + /** + * Process remaining bytes (less than 4) + * + * @param array $bytes Byte array + * @param int $offset Starting offset + * @param int $remainder Number of remaining bytes (1-3) + * @return int Processed remainder as 32-bit integer + */ + private function processRemainder(array $bytes, int $offset, int $remainder): int + { $k1 = 0; + switch ($remainder) { case 3: - $k1 ^= $source[$i + 2] << 16; + $k1 ^= $bytes[$offset + 2] << 16; + // Fall through case 2: - $k1 ^= $source[$i + 1] << 8; + $k1 ^= $bytes[$offset + 1] << 8; + // Fall through case 1: - $k1 ^= $source[$i]; - $k1 = $this->multiply($k1, 0xcc9e2d51); - $k1 = $this->rotateLeft($k1); - $k1 = $this->multiply($k1, 0x1b873593); - $h1 ^= $k1; + $k1 ^= $bytes[$offset]; + $k1 = $this->mixChunk($k1); + break; } - $h1 ^= $keyLength; - $h1 ^= $this->rotateRight($h1, 16, 0x8000); + + return $k1; + } + + /** + * Finalize hash with avalanche mixing + * + * @param int $h1 Current hash value + * @param int $length Original input length + * @return int Final 32-bit hash + */ + private function finalize(int $h1, int $length): int + { + // XOR with length + $h1 ^= $length; + + // Final avalanche mixing + $h1 ^= $this->rotateRight($h1, 16); $h1 = $this->multiply($h1, 0x85ebca6b); - $h1 ^= $this->rotateRight($h1, 13, 0x40000); + $h1 ^= $this->rotateRight($h1, 13); $h1 = $this->multiply($h1, 0xc2b2ae35); - $h1 ^= $this->rotateRight($h1, 16, 0x8000); + $h1 ^= $this->rotateRight($h1, 16); + return $h1; } + + /** + * Compute normalized hash value in range [0, 1) + * + * Useful for percentage-based bucketing and A/B testing. + * + * @param string $source Input string to hash + * @param int $seed Optional seed value + * @return float Normalized hash value between 0 and 1 + * @throws InvalidArgumentException If string cannot be processed + */ + public function murmurHash3Normalized(string $source, int $seed = 0): float + { + $hash = $this->murmurHash3Int32($source, $seed); + + // Convert to unsigned 32-bit integer and normalize to [0, 1) + $unsigned = $hash & 0xffffffff; + + return $unsigned / 0x100000000; // Divide by 2^32 + } } diff --git a/src/Visitor/DefaultStrategy.php b/src/Visitor/DefaultStrategy.php index 04c5b3f9..56e467d1 100644 --- a/src/Visitor/DefaultStrategy.php +++ b/src/Visitor/DefaultStrategy.php @@ -2,21 +2,23 @@ namespace Flagship\Visitor; -use Flagship\Enum\FlagshipContext; use Flagship\Hit\Event; use Flagship\Hit\Activate; use Flagship\Enum\LogLevel; use Flagship\Model\FlagDTO; use Flagship\Hit\HitAbstract; use Flagship\Enum\DecisionMode; -use Flagship\Flag\FSFlagMetadata; +use Flagship\Model\CampaignDTO; use Flagship\Enum\EventCategory; -use Flagship\Enum\FlagshipField; -use Flagship\Enum\FSFetchStatus; use Flagship\Enum\FSFetchReason; +use Flagship\Enum\FSFetchStatus; +use Flagship\Model\VariationDTO; +use Flagship\Flag\FSFlagMetadata; use Flagship\Hit\Troubleshooting; +use Flagship\Enum\FlagshipContext; use Flagship\Enum\FlagshipConstant; use Flagship\Model\FetchFlagsStatus; +use Flagship\Model\ModificationsDTO; use Flagship\Enum\TroubleshootingLabel; /** @@ -48,7 +50,7 @@ public function setConsent(bool $hasConsented): void $troubleshooting = new Troubleshooting(); $troubleshooting->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setLogLevel(LogLevel::INFO)->setTraffic($visitor->getTraffic())->setFlagshipInstanceId($visitor->getFlagshipInstanceId())->setVisitorSessionId($visitor->getInstanceId())->setHitContent($consentHit->toApiKeys())->setVisitorId($visitor->getVisitorId())->setConfig($this->getConfig())->setAnonymousId($visitor->getAnonymousId()); - if ($this->getDecisionManager() && $this->getDecisionManager()->getTroubleshootingData()) { + if ($this->getDecisionManager()->getTroubleshootingData()) { $this->sendTroubleshootingHit($troubleshooting); return; } @@ -57,20 +59,11 @@ public function setConsent(bool $hasConsented): void /** * @param string $key context key. - * @param bool|string|numeric $value : context value. + * @param scalar $value : context value. * @return void */ - protected function updateContextKeyValue(string $key, mixed $value): void + protected function updateContextKeyValue(string $key, float|bool|int|string $value): void { - if (!$this->isKeyValid($key) || !$this->isValueValid($value)) { - $this->logError( - $this->getVisitor()->getConfig(), - FlagshipConstant::CONTEXT_PARAM_ERROR, - [FlagshipConstant::TAG => FlagshipConstant::TAG_UPDATE_CONTEXT] - ); - return; - } - if ( $key === FlagshipContext::FLAGSHIP_CLIENT || $key === FlagshipContext::FLAGSHIP_VERSION || $key === FlagshipContext::FLAGSHIP_VISITOR @@ -105,8 +98,17 @@ protected function setFetchStatus(FSFetchStatus $status, FSFetchReason $reason): /** * @inheritDoc */ - public function updateContext(string $key, float|bool|int|string|null $value): void + public function updateContext(string $key, float|bool|int|string $value): void { + if (empty($key)) { + $this->logError( + $this->getVisitor()->getConfig(), + FlagshipConstant::CONTEXT_PARAM_ERROR, + [FlagshipConstant::TAG => __FUNCTION__] + ); + return; + } + $oldContext = $this->getVisitor()->getContext(); $this->updateContextKeyValue($key, $value); @@ -253,56 +255,59 @@ public function unauthenticate(): void /** * @param VisitorAbstract $visitor - * @return array + * @return CampaignDTO[] */ protected function fetchVisitorCampaigns(VisitorAbstract $visitor): array { $now = $this->getNow(); $visitorCache = $visitor->visitorCache; - if ( - !isset( - $visitorCache, - $visitorCache[self::DATA], - $visitorCache[self::DATA][self::CAMPAIGNS] - ) || - !is_array($visitorCache[self::DATA][self::CAMPAIGNS]) - ) { + + if ($visitorCache === null) { return []; } - $data = $visitorCache[self::DATA]; - $visitor->updateContextCollection($data[self::CONTEXT]); - $campaigns = []; - foreach ($data[self::CAMPAIGNS] as $item) { - $campaigns[] = [ - FlagshipField::FIELD_ID => $item[FlagshipField::FIELD_CAMPAIGN_ID], - FlagshipField::FIELD_VARIATION_GROUP_ID => $item[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION => [ - FlagshipField::FIELD_ID => $item[self::CAMPAIGN_ID], - FlagshipField::FIELD_REFERENCE => $item[FlagshipField::FIELD_IS_REFERENCE], - FlagshipField::FIELD_MODIFICATIONS => [ - FlagshipField::FIELD_CAMPAIGN_TYPE => $item[FlagshipField::FIELD_CAMPAIGN_TYPE], - FlagshipField::FIELD_VALUE => $item[self::FLAGS], - ], - ], - ]; + if (empty($visitorCache->getData()->getCampaigns())) { + return []; } - if (count($campaigns)) { - $this->logDebugSprintf( - $this->getConfig(), - FlagshipConstant::PROCESS_FETCHING_FLAGS, - FlagshipConstant::FETCH_CAMPAIGNS_FROM_CACHE, - [ - $this->getVisitor()->getVisitorId(), - $this->getVisitor()->getAnonymousId(), - $this->getVisitor()->getContext(), - $campaigns, - ($this->getNow() - $now), - ] - ); + $data = $visitorCache->getData(); + + $campaignsCache = $data->getCampaigns(); + + if (empty($campaignsCache)) { + return []; } + $campaigns = array_map(function ($item) { + $modifications = new ModificationsDTO( + $item->getFlags()->getType(), + $item->getFlags()->getValue() + ); + $variation = new VariationDTO( + $item->getVariationId(), + $modifications + ); + $variation->setReference($item->getIsReference()); + return new CampaignDTO( + $item->getCampaignId(), + $item->getVariationGroupId(), + $variation + ); + }, $campaignsCache); + + $this->logDebugSprintf( + $this->getConfig(), + FlagshipConstant::PROCESS_FETCHING_FLAGS, + FlagshipConstant::FETCH_CAMPAIGNS_FROM_CACHE, + [ + $this->getVisitor()->getVisitorId(), + $this->getVisitor()->getAnonymousId(), + $this->getVisitor()->getContext(), + $campaigns, + ($this->getNow() - $now), + ] + ); + return $campaigns; } @@ -316,7 +321,14 @@ protected function logFetchFlagsStarted(string $functionName): void ); } - protected function logFetchCampaignsSuccess($functionName, $campaigns, $now): void + /** + * + * @param string $functionName + * @param CampaignDTO[]|null $campaigns + * @param float $now + * @return void + */ + protected function logFetchCampaignsSuccess(string $functionName, ?array $campaigns, float $now): void { $this->logDebugSprintf( $this->getConfig(), @@ -326,13 +338,19 @@ protected function logFetchCampaignsSuccess($functionName, $campaigns, $now): vo $this->getVisitor()->getVisitorId(), $this->getVisitor()->getAnonymousId(), $this->getVisitor()->getContext(), - $campaigns, + $campaigns ?? [], ($this->getNow() - $now), ] ); } - protected function logFetchFlagsFromCampaigns($functionName, $flagsDTO): void + /** + * + * @param string $functionName + * @param FlagDTO[] $flagsDTO + * @return void + */ + protected function logFetchFlagsFromCampaigns(string $functionName, array $flagsDTO): void { $this->logDebugSprintf( $this->getConfig(), @@ -347,7 +365,14 @@ protected function logFetchFlagsFromCampaigns($functionName, $flagsDTO): void ); } - protected function sendTroubleshootingAndAnalyticHits($flagsDTO, $campaigns, $now): void + /** + * + * @param FlagDTO[] $flagsDTO + * @param CampaignDTO[] $campaigns + * @param float $now + * @return void + */ + protected function sendTroubleshootingAndAnalyticHits(array $flagsDTO, array $campaigns, float $now): void { $troubleshootingData = $this->getDecisionManager()->getTroubleshootingData(); $this->getTrackingManager()->setTroubleshootingData($troubleshootingData); @@ -361,7 +386,12 @@ protected function sendTroubleshootingAndAnalyticHits($flagsDTO, $campaigns, $no } - protected function getCampaignsFromCacheIfNotArray($campaigns): array + /** + * + * @param CampaignDTO[]|null $campaigns + * @return CampaignDTO[] + */ + protected function getCampaignsFromCacheIfNotArray(?array $campaigns): array { if (!is_array($campaigns)) { $campaigns = $this->fetchVisitorCampaigns($this->getVisitor()); @@ -398,9 +428,11 @@ public function fetchFlags(): void $this->logFetchCampaignsSuccess($functionName, $campaigns, $now); $campaigns = $this->getCampaignsFromCacheIfNotArray($campaigns); + $this->getVisitor()->campaigns = $campaigns; $flagsDTO = $decisionManager->getFlagsData($campaigns); + $this->getVisitor()->setFlagsDTO($flagsDTO); if ($this->getVisitor()->getFetchStatus()->getStatus() == FSFetchStatus::FETCHING) { @@ -440,7 +472,7 @@ public function sendHit(HitAbstract $hit): void /** * @param FlagDTO $flag - * @param mixed|null $defaultValue + * @param scalar|array|null $defaultValue * @return void */ protected function activateFlag(FlagDTO $flag, mixed $defaultValue = null): void @@ -452,15 +484,26 @@ protected function activateFlag(FlagDTO $flag, mixed $defaultValue = null): void $flag->getIsReference(), $flag->getCampaignType(), $flag->getSlug(), - $flag->getCampaignName(), - $flag->getVariationGroupName(), - $flag->getVariationName() + $flag->getCampaignName() ?? '', + $flag->getVariationGroupName() ?? '', + $flag->getVariationName() ?? '' ); $visitor = $this->getVisitor(); - $activateHit = new Activate($flag->getVariationGroupId(), $flag->getVariationId()); + $activateHit = new Activate( + $flag->getVariationGroupId(), + $flag->getVariationId(), + $flag->getKey(), + $flagMetadata + ); - $activateHit->setFlagKey($flag->getKey())->setFlagValue($flag->getValue())->setFlagDefaultValue($defaultValue)->setVisitorContext($visitor->getContext())->setFlagMetadata($flagMetadata)->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId())->setConfig($this->getConfig()); + $activateHit + ->setFlagValue($flag->getValue()) + ->setFlagDefaultValue($defaultValue) + ->setVisitorContext($visitor->getContext()) + ->setVisitorId($visitor->getVisitorId()) + ->setAnonymousId($visitor->getAnonymousId()) + ->setConfig($this->getConfig()); $this->getTrackingManager()->activateFlag($activateHit); @@ -469,18 +512,22 @@ protected function activateFlag(FlagDTO $flag, mixed $defaultValue = null): void $this->sendTroubleshootingHit($troubleshooting); } - private function sendFlagTroubleshooting($label, $key, $defaultValue, $visitorExposed): void + private function sendFlagTroubleshooting(TroubleshootingLabel $label, string $key, mixed $defaultValue, bool $visitorExposed): void { $visitor = $this->getVisitor(); $troubleshooting = new Troubleshooting(); - $troubleshooting->setLabel($label)->setLogLevel(LogLevel::WARNING)->setVisitorSessionId($visitor->getInstanceId())->setFlagshipInstanceId($this->getFlagshipInstanceId())->setTraffic($visitor->getTraffic())->setVisitorContext($visitor->getContext())->setFlagKey($key)->setFlagDefault($defaultValue)->setVisitorExposed($visitorExposed)->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId())->setConfig($this->getConfig()); + $troubleshooting->setLabel($label) + ->setLogLevel(LogLevel::WARNING) + ->setVisitorSessionId($visitor->getInstanceId()) + ->setFlagshipInstanceId($this->getFlagshipInstanceId()) + ->setTraffic($visitor->getTraffic())->setVisitorContext($visitor->getContext())->setFlagKey($key)->setFlagDefault($defaultValue)->setVisitorExposed($visitorExposed)->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId())->setConfig($this->getConfig()); $this->sendTroubleshootingHit($troubleshooting); } /** * @param string $key - * @param float|array|bool|int|string $defaultValue + * @param float|array|bool|int|string $defaultValue * @param FlagDTO|null $flag * @param bool $hasGetValueBeenCalled * @return void @@ -488,7 +535,7 @@ private function sendFlagTroubleshooting($label, $key, $defaultValue, $visitorEx public function visitorExposed( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $hasGetValueBeenCalled = false ): void { if (!$flag) { @@ -559,16 +606,17 @@ public function visitorExposed( /** + * * @param string $key - * @param float|array|bool|int|string $defaultValue + * @param scalar|array|null $defaultValue * @param FlagDTO|null $flag * @param boolean $userExposed - * @return float|array|bool|int|string|null + * @return scalar|array|null */ public function getFlagValue( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $userExposed = true ): float|array|bool|int|string|null { if (!$flag) { @@ -642,7 +690,7 @@ public function getFlagValue( * @param FlagDTO|null $flag * @return FSFlagMetadata */ - public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetadata + public function getFlagMetadata(string $key, ?FlagDTO $flag = null): FSFlagMetadata { $flagMetadataFuncName = 'flag.metadata'; if (!$flag) { @@ -661,9 +709,9 @@ public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetada $flag->getIsReference(), $flag->getCampaignType(), $flag->getSlug(), - $flag->getCampaignName(), - $flag->getVariationGroupName(), - $flag->getVariationName() + $flag->getCampaignName() ?? '', + $flag->getVariationGroupName() ?? '', + $flag->getVariationName() ?? '' ); } } diff --git a/src/Visitor/NoConsentStrategy.php b/src/Visitor/NoConsentStrategy.php index 68d99821..bdccdc20 100644 --- a/src/Visitor/NoConsentStrategy.php +++ b/src/Visitor/NoConsentStrategy.php @@ -27,7 +27,7 @@ public function sendHit(HitAbstract $hit): void public function visitorExposed( $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $hasGetValueBeenCalled = false ): void { $this->log(__FUNCTION__); diff --git a/src/Visitor/NotReadyStrategy.php b/src/Visitor/NotReadyStrategy.php index 323c4b35..d7286597 100644 --- a/src/Visitor/NotReadyStrategy.php +++ b/src/Visitor/NotReadyStrategy.php @@ -39,7 +39,7 @@ public function fetchFlags(): void public function getFlagValue( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $userExposed = true ): float|array|bool|int|string|null { $this->log(__FUNCTION__); @@ -52,7 +52,7 @@ public function getFlagValue( public function visitorExposed( $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $hasGetValueBeenCalled = false ): void { $this->log(__FUNCTION__); @@ -61,7 +61,7 @@ public function visitorExposed( /** * @inheritDoc */ - public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetadata + public function getFlagMetadata(string $key, ?FlagDTO $flag = null): FSFlagMetadata { $this->log(__FUNCTION__); return FSFlagMetadata::getEmpty(); diff --git a/src/Visitor/PanicStrategy.php b/src/Visitor/PanicStrategy.php index e31e820f..be04b3e9 100644 --- a/src/Visitor/PanicStrategy.php +++ b/src/Visitor/PanicStrategy.php @@ -92,7 +92,7 @@ public function visitorExposed( /** * @inheritDoc */ - public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetadata + public function getFlagMetadata(string $key, ?FlagDTO $flag = null): FSFlagMetadata { $this->log(__FUNCTION__); return FSFlagMetadata::getEmpty(); diff --git a/src/Visitor/StrategyAbstract.php b/src/Visitor/StrategyAbstract.php index e70c1fca..856740e8 100644 --- a/src/Visitor/StrategyAbstract.php +++ b/src/Visitor/StrategyAbstract.php @@ -14,14 +14,20 @@ use Flagship\Utils\ConfigManager; use Flagship\Config\FlagshipConfig; use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; use Flagship\Traits\ValidatorTrait; use Flagship\Config\BucketingConfig; +use Flagship\Model\CampaignCacheDTO; +use Flagship\Model\ModificationsDTO; use Flagship\Enum\VisitorCacheStatus; use Flagship\Traits\HasSameTypeTrait; use Flagship\Enum\TroubleshootingLabel; use Flagship\Model\TroubleshootingData; +use Flagship\Model\VisitorCacheDataDTO; use Flagship\Api\TrackingManagerAbstract; use Flagship\Decision\DecisionManagerAbstract; +use Flagship\Cache\IVisitorCacheImplementation; +use Flagship\Model\CampaignDTO; /** * @@ -80,7 +86,7 @@ public function getFlagshipInstanceId(): ?string * @param ?string $flagshipInstanceId * @return StrategyAbstract */ - public function setFlagshipInstanceId(?string $flagshipInstanceId): static + public function setFlagshipInstanceId(?string $flagshipInstanceId): self { $this->flagshipInstanceId = $flagshipInstanceId; return $this; @@ -98,7 +104,7 @@ public function getMurmurHash(): MurmurHash * @param MurmurHash $murmurHash * @return StrategyAbstract */ - public function setMurmurHash(MurmurHash $murmurHash): static + public function setMurmurHash(MurmurHash $murmurHash): self { $this->murmurHash = $murmurHash; return $this; @@ -147,26 +153,36 @@ protected function getDecisionManager(): DecisionManagerAbstract return $this->getConfigManager()->getDecisionManager(); } - abstract protected function updateContextKeyValue(string $key, mixed $value): void; + abstract protected function updateContextKeyValue(string $key, float|bool|int|string $value): void; /** + * @param array $item + * @return boolean * @throws Exception */ private function checkLookupVisitorDataV1(array $item): bool { - if (!$item || !isset($item[self::DATA]) || !isset($item[self::DATA][self::VISITOR_ID])) { + if (!isset($item[self::DATA]) || !is_array($item[self::DATA])) { + return false; + } + + $data = $item[self::DATA]; + + if (!isset($data[self::VISITOR_ID]) || !is_string($data[self::VISITOR_ID])) { return false; } - $data = $item[self::DATA]; $visitorId = $data[self::VISITOR_ID]; - if ($visitorId !== $this->getVisitor()->getVisitorId() && $visitorId !== $this->getVisitor()->getAnonymousId()) { + $currentVisitorId = $this->getVisitor()->getVisitorId(); + $anonymousId = $this->getVisitor()->getAnonymousId(); + + if ($visitorId !== $currentVisitorId && $visitorId !== $anonymousId) { throw new Exception(sprintf( self::VISITOR_ID_MISMATCH_ERROR, $visitorId, - $this->getVisitor()->getVisitorId() + $currentVisitorId )); } @@ -179,15 +195,8 @@ private function checkLookupVisitorDataV1(array $item): bool return false; } - foreach ($campaigns as $item) { - if ( - !isset( - $item[self::CAMPAIGN_ID], - $item[self::CAMPAIGN_TYPE], - $item[self::VARIATION_GROUP_ID], - $item[self::VARIATION_ID] - ) - ) { + foreach ($campaigns as $campaign) { + if (!is_array($campaign) || !$this->isValidCampaign($campaign)) { return false; } } @@ -195,9 +204,23 @@ private function checkLookupVisitorDataV1(array $item): bool return true; } + /** + * @param array $campaign + * @return bool + */ + private function isValidCampaign(array $campaign): bool + { + return isset( + $campaign[self::CAMPAIGN_ID], + $campaign[self::CAMPAIGN_TYPE], + $campaign[self::VARIATION_GROUP_ID], + $campaign[self::VARIATION_ID] + ); + } + /** - * @param array $item + * @param array $item * @return boolean * @throws Exception */ @@ -226,35 +249,38 @@ public function lookupVisitor(): void $visitor->setVisitorCacheStatus(VisitorCacheStatus::NONE); - $visitorCache = $visitorCacheInstance->lookupVisitor($this->visitor->getVisitorId()); - $hasVisitorCache = is_array($visitorCache) && count($visitorCache) > 0; + $visitorCacheArray = $visitorCacheInstance->lookupVisitor($this->visitor->getVisitorId()); + + $hasVisitorCache = !empty($visitorCacheArray); if ($hasVisitorCache) { $visitor->setVisitorCacheStatus(VisitorCacheStatus::VISITOR_ID_CACHE); } - if (!$hasVisitorCache && $visitor->getAnonymousId()) { - $visitorCache = $visitorCacheInstance->lookupVisitor($this->visitor->getAnonymousId()); - if ($visitorCache) { + $anonymousId = $visitor->getAnonymousId(); + + if (!$hasVisitorCache && $anonymousId) { + $visitorCacheArray = $visitorCacheInstance->lookupVisitor($anonymousId); + if (!empty($visitorCacheArray)) { $visitor->setVisitorCacheStatus(VisitorCacheStatus::ANONYMOUS_ID_CACHE); } } - if ($visitor->getVisitorCacheStatus() === VisitorCacheStatus::NONE) { - $visitor->visitorCache = []; + if ($visitor->getVisitorCacheStatus() === VisitorCacheStatus::NONE || empty($visitorCacheArray)) { + $visitor->visitorCache = null; return; } - if (!$this->checkLookupVisitorData($visitorCache)) { + if (!$this->checkLookupVisitorData($visitorCacheArray)) { throw new Exception(self::LOOKUP_VISITOR_JSON_OBJECT_ERROR); } - $visitor->visitorCache = $visitorCache; + $visitor->visitorCache = VisitorCacheDTO::fromArray($visitorCacheArray); - if ($visitor->getVisitorCacheStatus() === VisitorCacheStatus::VISITOR_ID_CACHE && $visitor->getAnonymousId()) { - $visitorCache = $visitorCacheInstance->lookupVisitor($this->visitor->getAnonymousId()); - if (is_array($visitorCache) && count($visitorCache) > 0) { + if ($visitor->getVisitorCacheStatus() === VisitorCacheStatus::VISITOR_ID_CACHE && $anonymousId) { + $anonymousCache = $visitorCacheInstance->lookupVisitor($anonymousId); + if (!empty($anonymousCache)) { $visitor->setVisitorCacheStatus(VisitorCacheStatus::VISITOR_ID_CACHE_WITH_ANONYMOUS_ID_CACHE); } } @@ -263,6 +289,139 @@ public function lookupVisitor(): void } } + /** + * Build campaigns cache from visitor campaigns + * + * @param VisitorAbstract $visitor + * @return array{campaigns: array, assignmentsHistory: array} + */ + private function buildCampaignsCache(VisitorAbstract $visitor): array + { + $assignmentsHistory = []; + $campaigns = []; + + foreach ($visitor->campaigns as $campaign) { + + $variationGroupId = $campaign->getVariationGroupId(); + $variationId = $campaign->getVariation()->getId(); + + $assignmentsHistory[$variationGroupId] = $variationId; + + $flags = new ModificationsDTO( + $campaign->getVariation()->getModifications()->getType(), + $campaign->getVariation()->getModifications()->getValue() + ); + $campaignCache = new CampaignCacheDTO( + $campaign->getId(), + $variationGroupId, + $variationId, + $flags + ); + + $campaignCache->setSlug($campaign->getSlug()) + ->setType($campaign->getType()) + ->setActivated(false) + ->setIsReference($campaign->getVariation()->getReference()); + + $campaigns[] = $campaignCache; + } + + return [ + 'campaigns' => $campaigns, + 'assignmentsHistory' => $assignmentsHistory + ]; + } + + /** + * Merge new assignments history with existing one + * + * @param array $newAssignments + * @param VisitorCacheDTO|null $existingCache + * @return array + */ + private function mergeAssignmentsHistory(array $newAssignments, ?VisitorCacheDTO $existingCache): array + { + $existingAssignments = $existingCache?->getData()?->getAssignmentsHistory(); + + if (empty($existingAssignments)) { + return $newAssignments; + } + + return [...$existingAssignments, ...$newAssignments]; + } + + /** + * Create visitor cache DTO with all data + * + * @param string $visitorId + * @param string|null $anonymousId + * @param VisitorAbstract $visitor + * @param array $campaigns + * @param array $assignmentsHistory + * @return VisitorCacheDTO + */ + private function createVisitorCache( + string $visitorId, + ?string $anonymousId, + VisitorAbstract $visitor, + array $campaigns, + array $assignmentsHistory + ): VisitorCacheDTO { + $data = new VisitorCacheDataDTO($visitorId, $anonymousId); + + $data + ->setConsent($visitor->hasConsented()) + ->setContext($visitor->getContext()) + ->setCampaigns($campaigns) + ->setAssignmentsHistory($assignmentsHistory); + + return new VisitorCacheDTO(self::CURRENT_VERSION, $data); + } + + /** + * Cache data for anonymous visitor + * + * @param IVisitorCacheImplementation $cacheInstance + * @param string $anonymousId + * @param VisitorAbstract $visitor + * @param array $campaigns + * @param array $assignmentsHistory + * @return void + */ + private function cacheAnonymousVisitor( + IVisitorCacheImplementation $cacheInstance, + string $anonymousId, + VisitorAbstract $visitor, + array $campaigns, + array $assignmentsHistory + ): void { + $anonymousData = new VisitorCacheDataDTO($anonymousId, null); + + $anonymousData + ->setConsent($visitor->hasConsented()) + ->setContext($visitor->getContext()) + ->setCampaigns($campaigns) + ->setAssignmentsHistory($assignmentsHistory); + + $anonymousCache = new VisitorCacheDTO(self::CURRENT_VERSION, $anonymousData); + + $cacheInstance->cacheVisitor($anonymousId, $anonymousCache->toArray()); + } + + + /** + * Determine if anonymous visitor should be cached + * + * @param VisitorAbstract $visitor + * @return bool + */ + private function shouldCacheAnonymousVisitor(VisitorAbstract $visitor): bool + { + $cacheStatus = $visitor->getVisitorCacheStatus(); + + return $cacheStatus === VisitorCacheStatus::NONE + || $cacheStatus === VisitorCacheStatus::VISITOR_ID_CACHE; + } /** * @return void @@ -275,74 +434,45 @@ public function cacheVisitor(): void return; } - $visitor = $this->getVisitor(); - $assignmentsHistory = []; - $campaigns = []; - - foreach ($visitor->campaigns as $campaign) { - $variation = $campaign[FlagshipField::FIELD_VARIATION]; - $modifications = $variation[FlagshipField::FIELD_MODIFICATIONS]; - $assignmentsHistory[$campaign[FlagshipField::FIELD_VARIATION_GROUP_ID]] = $variation[FlagshipField::FIELD_ID]; - - $campaigns[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_SLUG => $campaign[FlagshipField::FIELD_SLUG] ?? null, - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - self::ACTIVATED => false, - self::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; - } + $visitor = $this->getVisitor(); + $visitorId = $visitor->getVisitorId(); + $anonymousId = $visitor->getAnonymousId(); - if ( - isset( - $visitor->visitorCache, - $visitor->visitorCache[self::DATA], - $visitor->visitorCache[self::DATA][self::ASSIGNMENTS_HISTORY] - ) - ) { - $assignmentsHistory = array_merge( - $visitor->visitorCache[self::DATA][self::ASSIGNMENTS_HISTORY], + $campaignsData = $this->buildCampaignsCache($visitor); + + $assignmentsHistory = $this->mergeAssignmentsHistory( + $campaignsData['assignmentsHistory'], + $visitor->visitorCache + ); + + $visitorCache = $this->createVisitorCache( + $visitorId, + $anonymousId, + $visitor, + $campaignsData['campaigns'], + $assignmentsHistory + ); + + $visitorCacheInstance->cacheVisitor($visitorId, $visitorCache->toArray()); + + if ($anonymousId && $this->shouldCacheAnonymousVisitor($visitor)) { + $this->cacheAnonymousVisitor( + $visitorCacheInstance, + $anonymousId, + $visitor, + $campaignsData['campaigns'], $assignmentsHistory ); } - $data = [ - self::VERSION => self::CURRENT_VERSION, - self::DATA => [ - self::VISITOR_ID => $visitor->getVisitorId(), - self::ANONYMOUS_ID => $visitor->getAnonymousId(), - self::CONSENT => $visitor->hasConsented(), - self::CONTEXT => $visitor->getContext(), - self::CAMPAIGNS => $campaigns, - self::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; - - $visitorCacheInstance->cacheVisitor($visitor->getVisitorId(), $data); - - if ($visitor->getAnonymousId() && ($visitor->getVisitorCacheStatus() === VisitorCacheStatus::NONE || - $visitor->getVisitorCacheStatus() === VisitorCacheStatus::VISITOR_ID_CACHE)) { - $anonymousData = [ - self::VERSION => self::CURRENT_VERSION, - self::DATA => [ - self::VISITOR_ID => $visitor->getAnonymousId(), - self::ANONYMOUS_ID => null, - self::CONSENT => $visitor->hasConsented(), - self::CONTEXT => $visitor->getContext(), - self::CAMPAIGNS => $campaigns, - self::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; - $visitorCacheInstance->cacheVisitor($visitor->getAnonymousId(), $anonymousData); - } - - $visitor->visitorCache = $data; + $visitor->visitorCache = $visitorCache; } catch (Exception $exception) { - $this->logError($this->getConfig(), $exception->getMessage(), [FlagshipConstant::TAG => __FUNCTION__]); - } //end try + $this->logError( + $this->getConfig(), + $exception->getMessage(), + [FlagshipConstant::TAG => __FUNCTION__] + ); + } } @@ -398,7 +528,21 @@ public function sendSdkConfigAnalyticHit(): void $fetchThirdPartyData = $config->getFetchThirdPartyData(); } $analytic = new UsageHit(); - $analytic->setLabel(TroubleshootingLabel::SDK_CONFIG)->setLogLevel(LogLevel::INFO)->setSdkConfigMode($config->getDecisionMode())->setSdkConfigLogLevel($config->getLogLevel())->setSdkConfigTimeout($config->getTimeout())->setSdkConfigTrackingManagerConfigStrategy($config->getCacheStrategy())->setSdkConfigBucketingUrl($bucketingUrl)->setSdkConfigFetchThirdPartyData($fetchThirdPartyData)->setSdkConfigUsingOnVisitorExposed(!!$config->getOnVisitorExposed())->setSdkConfigUsingCustomHitCache(!!$config->getHitCacheImplementation())->setSdkConfigUsingCustomVisitorCache(!!$config->getVisitorCacheImplementation())->setSdkStatus($visitor->getSdkStatus())->setFlagshipInstanceId($this->getFlagshipInstanceId())->setVisitorId($this->getFlagshipInstanceId())->setConfig($config); + $analytic->setLabel(TroubleshootingLabel::SDK_CONFIG) + ->setLogLevel(LogLevel::INFO) + ->setSdkConfigMode($config->getDecisionMode()) + ->setSdkConfigLogLevel($config->getLogLevel()) + ->setSdkConfigTimeout($config->getTimeout()) + ->setSdkConfigTrackingManagerConfigStrategy($config->getCacheStrategy()) + ->setSdkConfigBucketingUrl($bucketingUrl) + ->setSdkConfigFetchThirdPartyData($fetchThirdPartyData) + ->setSdkConfigUsingOnVisitorExposed(!!$config->getOnVisitorExposed()) + ->setSdkConfigUsingCustomHitCache(!!$config->getHitCacheImplementation()) + ->setSdkConfigUsingCustomVisitorCache(!!$config->getVisitorCacheImplementation()) + ->setSdkStatus($visitor->getSdkStatus()) + ->setFlagshipInstanceId($this->getFlagshipInstanceId()) + ->setVisitorId($this->getFlagshipInstanceId() ?? "") + ->setConfig($config); $this->getTrackingManager()->addUsageHit($analytic); } @@ -406,7 +550,7 @@ public function sendSdkConfigAnalyticHit(): void * /** * @param TroubleshootingData $troubleshootingData * @param FlagDTO[] $flagsDTO - * @param array $campaigns + * @param CampaignDTO[] $campaigns * @param float|int $now * @return void * / @@ -437,7 +581,29 @@ public function sendFetchFlagsTroubleshooting( $visitor->setTraffic($traffic); $troubleshootingHit = new Troubleshooting(); - $troubleshootingHit->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS)->setLogLevel(LogLevel::INFO)->setVisitorSessionId($visitor->getInstanceId())->setFlagshipInstanceId($visitor->getFlagshipInstanceId())->setTraffic($traffic)->setVisitorAssignmentHistory($assignmentHistory)->setVisitorContext($visitor->getContext())->setSdkStatus($visitor->getSdkStatus())->setVisitorCampaigns($campaigns)->setFlagshipInstanceId($this->getFlagshipInstanceId())->setVisitorFlags($flagsDTO)->setVisitorConsent($visitor->hasConsented())->setVisitorIsAuthenticated(!!$visitor->getAnonymousId())->setHttpResponseTime(($this->getNow() - $now))->setSdkConfigMode($config->getDecisionMode())->setSdkConfigLogLevel($config->getLogLevel())->setSdkConfigTimeout($config->getTimeout())->setSdkConfigBucketingUrl($bucketingUrl)->setSdkConfigFetchThirdPartyData($fetchThirdPartyData)->setSdkConfigUsingOnVisitorExposed(!!$config->getOnVisitorExposed())->setSdkConfigUsingCustomHitCache(!!$config->getHitCacheImplementation())->setSdkConfigUsingCustomVisitorCache(!!$config->getVisitorCacheImplementation())->setSdkConfigTrackingManagerConfigStrategy($config->getCacheStrategy())->setVisitorId($visitor->getVisitorId())->setAnonymousId($visitor->getAnonymousId())->setConfig($config); + $troubleshootingHit->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS) + ->setLogLevel(LogLevel::INFO) + ->setVisitorSessionId($visitor->getInstanceId()) + ->setFlagshipInstanceId($visitor->getFlagshipInstanceId()) + ->setTraffic($traffic) + ->setVisitorAssignmentHistory($assignmentHistory) + ->setVisitorContext($visitor->getContext()) + ->setSdkStatus($visitor->getSdkStatus()) + ->setVisitorCampaigns($campaigns) + ->setFlagshipInstanceId($this->getFlagshipInstanceId()) + ->setVisitorFlags($flagsDTO) + ->setVisitorConsent($visitor->hasConsented()) + ->setVisitorIsAuthenticated(!!$visitor->getAnonymousId()) + ->setHttpResponseTime(($this->getNow() - $now)) + ->setSdkConfigMode($config->getDecisionMode()) + ->setSdkConfigLogLevel($config->getLogLevel()) + ->setSdkConfigTimeout($config->getTimeout()) + ->setSdkConfigBucketingUrl($bucketingUrl) + ->setSdkConfigFetchThirdPartyData($fetchThirdPartyData) + ->setSdkConfigUsingOnVisitorExposed(!!$config->getOnVisitorExposed()) + ->setSdkConfigUsingCustomHitCache(!!$config->getHitCacheImplementation()) + ->setSdkConfigUsingCustomVisitorCache(!!$config->getVisitorCacheImplementation())->setSdkConfigTrackingManagerConfigStrategy($config->getCacheStrategy())->setVisitorId($visitor->getVisitorId()) + ->setAnonymousId($visitor->getAnonymousId())->setConfig($config); $this->sendTroubleshootingHit($troubleshootingHit); } @@ -453,14 +619,25 @@ public function sendConsentHitTroubleshooting(): void $this->getVisitor()->setConsentHitTroubleshooting(null); } + /** + * @param array $context + * @return void + */ public function initialContext(array $context): void { if (count($context) == 0) { return; } foreach ($context as $itemKey => $item) { + if (!is_scalar($item) || empty($itemKey) || !is_string($itemKey)) { + $this->logError( + $this->getVisitor()->getConfig(), + FlagshipConstant::CONTEXT_PARAM_ERROR, + [FlagshipConstant::TAG => FlagshipConstant::TAG_UPDATE_CONTEXT] + ); + continue; + } $this->updateContextKeyValue($itemKey, $item); } } - } diff --git a/src/Visitor/Visitor.php b/src/Visitor/Visitor.php index 48781ef6..04d3c412 100644 --- a/src/Visitor/Visitor.php +++ b/src/Visitor/Visitor.php @@ -59,7 +59,7 @@ public function getVisitorId(): string } - public function setVisitorId($visitorId): static + public function setVisitorId(string $visitorId): self { $this->getVisitorDelegate()->setVisitorId($visitorId); return $this; @@ -113,7 +113,7 @@ public function getAnonymousId(): ?string /** * @inheritDoc */ - public function updateContext(string $key, float|bool|int|string|null $value): void + public function updateContext(string $key, float|bool|int|string $value): void { $this->getVisitorDelegate()->updateContext($key, $value); } diff --git a/src/Visitor/VisitorAbstract.php b/src/Visitor/VisitorAbstract.php index c7b3a9ef..cef95938 100644 --- a/src/Visitor/VisitorAbstract.php +++ b/src/Visitor/VisitorAbstract.php @@ -8,14 +8,17 @@ use Flagship\Model\FlagDTO; use Flagship\Enum\FSSdkStatus; use Flagship\Utils\MurmurHash; +use Flagship\Model\CampaignDTO; use Flagship\Hit\Troubleshooting; use Flagship\Utils\ConfigManager; use Flagship\Config\FlagshipConfig; use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; use Flagship\Traits\ValidatorTrait; +use Flagship\Enum\VisitorCacheStatus; use Flagship\Utils\ContainerInterface; +use Flagship\Model\VisitorCacheDataDTO; use Flagship\Model\FetchFlagsStatusInterface; -use Flagship\Enum\visitorCacheStatus; abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, VisitorFlagInterface { @@ -38,7 +41,7 @@ abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, Vi private ?string $anonymousId = null; /** - * @var array + * @var array */ public array $context = []; @@ -48,7 +51,7 @@ abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, Vi protected array $flagsDTO = []; /** - * @var array + * @var CampaignDTO[] */ public array $campaigns = []; @@ -68,14 +71,9 @@ abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, Vi private ContainerInterface $dependencyIContainer; /** - * @var ?array + * @var ?VisitorCacheDTO */ - public ?array $visitorCache; - - /** - * @var string - */ - private string $flagSyncStatus; + public ?VisitorCacheDTO $visitorCache; /** * @var int|float|null @@ -97,7 +95,7 @@ abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, Vi protected ?Troubleshooting $ConsentHitTroubleshooting = null; /** - * @var callable + * @var ?callable */ protected $onFetchFlagsStatusChanged; @@ -108,6 +106,12 @@ abstract class VisitorAbstract implements VisitorInterface, JsonSerializable, Vi */ protected FetchFlagsStatusInterface $fetchStatus; + /** + * + * @var array + */ + protected array $deDuplicationCache = []; + protected bool $hasContextBeenUpdated = true; public function setHasContextBeenUpdated(bool $hasContextBeenUpdated): static @@ -133,21 +137,21 @@ public function setVisitorCacheStatus(visitorCacheStatus $visitorCacheStatus): s $this->visitorCacheStatus = $visitorCacheStatus; return $this; } - protected array $deDuplicationCache = []; + /** - * @return callable + * @return ?callable */ - public function getOnFetchFlagsStatusChanged(): callable + public function getOnFetchFlagsStatusChanged(): callable|null { return $this->onFetchFlagsStatusChanged; } /** - * @param callable $onFetchFlagsStatusChanged + * @param callable|null $onFetchFlagsStatusChanged * @return VisitorAbstract */ - public function setOnFetchFlagsStatusChanged(callable $onFetchFlagsStatusChanged): static + public function setOnFetchFlagsStatusChanged(?callable $onFetchFlagsStatusChanged): self { $this->onFetchFlagsStatusChanged = $onFetchFlagsStatusChanged; return $this; @@ -182,7 +186,7 @@ public function getConsentHitTroubleshooting(): ?Troubleshooting * @param Troubleshooting|null $ConsentHitTroubleshooting * @return VisitorAbstract */ - public function setConsentHitTroubleshooting(?Troubleshooting $ConsentHitTroubleshooting): static + public function setConsentHitTroubleshooting(?Troubleshooting $ConsentHitTroubleshooting): self { $this->ConsentHitTroubleshooting = $ConsentHitTroubleshooting; return $this; @@ -208,7 +212,7 @@ public function getFlagshipInstanceId(): ?string * @param ?string $flagshipInstanceId * @return VisitorAbstract */ - public function setFlagshipInstanceId(?string $flagshipInstanceId): static + public function setFlagshipInstanceId(?string $flagshipInstanceId): self { $this->flagshipInstanceId = $flagshipInstanceId; return $this; @@ -225,7 +229,7 @@ public static function setSdkStatus(FSSdkStatus $sdkStatus): void public function __construct() { - $this->visitorCache = []; + $this->visitorCache = null; $this->instanceId = $this->newGuid(); } @@ -249,7 +253,7 @@ public function getTraffic(): float|int|null * @param float|int $traffic * @return VisitorAbstract */ - public function setTraffic(float|int $traffic): static + public function setTraffic(float|int $traffic): self { $this->traffic = $traffic; return $this; @@ -267,7 +271,7 @@ public function getConfigManager(): ConfigManager * @param FlagDTO[] $flagsDTO * @return VisitorAbstract */ - public function setFlagsDTO(array $flagsDTO): static + public function setFlagsDTO(array $flagsDTO): self { $this->flagsDTO = $flagsDTO; return $this; @@ -286,7 +290,7 @@ public function getFlagsDTO(): array * @param ConfigManager $configManager * @return VisitorAbstract */ - public function setConfigManager(ConfigManager $configManager): static + public function setConfigManager(ConfigManager $configManager): self { $this->configManager = $configManager; return $this; @@ -305,7 +309,7 @@ public function getVisitorId(): string * @param string $visitorId * @return VisitorAbstract */ - public function setVisitorId(string $visitorId): static + public function setVisitorId(string $visitorId): self { if (empty($visitorId)) { $this->logError( @@ -334,7 +338,7 @@ public function getAnonymousId(): ?string * @param string|null $anonymousId * @return VisitorAbstract */ - public function setAnonymousId(?string $anonymousId): static + public function setAnonymousId(?string $anonymousId): self { $this->anonymousId = $anonymousId; return $this; @@ -342,7 +346,7 @@ public function setAnonymousId(?string $anonymousId): static /** - * @return array + * @return array */ public function getContext(): array { @@ -353,7 +357,7 @@ public function getContext(): array /** * Clear the current context and set a new context value * - * @param array $context collection of keys, values. e.g: ["age"=>42, "vip"=>true, "country"=>"UK"] + * @param array $context collection of keys, values. e.g: ["age"=>42, "vip"=>true, "country"=>"UK"] * @return void */ public function setContext(array $context): void @@ -362,6 +366,12 @@ public function setContext(array $context): void $this->updateContextCollection($context); } + /** + * + * + * @param array $context + * @return void + */ public function initialContext(array $context): void { $this->context = []; @@ -382,7 +392,7 @@ public function getConfig(): FlagshipConfig * @param FlagshipConfig $config * @return VisitorAbstract */ - public function setConfig(FlagshipConfig $config): static + public function setConfig(FlagshipConfig $config): self { $this->config = $config; return $this; @@ -395,17 +405,19 @@ public function setConfig(FlagshipConfig $config): static */ protected function getStrategy(): StrategyAbstract { - if (Flagship::getStatus() === FSSdkStatus::SDK_NOT_INITIALIZED) { - $strategy = $this->getDependencyIContainer()->get(NotReadyStrategy::class, [$this], true); - } elseif (Flagship::getStatus() === FSSdkStatus::SDK_PANIC) { - $strategy = $this->getDependencyIContainer()->get(PanicStrategy::class, [$this], true); - } elseif (!$this->hasConsented()) { - $strategy = $this->getDependencyIContainer()->get(NoConsentStrategy::class, [$this], true); - } else { - $strategy = $this->getDependencyIContainer()->get(DefaultStrategy::class, [$this], true); - } + $strategyClass = match (true) { + Flagship::getStatus() === FSSdkStatus::SDK_NOT_INITIALIZED => NotReadyStrategy::class, + Flagship::getStatus() === FSSdkStatus::SDK_PANIC => PanicStrategy::class, + !$this->hasConsented() => NoConsentStrategy::class, + default => DefaultStrategy::class, + }; + + /** @var StrategyAbstract $strategy */ + $strategy = $this->getDependencyIContainer()->get($strategyClass, [$this], true); + $strategy->setMurmurHash(new MurmurHash()); $strategy->setFlagshipInstanceId($this->getFlagshipInstanceId()); + return $strategy; } @@ -460,9 +472,9 @@ public function sendTroubleshootingHit(Troubleshooting $hit): void public function jsonSerialize(): mixed { return [ - 'visitorId' => $this->getVisitorId(), - 'context' => $this->getContext(), - 'hasConsent' => $this->hasConsented(), - ]; + 'visitorId' => $this->getVisitorId(), + 'context' => $this->getContext(), + 'hasConsent' => $this->hasConsented(), + ]; } } diff --git a/src/Visitor/VisitorBuilder.php b/src/Visitor/VisitorBuilder.php index d3468b8e..b013d9e1 100644 --- a/src/Visitor/VisitorBuilder.php +++ b/src/Visitor/VisitorBuilder.php @@ -20,7 +20,7 @@ class VisitorBuilder private bool $hasConsented; /** - * @var array + * @var array */ private array $context; /** @@ -97,7 +97,7 @@ public static function builder( * @param bool $isAuthenticated true for an authenticated visitor, false for an anonymous visitor. * @return VisitorBuilder */ - public function setIsAuthenticated(bool $isAuthenticated): static + public function setIsAuthenticated(bool $isAuthenticated): self { $this->isAuthenticated = $isAuthenticated; return $this; @@ -106,11 +106,11 @@ public function setIsAuthenticated(bool $isAuthenticated): static /** * Specify visitor initial context key / values used for targeting. * Context key must be String, and value type must be one of the following : Number, Boolean, String. - * @param array $context : visitor context. + * @param array $context : visitor context. * e.g: ["age"=>42, "vip"=>true, "country"=>"UK"]. * @return VisitorBuilder */ - public function setContext(array $context): static + public function setContext(array $context): self { $this->context = $context; return $this; @@ -123,7 +123,7 @@ public function setContext(array $context): static * @param callable $onFetchFlagsStatusChanged * @return VisitorBuilder */ - public function setOnFetchFlagsStatusChanged(callable $onFetchFlagsStatusChanged): static + public function setOnFetchFlagsStatusChanged(callable $onFetchFlagsStatusChanged): self { $this->onFetchFlagsStatusChanged = $onFetchFlagsStatusChanged; return $this; @@ -135,19 +135,27 @@ public function setOnFetchFlagsStatusChanged(callable $onFetchFlagsStatusChanged */ public function build(): VisitorInterface { + /** + * @var VisitorDelegate $visitorDelegate + */ $visitorDelegate = $this->dependencyIContainer->get(VisitorDelegate::class, [ - $this->dependencyIContainer, - $this->configManager, - $this->visitorId, - $this->isAuthenticated, - $this->context, - $this->hasConsented, - $this->flagshipInstance, - $this->onFetchFlagsStatusChanged, - ], true); + $this->dependencyIContainer, + $this->configManager, + $this->visitorId, + $this->isAuthenticated, + $this->context, + $this->hasConsented, + $this->flagshipInstance, + $this->onFetchFlagsStatusChanged, + ], true); $visitorDelegate->setFlagshipInstanceId($this->flagshipInstance); - return $this->dependencyIContainer->get(Visitor::class, [$visitorDelegate], true); + /** + * @var VisitorInterface $visitor + */ + $visitor = $this->dependencyIContainer->get(Visitor::class, [$visitorDelegate], true); + + return $visitor; } } diff --git a/src/Visitor/VisitorCoreInterface.php b/src/Visitor/VisitorCoreInterface.php index 28bbacea..f1f3ebf3 100644 --- a/src/Visitor/VisitorCoreInterface.php +++ b/src/Visitor/VisitorCoreInterface.php @@ -6,68 +6,68 @@ interface VisitorCoreInterface { - /** - * Set if visitor has consented for private data usage. - * @param bool $hasConsented True if the visitor has consented false otherwise. - * @return void - */ - public function setConsent(bool $hasConsented): void; - /** - * Update the visitor context values, matching the given keys, used for targeting. - * - * A new context value associated with this key will be created if there is no previous matching value. - * Context key must be String, and value type must be one of the following : Number, Boolean, String. - * - * @param string $key context key. - * @param bool|string|numeric $value : context value. - * @return void - */ - public function updateContext(string $key, float|bool|int|string|null $value): void; + /** + * Set if visitor has consented for private data usage. + * @param bool $hasConsented True if the visitor has consented false otherwise. + * @return void + */ + public function setConsent(bool $hasConsented): void; + /** + * Update the visitor context values, matching the given keys, used for targeting. + * + * A new context value associated with this key will be created if there is no previous matching value. + * Context key must be String, and value type must be one of the following : Number, Boolean, String. + * + * @param string $key context key. + * @param scalar $value : context value. + * @return void + */ + public function updateContext(string $key, float|bool|int|string $value): void; - /** - * Update the visitor context values, matching the given keys, used for targeting. - * - * A new context value associated with this key will be created if there is no previous matching value. - * Context keys must be String, and values types must be one of the following : Number, Boolean, String. - * - * @param array $context collection of keys, values. e.g: ["age"=>42, "IsVip"=>true, "country"=>"UK"] - */ - public function updateContextCollection(array $context); + /** + * Update the visitor context values, matching the given keys, used for targeting. + * + * A new context value associated with this key will be created if there is no previous matching value. + * Context keys must be String, and values types must be one of the following : Number, Boolean, String. + * + * @param array $context collection of keys, values. e.g: ["age"=>42, "IsVip"=>true, "country"=>"UK"] + */ + public function updateContextCollection(array $context): void; - /** - * Clear the visitor's context - * @return void - */ - public function clearContext(): void; + /** + * Clear the visitor's context + * @return void + */ + public function clearContext(): void; - /** - * In DecisionApi Mode this function calls the Flagship Decision API to run - * campaign assignments according to the current user context - * and retrieve applicable flags.
- * In bucketing Mode, it checks bucketing file, - * validates campaigns targeting the visitor, - * assigns a variation and retrieve applicable flags - * @return void - */ - public function fetchFlags(): void; + /** + * In DecisionApi Mode this function calls the Flagship Decision API to run + * campaign assignments according to the current user context + * and retrieve applicable flags.
+ * In bucketing Mode, it checks bucketing file, + * validates campaigns targeting the visitor, + * assigns a variation and retrieve applicable flags + * @return void + */ + public function fetchFlags(): void; - /** - * Send a Hit to Flagship servers for reporting. - * @param HitAbstract $hit - * @return void - */ - public function sendHit(HitAbstract $hit): void; + /** + * Send a Hit to Flagship servers for reporting. + * @param HitAbstract $hit + * @return void + */ + public function sendHit(HitAbstract $hit): void; - /** - * Authenticate anonymous visitor - * @param string $visitorId - * @return void - */ - public function authenticate(string $visitorId): void; + /** + * Authenticate anonymous visitor + * @param string $visitorId + * @return void + */ + public function authenticate(string $visitorId): void; - /** - * This function change authenticated Visitor to anonymous visitor - * @return void - */ - public function unauthenticate(): void; + /** + * This function change authenticated Visitor to anonymous visitor + * @return void + */ + public function unauthenticate(): void; } diff --git a/src/Visitor/VisitorDelegate.php b/src/Visitor/VisitorDelegate.php index ad90e545..394e99d2 100644 --- a/src/Visitor/VisitorDelegate.php +++ b/src/Visitor/VisitorDelegate.php @@ -29,7 +29,7 @@ class VisitorDelegate extends VisitorAbstract * @param ConfigManager $configManager * @param ?string $visitorId visitor unique identifier. * @param boolean $isAuthenticated - * @param array $context visitor context. e.g: ["age"=>42, "isVip"=>true, "country"=>"UK"] + * @param array $context visitor context. e.g: ["age"=>42, "isVip"=>true, "country"=>"UK"] * @param boolean $hasConsented * @param string|null $flagshipInstanceId * @param callable|null $onFetchFlagsStatusChanged @@ -41,8 +41,8 @@ public function __construct( bool $isAuthenticated = false, array $context = [], bool $hasConsented = false, - string $flagshipInstanceId = null, - callable $onFetchFlagsStatusChanged = null + ?string $flagshipInstanceId = null, + ?callable $onFetchFlagsStatusChanged = null ) { parent::__construct(); $this->onFetchFlagsStatusChanged = $onFetchFlagsStatusChanged; @@ -78,7 +78,7 @@ private function loadPredefinedContext(): void /** * @inheritDoc */ - public function updateContext(string $key, float|bool|int|string|null $value): void + public function updateContext(string $key, float|bool|int|string $value): void { $this->getStrategy()->updateContext($key, $value); } @@ -139,7 +139,7 @@ public function fetchFlags(): void public function visitorExposed( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $hasGetValueBeenCalled = false ): void { $this->getStrategy()->visitorExposed($key, $defaultValue, $flag, $hasGetValueBeenCalled); @@ -148,11 +148,14 @@ public function visitorExposed( /** * @inheritDoc + * @phpstan-template T of scalar|array|null + * @param T $defaultValue + * @return T */ public function getFlagValue( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $userExposed = true ): float|array|bool|int|string|null { return $this->getStrategy()->getFlagValue($key, $defaultValue, $flag, $userExposed); @@ -162,7 +165,7 @@ public function getFlagValue( /** * @inheritDoc */ - public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetadata + public function getFlagMetadata(string $key, ?FlagDTO $flag = null): FSFlagMetadata { return $this->getStrategy()->getFlagMetadata($key, $flag); } @@ -182,8 +185,8 @@ public function getFlag(string $key): FSFlagInterface FlagshipConstant::GET_FLAG, $this->flagSyncStatusMessage($fetchFlagsStatus->getReason()), [ - $this->getVisitorId(), - $key, + $this->getVisitorId(), + $key, ] ); } diff --git a/src/Visitor/VisitorFlagInterface.php b/src/Visitor/VisitorFlagInterface.php index 49a15d09..41b53312 100644 --- a/src/Visitor/VisitorFlagInterface.php +++ b/src/Visitor/VisitorFlagInterface.php @@ -11,7 +11,7 @@ interface VisitorFlagInterface * Returns the value from the assigned campaign variation or the Flag default value if the Flag does not exist, * or if types are different. * @param string $key - * @param array|bool|string|numeric $defaultValue + * @param mixed[]|bool|string|numeric $defaultValue * @param FlagDTO|null $flag * @param bool $hasGetValueBeenCalled * @return void @@ -19,21 +19,23 @@ interface VisitorFlagInterface public function visitorExposed( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $hasGetValueBeenCalled = false ): void; /** + * + * @phpstan-template T of scalar|array|null * @param string $key - * @param array|bool|string|numeric $defaultValue + * @param T $defaultValue * @param FlagDTO|null $flag * @param bool $userExposed - * @return float|array|bool|int|string|null + * @return T */ public function getFlagValue( string $key, float|array|bool|int|string|null $defaultValue, - FlagDTO $flag = null, + ?FlagDTO $flag = null, bool $userExposed = true ): float|array|bool|int|string|null; @@ -42,5 +44,5 @@ public function getFlagValue( * @param FlagDTO|null $flag * @return FSFlagMetadata */ - public function getFlagMetadata(string $key, FlagDTO $flag = null): FSFlagMetadata; + public function getFlagMetadata(string $key, ?FlagDTO $flag = null): FSFlagMetadata; } diff --git a/src/Visitor/VisitorInterface.php b/src/Visitor/VisitorInterface.php index 308b894b..37875c2a 100644 --- a/src/Visitor/VisitorInterface.php +++ b/src/Visitor/VisitorInterface.php @@ -39,7 +39,7 @@ public function hasConsented(): bool; /** * Get the current context * - * @return array + * @return array */ public function getContext(): array; diff --git a/tests/Api/BatchingOnFailedCachingStrategyTest.php b/tests/Api/BatchingOnFailedCachingStrategyTest.php index f0a1eedc..7ed26128 100644 --- a/tests/Api/BatchingOnFailedCachingStrategyTest.php +++ b/tests/Api/BatchingOnFailedCachingStrategyTest.php @@ -4,8 +4,6 @@ namespace Flagship\Api; -require_once __DIR__ . "/../Traits/Round.php"; - use DateTime; use Exception; use Flagship\Hit\Page; @@ -15,6 +13,7 @@ use Flagship\Hit\HitBatch; use Flagship\Hit\UsageHit; use Flagship\Enum\LogLevel; +use phpmock\phpunit\PHPMock; use Flagship\Hit\HitAbstract; use Flagship\Traits\LogTrait; use Flagship\Hit\ActivateBatch; @@ -30,11 +29,13 @@ use Flagship\Enum\TroubleshootingLabel; use Flagship\Model\TroubleshootingData; use Flagship\Utils\HttpClientInterface; +use Flagship\Cache\IHitCacheImplementation; use PHPUnit\Framework\MockObject\MockObject; class BatchingOnFailedCachingStrategyTest extends TestCase { use LogTrait; + use PHPMock; public function testGeneralMethods() { @@ -50,7 +51,22 @@ public function testGeneralMethods() $strategy->hydrateHitsPoolQueue($key, $page); $this->assertSame([$key => $page], $strategy->getHitsPoolQueue()); - $activate = new Activate("varGroupId", "varID"); + $activate = new Activate( + "varGroupId", + "varID", + $key, + new FSFlagMetadata( + "campaignId", + "varGroupId", + "varID", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + ) + ); $activateKey = "activate-key"; $strategy->hydrateActivatePoolQueue($activateKey, $activate); $this->assertSame([$activateKey => $activate], $strategy->getActivatePoolQueue()); @@ -128,18 +144,48 @@ public function testAddHit() 'createdAt' => 1676542078047, ]; - $page3 = HitAbstract::hydrate(Page::getClassName(), $contentPage3); + $page3 = HitAbstract::hydrate(Page::class, $contentPage3); $page3->setConfig($config); $strategy->hydrateHitsPoolQueue($page3Key, $page3); - $activate = new Activate("varGrId", "varId"); + $activate = new Activate( + "varGrId", + "varId", + $page3Key, + new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + ) + ); $activate->setConfig($config)->setVisitorId($visitorId); $strategy->activateFlag($activate); - $activate2 = new Activate("varGrId", "varId"); + $activate2 = new Activate( + "varGrId", + "varId", + $page3Key, + new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + ) + ); $activate2->setConfig($config)->setVisitorId($newVisitor); $strategy->activateFlag($activate2); @@ -158,7 +204,7 @@ public function testAddHit() 'createdAt' => 1676542078044, ]; - $activate3 = HitAbstract::hydrate(Activate::getClassName(), $contentActivate); + $activate3 = HitAbstract::hydrate(Activate::class, $contentActivate); $activate3->setConfig($config); $strategy->hydrateActivatePoolQueue($activate3Key, $activate3); @@ -216,6 +262,8 @@ public function testAddHit() public function testSendActivateHit() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -223,10 +271,37 @@ public function testSendActivateHit() $url = FlagshipConstant::BASE_API_URL . '/' . FlagshipConstant::URL_ACTIVATE_MODIFICATION; - $activate = new Activate("varGrId", "VarId"); + $activate3Key = "$visitorId:51d18dbf-53ba-4aec-9bff-0d295c1d5d02"; + + $activate = new Activate( + "varGrId", + "VarId", + $activate3Key, + new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + ) + ); $activate->setConfig($config)->setVisitorId($visitorId); - $activate2 = new Activate("varGrId", "VarId"); + $activate2 = new Activate("varGrId", "VarId", $activate3Key, new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate2->setConfig($config)->setVisitorId($visitorId); /** @@ -253,7 +328,7 @@ public function testSendActivateHit() $strategy->activateFlag($activate); $strategy->activateFlag($activate2); - $activate3Key = "$visitorId:51d18dbf-53ba-4aec-9bff-0d295c1d5d02"; + $contentActivate = [ 'variationGroupId' => 'cagt08da51hg0787cns0', @@ -270,7 +345,7 @@ public function testSendActivateHit() 'createdAt' => 1676542078044, ]; - $activate3 = HitAbstract::hydrate(Activate::getClassName(), $contentActivate); + $activate3 = HitAbstract::hydrate(Activate::class, $contentActivate); $activate3->setConfig($config); $strategy->hydrateActivatePoolQueue($activate3Key, $activate3); @@ -312,6 +387,9 @@ public function testSendActivateHit() public function testSendActivateBatch() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); + $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -343,7 +421,17 @@ public function testSendActivateBatch() $activates = []; for ($i = 0; $i < 300; $i++) { - $activate = new Activate("varGrId", "VarId"); + $activate = new Activate("varGrId", "VarId", "key" . $i, new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setConfig($config)->setVisitorId($visitorId); $strategy->activateFlag($activate); $activates[] = $activate; @@ -367,6 +455,10 @@ public function testSendActivateBatch() public function testOnUserExposed() { + + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); + $config = new DecisionApiConfig(); $visitorId = "visitorId"; $context = ["key" => "value"]; @@ -386,7 +478,7 @@ public function testOnUserExposed() $flagValue1 = "value1"; $flagDefaultValue1 = "defaultValue1"; - $activate = new Activate($variationGroupId1, $variationId1); + $flagMetadata1 = new FSFlagMetadata( $campaignId1, @@ -400,7 +492,19 @@ public function testOnUserExposed() $variationName1 ); - $activate->setConfig($config)->setVisitorId($visitorId)->setVisitorContext($context)->setFlagKey($flagKey1)->setFlagValue($flagValue1)->setFlagDefaultValue($flagDefaultValue1)->setFlagMetadata($flagMetadata1); + $activate = new Activate( + $variationGroupId1, + $variationId1, + $flagKey1, + $flagMetadata1 + ); + + $activate + ->setVisitorContext($context) + ->setFlagValue($flagValue1) + ->setFlagDefaultValue($flagDefaultValue1) + ->setConfig($config) + ->setVisitorId($visitorId); $variationGroupId2 = "variationGroupId2"; $variationGroupName2 = "variationGroupName2"; @@ -424,8 +528,13 @@ public function testOnUserExposed() $variationName2 ); - $activate2 = new Activate($variationGroupId2, $variationId2); - $activate2->setConfig($config)->setVisitorId($visitorId)->setVisitorContext($context)->setFlagKey($flagKey2)->setFlagValue($flagValue2)->setFlagDefaultValue($flagDefaultValue2)->setFlagMetadata($flagMetadata2); + $activate2 = new Activate($variationGroupId2, $variationId2, $flagKey2, $flagMetadata2); + $activate2 + ->setVisitorContext($context) + ->setFlagValue($flagValue2) + ->setFlagDefaultValue($flagDefaultValue2) + ->setConfig($config) + ->setVisitorId($visitorId); $strategy = $this->getMockForAbstractClass( "Flagship\Api\BatchingOnFailedCachingStrategy", @@ -510,6 +619,8 @@ public function testOnUserExposed() public function testOnUserExposedError() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; $context = ["key" => "value"]; @@ -527,8 +638,6 @@ public function testOnUserExposedError() $variationName1 = "variationName1"; $campaignName1 = "campaignName1"; - $activate = new Activate($variationGroupId1, $variationId1); - $flagMetadata1 = new FSFlagMetadata( $campaignId1, $variationGroupId1, @@ -541,7 +650,20 @@ public function testOnUserExposedError() $variationName1 ); - $activate->setConfig($config)->setVisitorId($visitorId)->setVisitorContext($context)->setFlagKey($flagKey1)->setFlagValue($flagValue1)->setFlagMetadata($flagMetadata1); + $activate = new Activate( + $variationGroupId1, + $variationId1, + $flagKey1, + $flagMetadata1 + ); + + + $activate + ->setVisitorContext($context) + ->setFlagValue($flagValue1) + ->setConfig($config) + ->setVisitorId($visitorId) + ; $variationGroupId2 = "variationGroupId2"; $variationId2 = "variationId2"; @@ -564,8 +686,15 @@ public function testOnUserExposedError() $variationName2 ); - $activate2 = new Activate($variationGroupId2, $variationId2); - $activate2->setConfig($config)->setVisitorId($visitorId)->setVisitorContext($context)->setFlagKey($flagKey2)->setFlagValue($flagValue2)->setFlagMetadata($flagMetadata2); + $activate2 = new Activate( + $variationGroupId2, + $variationId2, + $flagKey2, + $flagMetadata2 + ); + $activate2->setVisitorContext($context) + ->setFlagValue($flagValue2) + ->setConfig($config)->setVisitorId($visitorId); $strategy = $this->getMockForAbstractClass( "Flagship\Api\BatchingOnFailedCachingStrategy", @@ -592,7 +721,9 @@ public function testOnUserExposedError() $requestBody = $activateBatch->toApiKeys(); - $httpClientMock->expects($this->once())->method("post")->with($url, [], $requestBody); + $httpClientMock->expects($this->once()) + ->method("post") + ->with($url, [], $requestBody); $count = 0; @@ -603,7 +734,9 @@ public function testOnUserExposedError() throw new Exception($exceptionMessage); }); - $strategy->expects($this->exactly(2))->method("logErrorSprintf"); + $strategy->expects($this->exactly(2)) + + ->method("logErrorSprintf"); $this->assertCount(2, $strategy->getActivatePoolQueue()); @@ -616,6 +749,9 @@ public function testOnUserExposedError() } public function testSendActivateHitFailed() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); + $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -623,10 +759,30 @@ public function testSendActivateHitFailed() $url = FlagshipConstant::BASE_API_URL . '/' . FlagshipConstant::URL_ACTIVATE_MODIFICATION; - $activate = new Activate("varGrId", "VarId"); + $activate = new Activate("varGrId", "VarId", "key", new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setConfig($config)->setVisitorId($visitorId); - $activate2 = new Activate("varGrId", "VarId"); + $activate2 = new Activate("varGrId", "VarId", "key", new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate2->setConfig($config)->setVisitorId($visitorId); @@ -657,7 +813,9 @@ public function testSendActivateHitFailed() $requestBody = $activateBatch->toApiKeys(); $exception = new Exception("activate error"); - $httpClientMock->expects($this->once())->method("post")->with($url, [], $requestBody)->willThrowException($exception); + $httpClientMock->expects($this->once()) + ->method("post")->with($url, [], $requestBody) + ->willThrowException($exception); $strategy->expects($this->never())->method("flushHits"); @@ -690,6 +848,8 @@ public function testSendActivateHitFailed() public function testSendBatch() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; //Mock class Curl @@ -742,7 +902,7 @@ public function testSendBatch() 'createdAt' => 1676542078047, ]; - $page3 = HitAbstract::hydrate(Page::getClassName(), $contentPage3); + $page3 = HitAbstract::hydrate(Page::class, $contentPage3); $page3->setConfig($config); @@ -791,6 +951,8 @@ public function testSendBatch() public function testSendBatchFailed() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -868,6 +1030,7 @@ public function testSendBatchFailed() public function testSendBatchWithExpiredHit() { + $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -875,11 +1038,21 @@ public function testSendBatchWithExpiredHit() $url = FlagshipConstant::HIT_EVENT_URL; - \Flagship\Traits\Round::$returnValue = FlagshipConstant::DEFAULT_HIT_CACHE_TIME_MS; + $invocationCount = $this->exactly(14); + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($invocationCount) + ->willReturnCallback(function () use ($invocationCount) { + $invocationCount = $invocationCount->getInvocationCount(); + if ($invocationCount > 1 && $invocationCount <= 6) { + return 0; + } + + return FlagshipConstant::DEFAULT_HIT_CACHE_TIME_MS + 1; + }); + $page = new Page("https://myurl.com"); $page->setConfig($config)->setVisitorId($visitorId); - \Flagship\Traits\Round::$returnValue = 0; $screen = new Screen("home"); $screen->setConfig($config)->setVisitorId($visitorId); @@ -904,7 +1077,6 @@ public function testSendBatchWithExpiredHit() ] ); - \Flagship\Traits\Round::$returnValue = FlagshipConstant::DEFAULT_HIT_CACHE_TIME_MS; $strategy->addHit($page); $strategy->addHit($screen); @@ -919,6 +1091,8 @@ public function testSendBatchWithExpiredHit() $batchHit = new HitBatch($config, $hits); $batchHit->setConfig($config); + + $requestBody = $batchHit->toApiKeys(); $httpClientMock->expects($this->once())->method("post")->with($url, [], $requestBody); @@ -1120,14 +1294,14 @@ public function testCacheHit() { $config = new DecisionApiConfig(); - $httpClientMock = $this->getMockForAbstractClass('Flagship\Utils\HttpClientInterface'); + $httpClientMock = $this->getMockForAbstractClass(HttpClientInterface::class); - $hitCacheImplementationMock = $this->getMockForAbstractClass("Flagship\Cache\IHitCacheImplementation"); + $hitCacheImplementationMock = $this->getMockForAbstractClass(IHitCacheImplementation::class); $config->setHitCacheImplementation($hitCacheImplementationMock); $strategy = $this->getMockForAbstractClass( - "Flagship\Api\BatchingOnFailedCachingStrategy", + BatchingOnFailedCachingStrategy::class, [ $config, $httpClientMock, @@ -1144,7 +1318,17 @@ public function testCacheHit() $visitorId = "visitorId"; $key = "$visitorId:key"; - $activate = new Activate("varGrid", "varId"); + $activate = new Activate("varGrid", "varId", "key", new FSFlagMetadata( + "campaignId", + "varGrid", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setVisitorId($visitorId)->setConfig($config); $activate->setKey($key); $activate->setFlagKey("flagKey"); @@ -1155,7 +1339,7 @@ public function testCacheHit() HitCacheFields::DATA => [ HitCacheFields::VISITOR_ID => $activate->getVisitorId(), HitCacheFields::ANONYMOUS_ID => $activate->getAnonymousId(), - HitCacheFields::TYPE => $activate->getType(), + HitCacheFields::TYPE => $activate->getType()->value, HitCacheFields::CONTENT => $activate->toArray(), HitCacheFields::TIME => 0, ], @@ -1183,14 +1367,22 @@ public function testCacheHitFailed() { $config = new DecisionApiConfig(); - $httpClientMock = $this->getMockForAbstractClass('Flagship\Utils\HttpClientInterface'); + $httpClientMock = $this->getMockForAbstractClass(HttpClientInterface::class); - $hitCacheImplementationMock = $this->getMockForAbstractClass("Flagship\Cache\IHitCacheImplementation"); + $hitCacheImplementationMock = $this->getMockForAbstractClass( + IHitCacheImplementation::class, + [], + "", + false, + false, + true, + ["cacheHit"] + ); $config->setHitCacheImplementation($hitCacheImplementationMock); $strategy = $this->getMockForAbstractClass( - "Flagship\Api\BatchingOnFailedCachingStrategy", + BatchingOnFailedCachingStrategy::class, [ $config, $httpClientMock, @@ -1204,7 +1396,18 @@ public function testCacheHitFailed() $visitorId = "visitorId"; $key = "$visitorId:key"; - $activate = new Activate("varGrid", "varId"); + $activate = new Activate("varGrid", "varId", "key", new FSFlagMetadata( + "campaignId", + "varGrid", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); + $activate->setVisitorId($visitorId)->setConfig($config); $activate->setKey($key); @@ -1213,7 +1416,7 @@ public function testCacheHitFailed() HitCacheFields::DATA => [ HitCacheFields::VISITOR_ID => $activate->getVisitorId(), HitCacheFields::ANONYMOUS_ID => $activate->getAnonymousId(), - HitCacheFields::TYPE => $activate->getType(), + HitCacheFields::TYPE => $activate->getType()->value, HitCacheFields::CONTENT => $activate->toArray(), HitCacheFields::TIME => 0, ], @@ -1234,7 +1437,10 @@ public function testCacheHitFailed() ] ); - $hitCacheImplementationMock->expects($this->exactly(1))->method("cacheHit")->with($data)->willThrowException($exception); + $hitCacheImplementationMock->expects($this->exactly(1)) + ->method("cacheHit") + ->willThrowException($exception) + ; $strategy->cacheHit([$activate]); } @@ -1269,7 +1475,7 @@ public function testAddTroubleshootingHit() $strategy->setTroubleshootingData($troubleshootingData); $troubleshooting = new Troubleshooting(); - $troubleshooting->setConfig($config)->setVisitorId($visitorId)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(100); + $troubleshooting->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(100)->setConfig($config)->setVisitorId($visitorId); $strategy->addTroubleshootingHit($troubleshooting); @@ -1277,7 +1483,7 @@ public function testAddTroubleshootingHit() $this->assertCount(1, $troubleshootingQueue); $troubleshooting2 = new Troubleshooting(); - $troubleshooting2->setConfig($config)->setVisitorId($visitorId)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(50); + $troubleshooting2->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(50)->setConfig($config)->setVisitorId($visitorId); $strategy->addTroubleshootingHit($troubleshooting2); @@ -1287,7 +1493,7 @@ public function testAddTroubleshootingHit() $troubleshootingData->setTraffic(49); $troubleshooting3 = new Troubleshooting(); - $troubleshooting3->setConfig($config)->setVisitorId($visitorId)->setTraffic(50); + $troubleshooting3->setTraffic(50)->setConfig($config)->setVisitorId($visitorId); $strategy->addTroubleshootingHit($troubleshooting3); @@ -1295,7 +1501,7 @@ public function testAddTroubleshootingHit() $this->assertCount(2, $troubleshootingQueue); $troubleshooting4 = new Troubleshooting(); - $troubleshooting4->setConfig($config)->setVisitorId($visitorId)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(50); + $troubleshooting4->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setTraffic(50)->setConfig($config)->setVisitorId($visitorId); $strategy->addTroubleshootingHit($troubleshooting4); @@ -1350,10 +1556,24 @@ public function testSendTroubleshootingQueue() $strategy->setTroubleshootingData($troubleshootingData); $troubleshooting = new Troubleshooting(); - $troubleshooting->setConfig($config)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setVisitorId($visitorId)->setTraffic(100); + $troubleshooting->setLogLevel(LogLevel::ALL) + ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) + ->setVisitorSessionId("visitorSessionId") + ->setTraffic(100) + ->setFlagshipInstanceId("flagshipInstanceId") + ->setVisitorId($visitorId) + + ->setConfig($config); $troubleshooting2 = new Troubleshooting(); - $troubleshooting2->setConfig($config)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setVisitorId($visitorId)->setTraffic(100); + $troubleshooting2->setLogLevel(LogLevel::ALL) + ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) + ->setTraffic(100) + ->setVisitorSessionId("visitorSessionId") + ->setFlagshipInstanceId("flagshipInstanceId") + ->setVisitorId($visitorId) + + ->setConfig($config); $strategy->addTroubleshootingHit($troubleshooting); $strategy->addTroubleshootingHit($troubleshooting2); @@ -1411,7 +1631,15 @@ public function testSendTroubleshootingQueueFailed() $strategy->setTroubleshootingData($troubleshootingData); $troubleshooting = new Troubleshooting(); - $troubleshooting->setConfig($config)->setLogLevel(LogLevel::ALL)->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT)->setVisitorSessionId("visitorSessionId")->setFlagshipInstanceId("flagshipInstanceId")->setVisitorId($visitorId)->setTraffic(100); + $troubleshooting + ->setTraffic(100) + ->setLogLevel(LogLevel::ALL) + ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) + ->setVisitorSessionId("visitorSessionId") + ->setFlagshipInstanceId("flagshipInstanceId") + ->setVisitorId($visitorId) + ->setConfig($config) + ; $strategy->addTroubleshootingHit($troubleshooting); @@ -1503,13 +1731,15 @@ public function testAddUsageHit() $strategy = new BatchingOnFailedCachingStrategy($config, $httpClientMock); $usageHit = new UsageHit(); - $usageHit->setConfig($config) + $usageHit + ->setTraffic(100) ->setLogLevel(LogLevel::ALL) ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) ->setVisitorSessionId("visitorSessionId") ->setFlagshipInstanceId("flagshipInstanceId") ->setVisitorId($visitorId) - ->setTraffic(100); + ->setConfig($config) + ; $strategy->addUsageHit($usageHit); @@ -1518,13 +1748,15 @@ public function testAddUsageHit() $this->assertCount(1, $usageHitQueue); $usageHit2 = new UsageHit(); - $usageHit2->setConfig($config) - ->setVisitorId($visitorId) + $usageHit2 + ->setTraffic(50) ->setLogLevel(LogLevel::ALL) ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) ->setVisitorSessionId("visitorSessionId") ->setFlagshipInstanceId("flagshipInstanceId") - ->setTraffic(50); + + ->setConfig($config) + ->setVisitorId($visitorId); $strategy->addUsageHit($usageHit2); @@ -1557,26 +1789,29 @@ public function testSendUsageHitQueue() $usageHit = new UsageHit(); - $usageHit->setConfig($config) - ->setVisitorId($visitorId) + $usageHit + ->setTraffic(100) ->setLogLevel(LogLevel::ALL) ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) ->setVisitorSessionId("visitorSessionId") ->setFlagshipInstanceId("flagshipInstanceId") - ->setTraffic(100); + ->setConfig($config) + ->setVisitorId($visitorId) + ; $strategy->addUsageHit($usageHit); $usageHit2 = new UsageHit(); - $usageHit2->setConfig($config) - ->setVisitorId($visitorId) + $usageHit2 ->setLogLevel(LogLevel::ALL) ->setLabel(TroubleshootingLabel::VISITOR_SEND_HIT) ->setVisitorSessionId("visitorSessionId") ->setFlagshipInstanceId("flagshipInstanceId") - ->setTraffic(50); + ->setTraffic(50) + ->setConfig($config) + ->setVisitorId($visitorId); $strategy->addUsageHit($usageHit2); diff --git a/tests/Api/NoBatchingContinuousCachingStrategyTest.php b/tests/Api/NoBatchingContinuousCachingStrategyTest.php index f48c44ad..d0a725c1 100644 --- a/tests/Api/NoBatchingContinuousCachingStrategyTest.php +++ b/tests/Api/NoBatchingContinuousCachingStrategyTest.php @@ -7,11 +7,13 @@ use Flagship\Hit\Event; use Flagship\Hit\Activate; use Flagship\Hit\UsageHit; +use phpmock\phpunit\PHPMock; use Flagship\Hit\HitAbstract; use Flagship\Traits\LogTrait; use Flagship\Hit\ActivateBatch; use PHPUnit\Framework\TestCase; use Flagship\Enum\EventCategory; +use Flagship\Flag\FSFlagMetadata; use Flagship\Hit\Troubleshooting; use Flagship\Enum\FlagshipConstant; use Flagship\Config\DecisionApiConfig; @@ -20,28 +22,31 @@ class NoBatchingContinuousCachingStrategyTest extends TestCase { use LogTrait; + use PHPMock; public function testAddHit() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; $httpClientMock = $this->getMockForAbstractClass('Flagship\Utils\HttpClientInterface'); $strategy = $this->getMockForAbstractClass( - "Flagship\Api\NoBatchingContinuousCachingStrategy", + NoBatchingContinuousCachingStrategy::class, [ - $config, - $httpClientMock, + $config, + $httpClientMock, ], "", true, true, true, [ - "cacheHit", - "flushHits", - "logDebugSprintf", + "cacheHit", + "flushHits", + "logDebugSprintf", ] ); @@ -66,23 +71,24 @@ public function testAddHit() $url = FlagshipConstant::HIT_EVENT_URL; - $httpClientMock->expects($this->exactly(3))->method("post")->with( - $this->logicalOr( - $url, - $url, - $url - ), - $this->logicalOr( - [], - [], - [] - ), - $this->logicalOr( - $requestBody, - $requestBody2, - $requestBody3 - ) - ); + $httpClientMock->expects($this->exactly(3)) + ->method("post")->with( + $this->logicalOr( + $url, + $url, + $url + ), + $this->logicalOr( + [], + [], + [] + ), + $this->logicalOr( + $requestBody, + $requestBody2, + $requestBody3 + ) + ); $headers = [FlagshipConstant::HEADER_CONTENT_TYPE => FlagshipConstant::HEADER_APPLICATION_JSON]; @@ -130,16 +136,16 @@ public function testAddHit() ), $this->logicalOr( [ - FlagshipConstant::SEND_HIT, - $logMessage, + FlagshipConstant::SEND_HIT, + $logMessage, ], [ - FlagshipConstant::SEND_HIT, - $logMessage1, + FlagshipConstant::SEND_HIT, + $logMessage1, ], [ - FlagshipConstant::SEND_HIT, - $logMessage2, + FlagshipConstant::SEND_HIT, + $logMessage2, ] ) ); @@ -159,6 +165,9 @@ public function testAddHit() public function testAddHitFailed() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); + $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -167,20 +176,20 @@ public function testAddHitFailed() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, - "instanceId", + $config, + $httpClientMock, + "instanceId", ], "", true, true, true, [ - "cacheHit", - "flushHits", - "logErrorSprintf", - "addTroubleshootingHit", - "sendTroubleshootingQueue", + "cacheHit", + "flushHits", + "logErrorSprintf", + "addTroubleshootingHit", + "sendTroubleshootingQueue", ] ); @@ -221,8 +230,8 @@ public function testAddHitFailed() FlagshipConstant::TRACKING_MANAGER, FlagshipConstant::UNEXPECTED_ERROR_OCCURRED, [ - FlagshipConstant::SEND_HIT, - $logMessage, + FlagshipConstant::SEND_HIT, + $logMessage, ] ); @@ -239,6 +248,8 @@ public function testAddHitFailed() public function testAddHitConsent() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -249,16 +260,16 @@ public function testAddHitConsent() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, + $config, + $httpClientMock, ], "", true, true, true, [ - "cacheHit", - "flushHits", + "cacheHit", + "flushHits", ] ); @@ -279,20 +290,20 @@ public function testAddHitConsent() $strategy->hydrateHitsPoolQueue($key2, $page2); $contentPage3 = [ - 'pageUrl' => 'page1', - 'visitorId' => $visitorId, - 'ds' => 'APP', - 'type' => 'PAGEVIEW', - 'anonymousId' => null, - 'userIP' => null, - 'pageResolution' => null, - 'locale' => null, - 'sessionNumber' => null, - 'key' => $page3Key, - 'createdAt' => 1676542078047, - ]; - - $page3 = HitAbstract::hydrate(Page::getClassName(), $contentPage3); + 'pageUrl' => 'page1', + 'visitorId' => $visitorId, + 'ds' => 'APP', + 'type' => 'PAGEVIEW', + 'anonymousId' => null, + 'userIP' => null, + 'pageResolution' => null, + 'locale' => null, + 'sessionNumber' => null, + 'key' => $page3Key, + 'createdAt' => 1676542078047, + ]; + + $page3 = HitAbstract::hydrate(Page::class, $contentPage3); $page3->setConfig($config); @@ -330,6 +341,8 @@ public function testAddHitConsent() public function testActivateFlag() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -338,17 +351,17 @@ public function testActivateFlag() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, + $config, + $httpClientMock, ], "", true, true, true, [ - "cacheHit", - "flushHits", - "logDebugSprintf", + "cacheHit", + "flushHits", + "logDebugSprintf", ] ); @@ -356,10 +369,30 @@ public function testActivateFlag() $strategy->expects($this->never())->method("flushHits"); - $activate = new Activate("varGr1", "varId1"); + $activate = new Activate("varGr1", "varId1", "key", new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setConfig($config)->setVisitorId($visitorId); - $activate2 = new Activate("varGrId2", "varId2"); + $activate2 = new Activate("varGrId2", "varId2", "key2", new FSFlagMetadata( + "campaignId2", + "varGrId2", + "varId2", + false, + "ab", + null, + "campaignName2", + "varGroupName2", + "varName2" + )); $activate2->setConfig($config)->setVisitorId($visitorId); $activateBatch = new ActivateBatch($config, [$activate]); @@ -420,12 +453,12 @@ public function testActivateFlag() ), $this->logicalOr( [ - FlagshipConstant::SEND_ACTIVATE, - $logMessage, + FlagshipConstant::SEND_ACTIVATE, + $logMessage, ], [ - FlagshipConstant::SEND_ACTIVATE, - $logMessage1, + FlagshipConstant::SEND_ACTIVATE, + $logMessage1, ] ) ); @@ -442,6 +475,8 @@ public function testActivateFlag() public function testActivateFlagFailed() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(1.0); $config = new DecisionApiConfig(); $visitorId = "visitorId"; @@ -450,26 +485,37 @@ public function testActivateFlagFailed() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, - "instanceId", + $config, + $httpClientMock, + "instanceId", ], "", true, true, true, [ - "cacheHit", - "flushHits", - "logErrorSprintf", - "addTroubleshootingHit", - "sendTroubleshootingQueue", + "cacheHit", + "flushHits", + "logErrorSprintf", + "addTroubleshootingHit", + "sendTroubleshootingQueue", ] ); $strategy->expects($this->never())->method("flushHits"); - $activate = new Activate("varGr1", "varId1"); + $activate = new Activate("varGr1", "varId1", "key1", new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); + $activate->setConfig($config)->setVisitorId($visitorId); $strategy->expects($this->once())->method("cacheHit")->with([$activate]); @@ -505,8 +551,8 @@ public function testActivateFlagFailed() FlagshipConstant::TRACKING_MANAGER, FlagshipConstant::UNEXPECTED_ERROR_OCCURRED, [ - FlagshipConstant::SEND_ACTIVATE, - $logMessage, + FlagshipConstant::SEND_ACTIVATE, + $logMessage, ] ); $strategy->expects($this->once())->method("addTroubleshootingHit"); @@ -534,8 +580,8 @@ public function testAddTroubleshootingHit() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, + $config, + $httpClientMock, ], "", true, @@ -564,8 +610,8 @@ public function testAddUsageHit() $strategy = $this->getMockForAbstractClass( "Flagship\Api\NoBatchingContinuousCachingStrategy", [ - $config, - $httpClientMock, + $config, + $httpClientMock, ], "", true, diff --git a/tests/Api/Round.php b/tests/Api/Round.php deleted file mode 100644 index 28b5de45..00000000 --- a/tests/Api/Round.php +++ /dev/null @@ -1,17 +0,0 @@ -getMockForAbstractClass( "Flagship\Api\BatchingCachingStrategyAbstract", [ - $config, - $httpClient, + $config, + $httpClient, ], "", true, true, true, [ - "addHit", - "activateFlag", - "sendBatch", - "sendTroubleshootingQueue", - "addTroubleshootingHit", - "setTroubleshootingData", - "getTroubleshootingData", - "addUsageHit", + "addHit", + "activateFlag", + "sendBatch", + "sendTroubleshootingQueue", + "addTroubleshootingHit", + "setTroubleshootingData", + "getTroubleshootingData", + "addUsageHit", ] ); $trackingManager = $this->getMockForAbstractClass( "Flagship\Api\TrackingManager", [ - $config, - $httpClient, + $config, + $httpClient, ], "", true, true, true, [ - "getStrategy", - "lookupHits", + "getStrategy", + "lookupHits", ] ); @@ -123,12 +130,22 @@ public function testCommonMethod() $page->setConfig($config); $trackingManager->addHit($page); - $activate = new Activate("varGrId", "varId"); + $activate = new Activate("varGrId", "varId", "key1", new FSFlagMetadata( + "campaignId", + "varGrId", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setConfig($config); $trackingManager->activateFlag($activate); $troubleshooting = new Troubleshooting(); - $troubleshooting->setConfig($config)->setTraffic(100); + $troubleshooting->setTraffic(100)->setConfig($config); $trackingManager->addTroubleshootingHit($troubleshooting); $troubleshootingData = new TroubleshootingData(); @@ -148,21 +165,6 @@ public function testLookupHits() $httpClient = new HttpClient(); $hitCacheImplementationMock = $this->getMockForAbstractClass("Flagship\Cache\IHitCacheImplementation"); - $trackingManager = $this->getMockForAbstractClass( - "Flagship\Api\TrackingManager", - [ - $config, - $httpClient, - ], - "", - true, - true, - true, - [ - "logDebugSprintf", - "logErrorSprintf", - ] - ); $config->setHitCacheImplementation($hitCacheImplementationMock); @@ -183,81 +185,119 @@ public function testLookupHits() $segment = new Segment(["key" => "value"], $config); $segment->setConfig($config)->setVisitorId($visitorId)->setKey("$visitorId:key5"); - $activate = new Activate("varGrid", "varId"); + $activate = new Activate("varGrid", "varId", "key1", new FSFlagMetadata( + "campaignId", + "varGrid", + "varId", + false, + "ab", + null, + "campaignName", + "varGroupName", + "varName" + )); $activate->setVisitorId($visitorId)->setConfig($config)->setKey("$visitorId:key6"); $transaction = new Transaction("transId", "aff"); $transaction->setVisitorId($visitorId)->setConfig($config)->setKey("$visitorId:key7"); /** - * @var $hits HitAbstract[] + * @var HitAbstract[] $hits */ $hits = [ - $event, - $item, - $page, - $screen, - $segment, - $activate, - $transaction, - ]; + $event, + $item, + $page, + $screen, + $segment, + $activate, + $transaction, + ]; + /** @var array $data */ $data = []; foreach ($hits as $hit) { $hitData = [ - HitCacheFields::VERSION => 1, - HitCacheFields::DATA => [ - HitCacheFields::VISITOR_ID => $hit->getVisitorId(), - HitCacheFields::ANONYMOUS_ID => $hit->getAnonymousId(), - HitCacheFields::TYPE => $hit->getType()->value, - HitCacheFields::CONTENT => $hit->toArray(), - HitCacheFields::TIME => \round(microtime(true) * 1000), - ], - ]; + HitCacheFields::VERSION => 1, + HitCacheFields::DATA => [ + HitCacheFields::VISITOR_ID => $hit->getVisitorId(), + HitCacheFields::ANONYMOUS_ID => $hit->getAnonymousId(), + HitCacheFields::TYPE => $hit->getType()->value, + HitCacheFields::CONTENT => $hit->toArray(), + HitCacheFields::TIME => \round(microtime(true) * 1000), + ], + ]; $data[$hit->getKey()] = $hitData; } + // invalid hit expired time $data["$visitorId:key8"] = [ - HitCacheFields::VERSION => 1, - HitCacheFields::DATA => [ - HitCacheFields::VISITOR_ID => $page->getVisitorId(), - HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), - HitCacheFields::TYPE => $page->getType()->value, - HitCacheFields::CONTENT => $page->toArray(), - HitCacheFields::TIME => (new DateTime("2020/01/01"))->format("Uv"), - ], - ]; + HitCacheFields::VERSION => 1, + HitCacheFields::DATA => [ + HitCacheFields::VISITOR_ID => $page->getVisitorId(), + HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), + HitCacheFields::TYPE => $page->getType()->value, + HitCacheFields::CONTENT => $page->toArray(), + HitCacheFields::TIME => (new DateTime("2020/01/01"))->format("Uv"), + ], + ]; + // Invalid hit with unknown type $data["$visitorId:key9"] = [ - HitCacheFields::VERSION => 1, - HitCacheFields::DATA => [ - HitCacheFields::VISITOR_ID => $page->getVisitorId(), - HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), - HitCacheFields::TYPE => "unknown", - HitCacheFields::CONTENT => $page->toArray(), - HitCacheFields::TIME => \round(microtime(true) * 1000), - ], - ]; + HitCacheFields::VERSION => 1, + HitCacheFields::DATA => [ + HitCacheFields::VISITOR_ID => $page->getVisitorId(), + HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), + HitCacheFields::TYPE => "unknown", + HitCacheFields::CONTENT => $page->toArray(), + HitCacheFields::TIME => \round(microtime(true) * 1000), + ], + ]; + // Invalid hit with missing version $key10 = "$visitorId:key10"; $data[$key10] = [ - HitCacheFields::DATA => [ - HitCacheFields::VISITOR_ID => $page->getVisitorId(), - HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), - HitCacheFields::TYPE => "unknown", - HitCacheFields::CONTENT => $page->toArray(), - HitCacheFields::TIME => \round(microtime(true) * 1000), - ], - ]; + HitCacheFields::DATA => [ + HitCacheFields::VISITOR_ID => $page->getVisitorId(), + HitCacheFields::ANONYMOUS_ID => $page->getAnonymousId(), + HitCacheFields::TYPE => $page->getType()->value, + HitCacheFields::CONTENT => $page->toArray(), + HitCacheFields::TIME => \round(microtime(true) * 1000), + ], + ]; - $hitCacheImplementationMock->expects($this->exactly(2))->method("lookupHits")->willReturnOnConsecutiveCalls($data, []); + // Invalid hit with missing data + $key11 = "$visitorId:key11"; + $data[$key11] = [ + HitCacheFields::DATA => null, + ]; + $hitCacheImplementationMock->expects($this->exactly(2)) + ->method("lookupHits") + ->willReturnOnConsecutiveCalls($data, []); - Round::$returnValue = \round(microtime(true) * 1000); - $trackingManager->lookupHits(); + $round = $this->getFunctionMock(__NAMESPACE__, 'round'); + $round->expects($this->any())->willReturn(microtime(true) * 1000); + + $trackingManager = $this->getMockForAbstractClass( + "Flagship\Api\TrackingManager", + [ + $config, + $httpClient, + ], + "", + true, + true, + true, + [ + "logDebugSprintf", + "logErrorSprintf", + ] + ); - $strategy = Utils::getProperty($trackingManager, 'strategy')->getValue($trackingManager); + $strategy = Utils::getProperty($trackingManager, 'strategy') + ->getValue($trackingManager); $this->assertCount(6, $strategy->getHitsPoolQueue()); $this->assertCount(1, $strategy->getActivatePoolQueue()); @@ -274,8 +314,8 @@ public function testLookupHitsFailed() $trackingManager = $this->getMockForAbstractClass( "Flagship\Api\TrackingManager", [ - $config, - $httpClient, + $config, + $httpClient, ], "", true, @@ -295,8 +335,8 @@ public function testLookupHitsFailed() FlagshipConstant::PROCESS_CACHE, FlagshipConstant::HIT_CACHE_ERROR, [ - "lookupHits", - $exception->getMessage(), + "lookupHits", + $exception->getMessage(), ] ); diff --git a/tests/Assets/Curl.php b/tests/Assets/Curl.php deleted file mode 100644 index 051f5851..00000000 --- a/tests/Assets/Curl.php +++ /dev/null @@ -1,72 +0,0 @@ -getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); + return $round; + } + + protected function mockErrorLog(?InvocationOrder $invocationRule = null): void + { + $invocationRule = $invocationRule ?? $this->once(); + $errorLog = $this->getFunctionMock('Flagship\Traits', 'error_log'); + $errorLog->expects($invocationRule) + ->willReturnCallback(function ($message) { + $this->capturedLogs[] = $message; + return true; + }); + } + + /** + * + * @param array $methods + * @return FlagshipLogManager|MockObject + */ + protected function mockLoggerManager(array $methods = []): MockObject + { + return $this->getMockBuilder( + FlagshipLogManager::class + ) + ->disableOriginalConstructor() + ->onlyMethods( + $methods + )->getMock(); + } +} diff --git a/tests/Decision/ApiManagerTest.php b/tests/Decision/ApiManagerTest.php index 622c2eba..44276ea6 100644 --- a/tests/Decision/ApiManagerTest.php +++ b/tests/Decision/ApiManagerTest.php @@ -36,14 +36,14 @@ public function testConstruct() public function testGetCampaignModifications() { $httpClientMock = $this->getMockForAbstractClass( - 'Flagship\Utils\HttpClientInterface', + HttpClientInterface::class, ['post'], "", false ); $trackingManager = $this->getMockForAbstractClass( - 'Flagship\Api\TrackingManagerAbstract', + TrackingManagerAbstract::class, ['sendConsentHit'], "", false @@ -57,72 +57,87 @@ public function testGetCampaignModifications() ); $modificationValue1 = [ - "background" => "bleu ciel", - "btnColor" => "#EE3300", - "borderColor" => null, //test modification null - 'isVip' => false, //test modification false - 'firstConnect' => true, - ]; + "background" => "bleu ciel", + "btnColor" => "#EE3300", + "borderColor" => null, //test modification null + 'isVip' => false, //test modification false + 'firstConnect' => true, + ]; $modificationValue2 = [ - "key" => "variation 2", - "key2" => 1, - "key3" => 3, - "key4" => 4, - "key5" => '',//test modification empty - ]; + "key" => "variation 2", + "key2" => 1, + "key3" => 3, + "key4" => 4, + "key5" => '', //test modification empty + ]; $modificationValue3 = [ - 'key' => 'variation 3', - 'key2' => 3, - ]; + 'key' => 'variation 3', + 'key2' => 3, + ]; $mergeModification = array_merge($modificationValue1, $modificationValue2); $campaigns = [ - [ - "id" => "c1e3t1nvfu1ncqfcdco0", - "variationGroupId" => "c1e3t1nvfu1ncqfcdcp0", - "variation" => [ - "id" => "c1e3t1nvfu1ncqfcdcq0", - "modifications" => [ - "type" => "FLAG", - "value" => $modificationValue1, - ], - "reference" => false, - ], - ], - [ - "id" => "c20j8bk3fk9hdphqtd1g", - "variationGroupId" => "c20j8bk3fk9hdphqtd2g", - "variation" => [ - "id" => "c20j9lgbcahhf2mvhbf0", - "modifications" => [ - "type" => "JSON", - "value" => $modificationValue2, - ], - "reference" => true, - ], - ], - [ - "id" => "c20j8bksdfk9hdphqtd1g", - "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", - "variation" => [ - "id" => "c20j9lrfcahhf2mvhbf0", - "modifications" => [ - "type" => "JSON", - "value" => $modificationValue3, - ], - "reference" => true, - ], - ], - ]; + [ + "id" => "c1e3t1nvfu1ncqfcdco0", + "name" => "Campaign 1", + "type" => "A/B Test", + "slug" => "campaign-1", + "variationGroupId" => "c1e3t1nvfu1ncqfcdcp0", + "variationGroupName" => "Variation Group 1", + "variation" => [ + "id" => "c1e3t1nvfu1ncqfcdcq0", + "name" => "Variation 1", + "modifications" => [ + "type" => "FLAG", + "value" => $modificationValue1, + ], + "reference" => false, + ], + ], + [ + "id" => "c20j8bk3fk9hdphqtd1g", + "type" => "A/B Test", + "variationGroupId" => "c20j8bk3fk9hdphqtd2g", + "variation" => [ + "id" => "c20j9lgbcahhf2mvhbf0", + "modifications" => [ + "type" => "JSON", + "value" => $modificationValue2, + ], + "reference" => true, + ], + ], + [ + "id" => "c20j8bksdfk9hdphqtd1g", + "type" => "A/B Test", + "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", + "variationGroupName" => "Variation Group 2", + "variation" => [ + "name" => "Variation 3", + "id" => "c20j9lrfcahhf2mvhbf0", + "modifications" => [ + "type" => "JSON", + "value" => $modificationValue3, + ], + "reference" => true, + ], + ] + ]; $visitorId = "visitorId"; $body = [ - "visitorId" => $visitorId, - "campaigns" => $campaigns, - ]; - - $httpPost = $httpClientMock->expects($this->exactly(2))->method('post')->willReturn(new HttpResponse(204, $body)); + "visitorId" => $visitorId, + "campaigns" => $campaigns, + ]; + + $httpPost = $httpClientMock->expects($this->exactly(3)) + ->method('post') + ->willReturnOnConsecutiveCalls( + new HttpResponse(204, $body), + new HttpResponse(204, $body), + new HttpResponse(204, []) + );; $config = new DecisionApiConfig("env_id", "apiKey"); $manager = new ApiManager($httpClientMock, $config); @@ -138,12 +153,12 @@ public function testGetCampaignModifications() $visitor = new VisitorDelegate(new Container(), $configManager, $visitorId, false, [], true); $postData = [ - "visitorId" => $visitor->getVisitorId(), - "anonymousId" => $visitor->getAnonymousId(), - "trigger_hit" => false, - "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, - "visitor_consent" => $visitor->hasConsented(), - ]; + "visitorId" => $visitor->getVisitorId(), + "anonymousId" => $visitor->getAnonymousId(), + "trigger_hit" => false, + "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, + "visitor_consent" => $visitor->hasConsented(), + ]; $url = FlagshipConstant::BASE_API_URL . '/' . $config->getEnvId() . '/' . FlagshipConstant::URL_CAMPAIGNS . '?' . @@ -161,12 +176,12 @@ public function testGetCampaignModifications() $this->logicalOr( $this->equalTo($postData), $this->equalTo([ - "visitorId" => $visitor->getVisitorId(), - "anonymousId" => $visitor->getAnonymousId(), - "trigger_hit" => false, - "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, - "visitor_consent" => false, - ]) + "visitorId" => $visitor->getVisitorId(), + "anonymousId" => $visitor->getAnonymousId(), + "trigger_hit" => false, + "context" => count($visitor->getContext()) > 0 ? $visitor->getContext() : null, + "visitor_consent" => false, + ]) ) ); @@ -182,18 +197,37 @@ public function testGetCampaignModifications() //Test campaignId $this->assertSame($campaigns[0]['id'], $modifications[2]->getCampaignId()); + //Test campaign name + $this->assertSame($campaigns[0]['name'], $modifications[3]->getCampaignName()); + + //Test campaign type + $this->assertSame($campaigns[0]['type'], $modifications[4]->getCampaignType()); + + //Test campaign slug + $this->assertSame($campaigns[0]['slug'], $modifications[4]->getSlug()); + //Test Variation group $this->assertSame($campaigns[2]['variationGroupId'], $modifications[5]->getVariationGroupId()); + //Test Variation group name + $this->assertSame($campaigns[2]['variationGroupName'], $modifications[5]->getVariationGroupName()); + //Test Variation $this->assertSame($campaigns[2]['variation']['id'], $modifications[6]->getVariationId()); + //Test Variation name + $this->assertSame($campaigns[2]['variation']['name'], $modifications[6]->getVariationName()); + //Test reference $this->assertSame($campaigns[2]['variation']['reference'], $modifications[6]->getIsReference()); // Test with consent = false $visitor->setConsent(false); $manager->getCampaignFlags($visitor); + + // Test with empty campaigns + $modifications = $manager->getCampaignFlags($visitor); + $this->assertSame([], $modifications); } public function testGetCampaignModificationsWithPanicMode() @@ -202,10 +236,10 @@ public function testGetCampaignModificationsWithPanicMode() $visitorId = "visitorId"; $body = [ - "visitorId" => $visitorId, - "campaigns" => [], - "panic" => true, - ]; + "visitorId" => $visitorId, + "campaigns" => [], + "panic" => true, + ]; $httpClientMock->method('post')->willReturn(new HttpResponse(204, $body)); @@ -250,63 +284,80 @@ public function testGetCampaignModificationsWithSomeFailed() $httpClientMock = $this->getMockForAbstractClass('Flagship\Utils\HttpClientInterface', ['post'], "", false); $modificationValue = [ - "background" => "bleu ciel", - "btnColor" => "#EE3300", - "borderColor" => null, - 'isVip' => false, - 'firstConnect' => true, - '' => 'hello world',//Test with invalid key - ]; + "background" => "bleu ciel", + "btnColor" => "#EE3300", + "borderColor" => null, + 'isVip' => false, + 'firstConnect' => true, + '' => 'hello world', //Test with invalid key + ]; $campaigns = [ - [ - "id" => "c1e3t1nvfu1ncqfcdco0", - "variationGroupId" => "c1e3t1nvfu1ncqfcdcp0", - "variation" => [ - "id" => "c1e3t1nvfu1ncqfcdcq0", - "modifications" => [ //Test modification without Value + [ + "id" => "c1e3t1nvfu1ncqfcdco0", + "variationGroupId" => "c1e3t1nvfu1ncqfcdcp0", + "variation" => [ + "id" => "c1e3t1nvfu1ncqfcdcq0", + "modifications" => [ //Test modification without Value "type" => "FLAG", - ], - "reference" => false, - ], - ], - [ - "id" => "c20j8bk3fk9hdphqtd1g", - "variationGroupId" => "c20j8bk3fk9hdphqtd2g", - "variation" => [ //Test Variation without modification - "id" => "c20j9lgbcahhf2mvhbf0", - "reference" => true, - ], - ], - [ // Test Campaign without variation - "id" => "c20j8bksdfk9hdphqtd1g", - "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", - - ], - [ - "id" => "c20j8bksdfk9hdphqtd1g", - "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", - "variation" => [ - "id" => "c20j9lrfcahhf2mvhbf0", - "modifications" => [ - "type" => "JSON", - "value" => $modificationValue, - ], - "reference" => true, - ], - ], - ]; + ], + "reference" => false, + ], + ], + [ + "id" => "c20j8bk3fk9hdphqtd1g", + "variationGroupId" => "c20j8bk3fk9hdphqtd2g", + "variation" => [ //Test Variation without modification + "id" => "c20j9lgbcahhf2mvhbf0", + "reference" => true, + ], + ], + [ // Test Campaign without variation + "id" => "c20j8bksdfk9hdphqtd1g", + "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", + + ], + [ + "id" => "c20j8bksdfk9hdphqtd1g", + "variationGroupId" => "c2sf8bk3fk9hdphqtd2g", + "variation" => [ + "id" => "c20j9lrfcahhf2mvhbf0", + "modifications" => [ + "type" => "JSON", + "value" => $modificationValue, + ], + "reference" => true, + ], + ], + [ // Test Campaign without variationGroupId + "id" => "c20j8bksdfk9hdphqtd1g", + "variation" => [ + "id" => "c20j9lrfcahhf2mvhbf0", + "modifications" => [ + "type" => "JSON", + "value" => [ + "key" => "variation 3", + "key2" => 3, + ], + ], + "reference" => true, + ], + ], + ]; $visitorId = "visitorId"; $body = [ - "visitorId" => $visitorId, - "campaigns" => $campaigns, - ]; + "visitorId" => $visitorId, + "campaigns" => $campaigns, + ]; - $httpClientMock->method('post')->willReturn(new HttpResponse(204, $body)); + $httpClientMock->method('post') + ->willReturn(new HttpResponse(204, $body)); $config = new DecisionApiConfig("env_id", "apiKey"); + $manager = new ApiManager($httpClientMock, $config); + $trackingManager = $this->getMockForAbstractClass( TrackingManagerAbstract::class, ['sendConsentHit'], diff --git a/tests/Decision/BucketingManagerTest.php b/tests/Decision/BucketingManagerTest.php index 3b83f12c..8f931d8a 100644 --- a/tests/Decision/BucketingManagerTest.php +++ b/tests/Decision/BucketingManagerTest.php @@ -6,6 +6,8 @@ use Exception; use ReflectionException; use Flagship\Utils\Utils; +use Flagship\Enum\LogLevel; +use Flagship\Model\FlagDTO; use Psr\Log\LoggerInterface; use Flagship\Utils\Container; use Flagship\Utils\HttpClient; @@ -13,13 +15,20 @@ use PHPUnit\Framework\TestCase; use Flagship\Enum\FlagshipField; use Flagship\Model\HttpResponse; +use Flagship\Model\TargetingDTO; +use Flagship\Model\VariationDTO; +use Flagship\Model\TargetingsDTO; use Flagship\Utils\ConfigManager; use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; use Flagship\Config\BucketingConfig; +use Flagship\Enum\TargetingOperator; +use Flagship\Model\VariationGroupDTO; use Flagship\Visitor\DefaultStrategy; use Flagship\Visitor\VisitorDelegate; use Flagship\Visitor\StrategyAbstract; use Flagship\Enum\TroubleshootingLabel; +use Flagship\Api\TrackingManagerInterface; use PHPUnit\Framework\MockObject\MockObject; class BucketingManagerTest extends TestCase @@ -45,53 +54,81 @@ public function testGetCampaignModification() $visitor = $this->getMockBuilder(VisitorDelegate::class)->setConstructorArgs([$container, $configManager, $visitorId, false, $visitorContext, true])->onlyMethods(["sendHit"])->getMock(); $bucketingFile = \file_get_contents(__DIR__ . '/bucketing.json'); - $httpClientMock->expects($this->exactly(6))->method('get')->with($bucketingUrl)->willReturnOnConsecutiveCalls( - new HttpResponse(204, null), - new HttpResponse(204, json_decode('{"panic": true}', true)), - new HttpResponse(204, json_decode('{}', true)), - new HttpResponse(204, json_decode('{"campaigns":[{}]}', true)), - new HttpResponse(204, json_decode('{"notExistKey": false}', true)), - new HttpResponse(204, json_decode($bucketingFile, true)) - ); + $httpClientMock->expects($this->exactly(6))->method('get') + ->with($bucketingUrl) + ->willReturnOnConsecutiveCalls( + new HttpResponse(204, null), + new HttpResponse(204, json_decode('{"panic": true}', true)), + new HttpResponse(204, json_decode('{}', true)), + new HttpResponse(204, json_decode('{"campaigns":[{}]}', true)), + new HttpResponse(204, json_decode('{"notExistKey": false}', true)), + new HttpResponse(204, json_decode($bucketingFile, true)) + ); //Test File not exist - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); - $this->assertCount(0, $campaigns); + $this->assertCount(0, $flags); //Test Panic Mode - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); - $this->assertCount(0, $campaigns); + $this->assertCount(0, $flags); //Test campaign property - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); - $this->assertCount(0, $campaigns); + $this->assertCount(0, $flags); //Test campaign[FIELD_VARIATION_GROUPS] - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); - $this->assertCount(0, $campaigns); + $this->assertCount(0, $flags); // - $campaigns = $bucketingManager->getCampaignFlags($visitor); - - $this->assertCount(0, $campaigns); - - // - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); + + $this->assertCount(0, $flags); + + // Test valid bucketing file + $flags = $bucketingManager->getCampaignFlags($visitor); + $this->assertIsArray($flags); + + foreach ($flags as $flag) { + $this->assertNotEmpty($flag->getKey()); + $this->assertNotEmpty($flag->getCampaignId()); + $this->assertNotEmpty($flag->getVariationGroupId()); + $this->assertNotEmpty($flag->getVariationId()); + $this->assertNotEmpty($flag->getCampaignType()); + if ($flag->getCampaignId() === "c1ndsu87m030114t8uu0") { + $this->assertEquals('toggle', $flag->getCampaignType()); + $this->assertEquals("campaign1", $flag->getCampaignName()); + $this->assertEquals("variationGroups1", $flag->getVariationGroupName()); + $this->assertEquals("c1ndsu87m030114t8uv0", $flag->getVariationGroupId()); + $this->assertEquals("c1ndsu87m030114t8uvg", $flag->getVariationId()); + $this->assertEquals("variation1", $flag->getVariationName()); + + $flagValue = match ($flag->getKey()) { + 'background' => 'bleu ciel', + 'btnColor' => '#EE3300', + 'keyBoolean' => false, + "keyNumber" => 5660, + default => null + }; + $this->assertEquals($flagValue, $flag->getValue()); + } + } - $this->assertCount(6, $campaigns); + $this->assertCount(6, $flags); //test invalid bucketing file url $config->setSyncAgentUrl(""); - $campaigns = $bucketingManager->getCampaignFlags($visitor); + $flags = $bucketingManager->getCampaignFlags($visitor); - $this->assertCount(0, $campaigns); + $this->assertCount(0, $flags); } /** @@ -119,16 +156,16 @@ public function testGetTroubleshootingData() $bucketingFile = \file_get_contents(__DIR__ . '/bucketing.json'); $bucketingContent = json_decode($bucketingFile, true); $troubleshooting = [ - "startDate" => "2023-04-13T09:33:38.049Z", - "endDate" => "2023-04-13T10:03:38.049Z", - "timezone" => "Europe/Paris", - "traffic" => 40, - ]; + "startDate" => "2023-04-13T09:33:38.049Z", + "endDate" => "2023-04-13T10:03:38.049Z", + "timezone" => "Europe/Paris", + "traffic" => 40.0, + ]; $bucketingContent["accountSettings"] = ["troubleshooting" => $troubleshooting]; $matcher = $this->exactly(1); $trackingManagerMock->expects($matcher)->method('addTroubleshootingHit')->with($this->callback(function ($param) use ($matcher) { - return $param->getLabel() === TroubleshootingLabel::SDK_BUCKETING_FILE; + return $param->getLabel() === TroubleshootingLabel::SDK_BUCKETING_FILE; })); $httpClientMock->expects($this->exactly(1))->method('get')->with($bucketingUrl)->willReturnOnConsecutiveCalls( @@ -158,8 +195,8 @@ public function testSendContext() $httpClientMock = $this->getMockForAbstractClass( 'Flagship\Utils\HttpClientInterface', [ - 'post', - 'get', + 'post', + 'get', ], "", false @@ -189,13 +226,13 @@ public function testSendContext() $visitorId = "visitor_1"; $visitorContext = [ - "age" => 20, - "sdk_osName" => PHP_OS, - "sdk_deviceType" => "server", - FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, - FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::FS_USERS => $visitorId, - ]; + "age" => 20, + "sdk_osName" => PHP_OS, + "sdk_deviceType" => "server", + FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, + FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::FS_USERS => $visitorId, + ]; $bucketingUrl = "http:127.0.0.1:3000"; @@ -265,144 +302,169 @@ public function testGetVariation() $configManager->setConfig($config); $visitor = new VisitorDelegate($container, $configManager, $visitorId, false, [], true); + $getVariationMethod = Utils::getMethod(BucketingManager::class, "getVariation"); //Test key id in variationGroup - $variationGroups = []; + $variationGroups = new VariationGroupDTO( + "", + new TargetingDTO([]), + [] + ); + + /** + * @var VariationDTO|null + */ $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertCount(0, $variation); + $this->assertNull($variation); //Test key id in variationGroup $variations = [ - [ - "id" => "c20j8bk3fk9hdphqtd30", - "name" => "variation1", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

Original

\n
"], - ], - "allocation" => 34, - "reference" => true, - ], - [ - "id" => "c20j8bk3fk9hdphqtd3g", - "name" => "variation2", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

variation 1

\n
"], - ], - "allocation" => 33, - ], - [ - "id" => "c20j9lgbcahhf2mvhbf0", - "name" => "variation3", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

variation 2

\n
"], - ], - "allocation" => 33, - ], - ]; + [ + "id" => "c20j8bk3fk9hdphqtd30", + "name" => "variation1", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

Original

\n
"], + ], + "allocation" => 34, + "reference" => true, + ], + [ + "id" => "c20j8bk3fk9hdphqtd3g", + "name" => "variation2", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

variation 1

\n
"], + ], + "allocation" => 33, + ], + [ + "id" => "c20j9lgbcahhf2mvhbf0", + "name" => "variation3", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

variation 2

\n
"], + ], + "allocation" => 33, + ], + ]; $variationGroups = [ - FlagshipField::FIELD_ID => "9273BKSDJtoto", - FlagshipField::FIELD_VARIATIONS => $variations, - FlagshipField::FIELD_NANE => "varGroupName", - ]; + FlagshipField::FIELD_ID => "9273BKSDJtoto", + FlagshipField::FIELD_VARIATIONS => $variations, + FlagshipField::FIELD_NANE => "varGroupName", + ]; + $variationGroups = VariationGroupDTO::fromArray($variationGroups); + + /** + * @var VariationDTO|null + */ $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertSame($variations[0]['id'], $variation['id']); + + $this->assertSame($variations[0]['id'], $variation->getId()); $variationGroups = [ - FlagshipField::FIELD_ID => "vgidéééà", - FlagshipField::FIELD_VARIATIONS => $variations, - ]; + FlagshipField::FIELD_ID => "vgidéééà", + FlagshipField::FIELD_VARIATIONS => $variations, + ]; + $variationGroups = VariationGroupDTO::fromArray($variationGroups); $visitorId = 'ëééééé'; $visitor->setVisitorId($visitorId); + /** + * @var VariationDTO|null + */ $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertSame($variations[2]['id'], $variation['id']); + $this->assertSame($variations[2]['id'], $variation->getId()); //Test realloc $realloCvariations = [ - [ - "id" => "c20j8bk3fk9hdphqtd30", - "name" => "variation1", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

Original

\n
"], - ], - "allocation" => 100, - "reference" => true, - ], - [ - "id" => "c20j8bk3fk9hdphqtd3g", - "name" => "variation2", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

variation 1

\n
"], - ], - "allocation" => 0, - ], - [ - "id" => "c20j9lgbcahhf2mvhbf0", - "name" => "variation2", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

variation 2

\n
"], - ], - "allocation" => 0, - ], - ]; + [ + "id" => "c20j8bk3fk9hdphqtd30", + "name" => "variation1", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

Original

\n
"], + ], + "allocation" => 100, + "reference" => true, + ], + [ + "id" => "c20j8bk3fk9hdphqtd3g", + "name" => "variation2", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

variation 1

\n
"], + ], + "allocation" => 0, + ], + [ + "id" => "c20j9lgbcahhf2mvhbf0", + "name" => "variation2", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

variation 2

\n
"], + ], + "allocation" => 0, + ], + ]; $variationGroups = [ - FlagshipField::FIELD_ID => "9273BKSDJtoto", - FlagshipField::FIELD_VARIATIONS => $realloCvariations, - ]; + FlagshipField::FIELD_ID => "9273BKSDJtoto", + FlagshipField::FIELD_VARIATIONS => $realloCvariations, + ]; + + $variationGroups = VariationGroupDTO::fromArray($variationGroups); + $assignmentsHistory = ["9273BKSDJtoto" => "c20j9lgbcahhf2mvhbf0"]; $visitorCache = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory], + ]; + + $visitorCache = VisitorCacheDTO::fromArray($visitorCache); $visitor->visitorCache = $visitorCache; $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertSame($realloCvariations[2]['id'], $variation['id']); + $this->assertSame($realloCvariations[2]['id'], $variation->getId()); //Test deleted variation $reallovariations = [ - [ - "id" => "c20j8bk3fk9hdphqtd30", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

Original

\n
"], - ], - "allocation" => 50, - "reference" => true, - ], - [ - "id" => "c20j8bk3fk9hdphqtd3g", - "modifications" => [ - "type" => "HTML", - "value" => ["my_html" => "
\n

variation 1

\n
"], - ], - "allocation" => 50, - ], - ]; + [ + "id" => "c20j8bk3fk9hdphqtd30", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

Original

\n
"], + ], + "allocation" => 50, + "reference" => true, + ], + [ + "id" => "c20j8bk3fk9hdphqtd3g", + "modifications" => [ + "type" => "HTML", + "value" => ["my_html" => "
\n

variation 1

\n
"], + ], + "allocation" => 50, + ], + ]; $variationGroups = [ - FlagshipField::FIELD_ID => "9273BKSDJtoto", - FlagshipField::FIELD_VARIATIONS => $reallovariations, - ]; + FlagshipField::FIELD_ID => "9273BKSDJtoto", + FlagshipField::FIELD_VARIATIONS => $reallovariations, + ]; + $variationGroups = VariationGroupDTO::fromArray($variationGroups); $visitor->visitorCache = $visitorCache; $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertCount(0, $variation); + $this->assertNull($variation); // $realloCvariations = [ @@ -447,6 +509,8 @@ public function testGetVariation() FlagshipField::FIELD_ID => "9273BKSDJtoto", FlagshipField::FIELD_VARIATIONS => $realloCvariations ]; + $variationGroups = VariationGroupDTO::fromArray($variationGroups); + $assignmentsHistory = []; $visitorCache = [ StrategyAbstract::VERSION => 1, @@ -455,19 +519,21 @@ public function testGetVariation() ] ]; + $visitorCache = VisitorCacheDTO::fromArray($visitorCache); + $visitor->visitorCache = $visitorCache; $variation = $getVariationMethod->invoke($bucketingManager, $variationGroups, $visitor); - $this->assertNotSame($realloCvariations[0]['id'], $variation['id']); - $this->assertNotSame($realloCvariations[1]['id'], $variation['id']); - $this->assertSame($realloCvariations[2]['id'], $variation['id']); + $this->assertNotSame($realloCvariations[0]['id'], $variation->getId()); + $this->assertNotSame($realloCvariations[1]['id'], $variation->getId()); + $this->assertSame($realloCvariations[2]['id'], $variation->getId()); } /** * @throws ReflectionException */ - public function testIsMatchTargeting() + public function testCheckVisitorMatchesTargeting() { $bucketingUrl = "http:127.0.0.1:3000"; $murmurhash = new MurmurHash(); @@ -480,133 +546,139 @@ public function testIsMatchTargeting() $configManager->setConfig($config); $visitor = new VisitorDelegate($container, $configManager, $visitorId, false, $visitorContext, true); - $isMatchTargetingMethod = Utils::getMethod(BucketingManager::class, "isMatchTargeting"); + $checkVisitorMatchesTargeting = Utils::getMethod(BucketingManager::class, "checkVisitorMatchesTargeting"); - $variationGroup = []; + $variationGroup = new VariationGroupDTO( + "", + new TargetingDTO([]), + [] + ); //Test key targeting variationGroup - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertFalse($output); //Test key targetingGroups in targeting - $variationGroup = [ + $variationGroup = VariationGroupDTO::fromArray([ FlagshipField::FIELD_TARGETING => [] - ]; - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertFalse($output); //Test key targetings in targetingGroups - $variationGroup = [ - FlagshipField::FIELD_TARGETING => [ - FlagshipField::FIELD_TARGETING_GROUPS => [ - [], - ], - ], - ]; - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + $variationGroup = VariationGroupDTO::fromArray([ + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [], + ], + ], + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertFalse($output); //Test not matching targetings $targetings = [ - "key" => "age", - "operator" => "EQUALS", - 'value' => 21, - ]; - $variationGroup = [ - FlagshipField::FIELD_TARGETING => [ - FlagshipField::FIELD_TARGETING_GROUPS => [ - [ - FlagshipField::FIELD_TARGETINGS => [$targetings], - ], - ], - ], - ]; - - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + "key" => "age", + "operator" => "EQUALS", + 'value' => 21, + ]; + $variationGroup = VariationGroupDTO::fromArray([ + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [ + FlagshipField::FIELD_TARGETINGS => [$targetings], + ], + ], + ], + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertFalse($output); //Test matching targetings $targetings2 = [ - "key" => "age", - "operator" => "EQUALS", - 'value' => 20, - ]; - - $variationGroup = [ - FlagshipField::FIELD_TARGETING => [ - FlagshipField::FIELD_TARGETING_GROUPS => [ - [ - FlagshipField::FIELD_TARGETINGS => [$targetings2], - ], - ], - ], - ]; - - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + "key" => "age", + "operator" => "EQUALS", + 'value' => 20, + ]; + + $variationGroup = VariationGroupDTO::fromArray([ + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [ + FlagshipField::FIELD_TARGETINGS => [$targetings2], + ], + ], + ], + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertTrue($output); //Test Many targetingGroups with one match $targetings2 = [ - "key" => "age", - "operator" => "EQUALS", - 'value' => 22, - ]; + "key" => "age", + "operator" => "EQUALS", + 'value' => 22, + ]; $targetingAllUsers = [ - "key" => "fs_all_users", - "operator" => "EQUALS", - 'value' => '', - ]; - - $variationGroup = [ - FlagshipField::FIELD_TARGETING => [ - FlagshipField::FIELD_TARGETING_GROUPS => [ - [ - FlagshipField::FIELD_TARGETINGS => [$targetings], - ], - [ - FlagshipField::FIELD_TARGETINGS => [$targetings2], - ], - [ - FlagshipField::FIELD_TARGETINGS => [$targetingAllUsers], - ], - ], - ], - ]; - - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + "key" => "fs_all_users", + "operator" => "EQUALS", + 'value' => '', + ]; + + $variationGroup = VariationGroupDTO::fromArray([ + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [ + FlagshipField::FIELD_TARGETINGS => [$targetings], + ], + [ + FlagshipField::FIELD_TARGETINGS => [$targetings2], + ], + [ + FlagshipField::FIELD_TARGETINGS => [$targetingAllUsers], + ], + ], + ], + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertTrue($output); //Test Many targetingGroups with all false - $variationGroup = [ - FlagshipField::FIELD_TARGETING => [ - FlagshipField::FIELD_TARGETING_GROUPS => [ - [ - FlagshipField::FIELD_TARGETINGS => [$targetings], - ], - [ - FlagshipField::FIELD_TARGETINGS => [$targetings2], - ], - [ - FlagshipField::FIELD_TARGETINGS => [$targetings2], - ], - ], - ], - ]; - - $output = $isMatchTargetingMethod->invoke($bucketingManager, $variationGroup, $visitor); + $variationGroup = VariationGroupDTO::fromArray([ + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [ + FlagshipField::FIELD_TARGETINGS => [$targetings], + ], + [ + FlagshipField::FIELD_TARGETINGS => [$targetings2], + ], + [ + FlagshipField::FIELD_TARGETINGS => [$targetings2], + ], + ], + ], + ]); + + $output = $checkVisitorMatchesTargeting->invoke($bucketingManager, $variationGroup, $visitor); $this->assertFalse($output); } /** * @throws ReflectionException */ - public function testCheckAndTargeting() + public function testCheckAllTargetingRulesMatch() { $bucketingUrl = "http:127.0.0.1:3000"; $murmurhash = new MurmurHash(); @@ -619,136 +691,267 @@ public function testCheckAndTargeting() $configManager->setConfig($config); $visitor = new VisitorDelegate($container, $configManager, $visitorId, false, $visitorContext, true); - $checkAndTargetingMethod = Utils::getMethod(BucketingManager::class, "checkAndTargeting"); + $checkAllTargetingRulesMatchMethod = Utils::getMethod(BucketingManager::class, "checkAllTargetingRulesMatch"); + + //Test empty targetings + $innerTargetings = []; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); //test key = fs_all_users - $targetingAllUsers = [ - "key" => "fs_all_users", - "operator" => "EQUALS", - 'value' => '', - ]; + $targetingAllUsers = TargetingsDTO::fromArray([ + "key" => "fs_all_users", + "operator" => "EQUALS", + 'value' => '', + ]); $innerTargetings = [$targetingAllUsers]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertTrue($output); //test key = fs_all_users and not match key - $innerTargetings = [$targetingAllUsers, [ + $innerTargetings = [$targetingAllUsers, TargetingsDTO::fromArray([ "key" => "anyValue", "operator" => "EQUALS", 'value' => '' - ]]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + ])]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertFalse($output); //Test operator EXISTS when context doesn't exist - $innerTargetingsExists = [$targetingAllUsers, [ + $innerTargetingsExists = [$targetingAllUsers, TargetingsDTO::fromArray([ "operator" => "EXISTS", "key" => "mixpanel::city", "value" => true, "provider" => "mixpanel" - ]]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); + ])]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); $this->assertFalse($output); //Test operator EXISTS when context exists $visitor->updateContext("mixpanel::city", false); - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); $this->assertTrue($output); //Test operator NOT_EXISTS when context exists - $innerTargetingsExists = [$targetingAllUsers, [ + $innerTargetingsExists = [$targetingAllUsers, TargetingsDTO::fromArray([ "operator" => "NOT_EXISTS", "key" => "mixpanel::city", "value" => true, "provider" => "mixpanel" - ]]; + ])]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); $this->assertFalse($output); //Test operator NOT_EXISTS when context doesn't exist - $innerTargetingsExists = [$targetingAllUsers, [ + $innerTargetingsExists = [$targetingAllUsers, TargetingsDTO::fromArray([ "operator" => "NOT_EXISTS", "key" => "mixpanel::genre", "value" => true, "provider" => "mixpanel" - ]]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); + ])]; + + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetingsExists, $visitor); $this->assertTrue($output); //test key = fs_users - $targetingFsUsers = [ - "key" => "fs_users", - "operator" => "EQUALS", - 'value' => $visitorId, - ]; + $targetingFsUsers = TargetingsDTO::fromArray([ + "key" => "fs_users", + "operator" => "EQUALS", + 'value' => $visitorId, + ]); $innerTargetings = [$targetingFsUsers]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertTrue($output); //test key not match context - $targetingKeyContext = [ - "key" => "anyKey", - "operator" => "EQUALS", - 'value' => "anyValue", - ]; + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "anyKey", + "operator" => "EQUALS", + 'value' => "anyValue", + ]); $innerTargetings = [$targetingKeyContext]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertFalse($output); //test key match context - $targetingKeyContext = [ - "key" => "age", - "operator" => "EQUALS", - 'value' => 20, - ]; + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "EQUALS", + 'value' => 20, + ]); $innerTargetings = [$targetingKeyContext]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertTrue($output); //test key match context with different value - $targetingKeyContext2 = [ - "key" => "age", - "operator" => "EQUALS", - 'value' => 21, - ]; + $targetingKeyContext2 = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "EQUALS", + 'value' => 21, + ]); $innerTargetings = [$targetingKeyContext2]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertFalse($output); //And logic //All true $innerTargetings = [ - $targetingAllUsers, - $targetingFsUsers, - $targetingKeyContext, - ]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $targetingAllUsers, + $targetingFsUsers, + $targetingKeyContext, + ]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertTrue($output); //Test one false $innerTargetings = [ - $targetingAllUsers, - $targetingFsUsers, - $targetingKeyContext2, - ]; - $output = $checkAndTargetingMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $targetingAllUsers, + $targetingFsUsers, + $targetingKeyContext2, + ]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + //Test targeting with array value + + // Match value in array + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "EQUALS", + 'value' => [20, 25, 30], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Not match value in array + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "EQUALS", + 'value' => [21, 25, 30], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + // Match value in array for NOT_EQUALS + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "NOT_EQUALS", + 'value' => [21, 25, 30], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Not match value in array for NOT_EQUALS + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "age", + "operator" => "NOT_EQUALS", + 'value' => [20, 25, 30], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + // Test CONTAINS operator + $visitor->updateContext("interests", "sports"); + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "interests", + "operator" => "CONTAINS", + 'value' => ["sports", "music", "movies"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Test CONTAINS operator with non-matching value + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "interests", + "operator" => "CONTAINS", + 'value' => ["travel", "cooking", "reading"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + // Test CONTAINS operator with substring match + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "keyword", + "operator" => "CONTAINS", + 'value' => ["abc", "dfg", "hij"], + ]); + $visitor->updateContext("keyword", "nopq_hij"); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Test CONTAINS operator with non-matching substring + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "keyword", + "operator" => "CONTAINS", + 'value' => ["abc", "dfg", "xyz"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + + // Test NOT_CONTAINS operator + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "interests", + "operator" => "NOT_CONTAINS", + 'value' => ["travel", "cooking", "reading"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Test NOT_CONTAINS operator with matching value + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "interests", + "operator" => "NOT_CONTAINS", + 'value' => ["sports", "music", "movies"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertFalse($output); + + // Test NOT_CONTAINS operator with non-matching substring + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "keyword", + "operator" => "NOT_CONTAINS", + 'value' => ["abc", "dfg", "xyz"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); + $this->assertTrue($output); + + // Test NOT_CONTAINS operator with matching substring + $targetingKeyContext = TargetingsDTO::fromArray([ + "key" => "keyword", + "operator" => "NOT_CONTAINS", + 'value' => ["abc", "dfg", "hij"], + ]); + $innerTargetings = [$targetingKeyContext]; + $output = $checkAllTargetingRulesMatchMethod->invoke($bucketingManager, $innerTargetings, $visitor); $this->assertFalse($output); } /** * @throws ReflectionException */ - public function testOperator() + public function testEvaluateOperator() { $bucketingUrl = "http:127.0.0.1:3000"; $murmurhash = new MurmurHash(); @@ -758,31 +961,26 @@ public function testOperator() $configManager = $this->getMockBuilder(ConfigManager::class)->disableOriginalConstructor()->getMock(); $configManager->setConfig($config); - $testOperatorMethod = Utils::getMethod(BucketingManager::class, "testOperator"); + $evaluateOperatorMethod = Utils::getMethod(BucketingManager::class, "evaluateOperator"); /*Test EQUALS*/ //Test different values $contextValue = 5; $targetingValue = 6; - $output = $testOperatorMethod->invoke($bucketingManager, 'EQUALS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::EQUALS, $contextValue, $targetingValue); $this->assertFalse($output); //Test different type $targetingValue = "5"; - $output = $testOperatorMethod->invoke($bucketingManager, 'EQUALS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::EQUALS, $contextValue, $targetingValue); $this->assertFalse($output); //Test same type $targetingValue = 5; - $output = $testOperatorMethod->invoke($bucketingManager, 'EQUALS', $contextValue, $targetingValue); - $this->assertTrue($output); - - - $targetingValue = [5, 1, 2, 3]; - $output = $testOperatorMethod->invoke($bucketingManager, 'EQUALS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::EQUALS, $contextValue, $targetingValue); $this->assertTrue($output); /* Test NOT_EQUALS */ @@ -790,148 +988,59 @@ public function testOperator() //Test different values $targetingValue = 6; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_EQUALS', $contextValue, $targetingValue); - $this->assertTrue($output); - - - $targetingValue = [6, 1, 2, 3]; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_EQUALS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::NOT_EQUALS, $contextValue, $targetingValue); $this->assertTrue($output); //Test different type $targetingValue = "5"; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_EQUALS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::NOT_EQUALS, $contextValue, $targetingValue); $this->assertTrue($output); //Test same type $targetingValue = 5; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_EQUALS', $contextValue, $targetingValue); - $this->assertFalse($output); - - $targetingValue = [1, 2, 3, 5, 6]; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_EQUALS', $contextValue, $targetingValue); - $this->assertFalse($output); - - /* Test CONTAINS */ - - //Test contextValue not contains targetingValue - - $targetingValue = [ - 8, - 7, - 4, - 1, - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'CONTAINS', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::NOT_EQUALS, $contextValue, $targetingValue); $this->assertFalse($output); - //Test contextValue contains targetingValue - $targetingValue = [ - 8, - 7, - 5, - 1, - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'CONTAINS', $contextValue, $targetingValue); - $this->assertTrue($output); - - //Test contextValue contains targetingValue - $contextValue = "nopq_hij"; - $targetingValue = [ - "abc", - "dfg", - "hij", - "klm", - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'CONTAINS', $contextValue, $targetingValue); - $this->assertTrue($output); - - //Test contextValue contains targetingValue - - $targetingValue = "hij"; - $output = $testOperatorMethod->invoke($bucketingManager, 'CONTAINS', $contextValue, $targetingValue); - $this->assertTrue($output); - - //Test contextValue contains targetingValue - - $targetingValue = "hidf"; - $output = $testOperatorMethod->invoke($bucketingManager, 'CONTAINS', $contextValue, $targetingValue); - $this->assertFalse($output); - - /* Test NOT_CONTAINS */ - - //Test contextValue not contains targetingValue - $contextValue = 5; - $targetingValue = [ - 8, - 7, - 4, - 1, - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_CONTAINS', $contextValue, $targetingValue); - $this->assertTrue($output); - - //Test contextValue contains targetingValue - - $targetingValue = [ - 8, - 7, - 5, - 1, - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_CONTAINS', $contextValue, $targetingValue); - $this->assertFalse($output); - - //Test contextValue contains targetingValue - $contextValue = "nopq_hij"; - $targetingValue = [ - "abc", - "dfg", - "hij", - "klm", - ]; - $output = $testOperatorMethod->invoke($bucketingManager, 'NOT_CONTAINS', $contextValue, $targetingValue); - $this->assertFalse($output); /* Test GREATER_THAN */ //Test contextValue not GREATER_THAN targetingValue $contextValue = 5; $targetingValue = 6; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue not GREATER_THAN targetingValue $targetingValue = 5; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue not GREATER_THAN targetingValue $contextValue = 'a'; $targetingValue = 'b'; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue not GREATER_THAN targetingValue $contextValue = 'abz'; $targetingValue = 'bcg'; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue GREATER_THAN targetingValue $contextValue = 8; $targetingValue = 2; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue GREATER_THAN targetingValue $contextValue = "9dlk"; $targetingValue = 8; - $output = $testOperatorMethod->invoke($bucketingManager, 'GREATER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::GREATER_THAN, $contextValue, $targetingValue); $this->assertTrue($output); /* Test LOWER_THAN */ @@ -939,37 +1048,37 @@ public function testOperator() //Test contextValue LOWER_THAN targetingValue $contextValue = 5; $targetingValue = 6; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue not GREATER_THAN targetingValue $targetingValue = 5; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue LOWER_THAN targetingValue $contextValue = 'a'; $targetingValue = 'b'; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue LOWER_THAN targetingValue $contextValue = 'abz'; $targetingValue = 'bcg'; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue not LOWER_THAN targetingValue $contextValue = 8; $targetingValue = 2; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); //Test contextValue not LOWER_THAN targetingValue $contextValue = "9dlk"; $targetingValue = 8; - $output = $testOperatorMethod->invoke($bucketingManager, 'LOWER_THAN', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::LOWER_THAN, $contextValue, $targetingValue); $this->assertFalse($output); /* Test GREATER_THAN_OR_EQUALS */ @@ -977,9 +1086,9 @@ public function testOperator() //Test contextValue GREATER_THAN targetingValue $contextValue = 8; $targetingValue = 6; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'GREATER_THAN_OR_EQUALS', + TargetingOperator::GREATER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -988,9 +1097,9 @@ public function testOperator() //Test contextValue EQUALS targetingValue $targetingValue = 8; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'GREATER_THAN_OR_EQUALS', + TargetingOperator::GREATER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -998,9 +1107,9 @@ public function testOperator() //Test contextValue LOWER_THAN targetingValue $contextValue = 7; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'GREATER_THAN_OR_EQUALS', + TargetingOperator::GREATER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1009,9 +1118,9 @@ public function testOperator() //Test contextValue LOWER_THAN targetingValue $contextValue = 'a'; $targetingValue = 'b'; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'GREATER_THAN_OR_EQUALS', + TargetingOperator::GREATER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1022,9 +1131,9 @@ public function testOperator() //Test contextValue GREATER_THAN targetingValue $contextValue = 8; $targetingValue = 6; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'LOWER_THAN_OR_EQUALS', + TargetingOperator::LOWER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1033,9 +1142,9 @@ public function testOperator() //Test contextValue EQUALS targetingValue $targetingValue = 8; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'LOWER_THAN_OR_EQUALS', + TargetingOperator::LOWER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1043,9 +1152,9 @@ public function testOperator() //Test contextValue LOWER_THAN targetingValue $contextValue = 7; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'LOWER_THAN_OR_EQUALS', + TargetingOperator::LOWER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1054,9 +1163,9 @@ public function testOperator() //Test contextValue LOWER_THAN targetingValue $contextValue = 'a'; $targetingValue = 'b'; - $output = $testOperatorMethod->invoke( + $output = $evaluateOperatorMethod->invoke( $bucketingManager, - 'LOWER_THAN_OR_EQUALS', + TargetingOperator::LOWER_THAN_OR_EQUALS, $contextValue, $targetingValue ); @@ -1067,13 +1176,13 @@ public function testOperator() //Test contextValue STARTS_WITH targetingValue $contextValue = "abcd"; $targetingValue = "ab"; - $output = $testOperatorMethod->invoke($bucketingManager, 'STARTS_WITH', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::STARTS_WITH, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue not STARTS_WITH targetingValue $targetingValue = "bc"; - $output = $testOperatorMethod->invoke($bucketingManager, 'STARTS_WITH', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::STARTS_WITH, $contextValue, $targetingValue); $this->assertFalse($output); /* Test ENDS_WITH */ @@ -1081,18 +1190,13 @@ public function testOperator() //Test contextValue ENDS_WITH targetingValue $targetingValue = "d"; - $output = $testOperatorMethod->invoke($bucketingManager, 'ENDS_WITH', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::ENDS_WITH, $contextValue, $targetingValue); $this->assertTrue($output); //Test contextValue not ENDS_WITH targetingValue $targetingValue = "ab"; - $output = $testOperatorMethod->invoke($bucketingManager, 'ENDS_WITH', $contextValue, $targetingValue); - $this->assertFalse($output); - - //Test Any operator else - - $output = $testOperatorMethod->invoke($bucketingManager, 'ANY', $contextValue, $targetingValue); + $output = $evaluateOperatorMethod->invoke($bucketingManager, TargetingOperator::ENDS_WITH, $contextValue, $targetingValue); $this->assertFalse($output); } @@ -1106,27 +1210,21 @@ public function testGetThirdPartySegment() false, true, [ - 'post', - 'get', + 'post', + 'get', ] ); - $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', - [], - "", - true, - true, - true, - ['error'] - ); - $trackingManagerMock = $this->getMockForAbstractClass("Flagship\Api\TrackingManagerInterface"); + $trackingManagerMock = $this->getMockForAbstractClass(TrackingManagerInterface::class); $bucketingUrl = "127.0.0.1:3000"; $murmurhash = new MurmurHash(); + $config = new BucketingConfig($bucketingUrl); - $config->setEnvId("env_id")->setFetchThirdPartyData(true)->setLogManager($logManagerStub); + $config->setEnvId("env_id") + ->setLogLevel(LogLevel::DEBUG) + ->setFetchThirdPartyData(true); $bucketingManager = new BucketingManager($httpClientMock, $config, $murmurhash); $bucketingManager->setFlagshipInstanceId("instance_id"); @@ -1136,58 +1234,69 @@ public function testGetThirdPartySegment() $visitorContext = ["age" => 20]; $container = new Container(); - $configManager = $this->getMockBuilder(ConfigManager::class)->disableOriginalConstructor()->getMock(); + $configManager = $this->getMockBuilder(ConfigManager::class) + ->disableOriginalConstructor()->getMock(); + $configManager->setConfig($config); - $visitor = $this->getMockBuilder(VisitorDelegate::class)->setConstructorArgs([$container, $configManager, $visitorId, false, $visitorContext, true])->onlyMethods(["sendHit"])->getMock(); + $visitor = $this->getMockBuilder(VisitorDelegate::class) + ->setConstructorArgs([$container, $configManager, $visitorId, false, $visitorContext, true]) + ->onlyMethods(["sendHit", "getConfig"])->getMock(); + + $visitor->method("getConfig")->willReturn($config); $segments = [ - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'gender', - 'value' => '', - 'expiration' => 1689771307, - 'partner' => 'facebook', - ], - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'generation', - 'value' => '', - 'expiration' => 1689771307, - 'partner' => 'facebook', - ], - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'city', - 'value' => 'london', - 'expiration' => 1689771117, - 'partner' => 'mixpanel', - ], - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'device', - 'value' => 'firefox', - 'expiration' => 1689771117, - 'partner' => 'mixpanel', - ], - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'gender', - 'value' => 'female', - 'expiration' => 1689771007, - 'partner' => 'segmentio', - ], - [ - 'visitor_id' => 'wonderful_visitor_1', - 'segment' => 'generation', - 'value' => 'gen-z', - 'expiration' => 1689771007, - 'partner' => 'segmentio', - ], - ]; + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'gender', + 'value' => '', + 'expiration' => 1689771307, + 'partner' => 'facebook', + ], + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'generation', + 'value' => '', + 'expiration' => 1689771307, + 'partner' => 'facebook', + ], + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'city', + 'value' => 'london', + 'expiration' => 1689771117, + 'partner' => 'mixpanel', + ], + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'device', + 'value' => 'firefox', + 'expiration' => 1689771117, + 'partner' => 'mixpanel', + ], + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'gender', + 'value' => 'female', + 'expiration' => 1689771007, + 'partner' => 'segmentio', + ], + [ + 'visitor_id' => 'wonderful_visitor_1', + 'segment' => 'generation', + 'value' => 'gen-z', + 'expiration' => 1689771007, + 'partner' => 'segmentio', + ], + ]; $segmentUrl = sprintf(FlagshipConstant::THIRD_PARTY_SEGMENT_URL, $config->getEnvId(), $visitorId); - $campaigns = ["campaigns" => []]; + $campaigns = ["campaigns" => [ + [ + "id" => "campaign_1", + "variation_groups" => [] + ] + ]]; $matcher = $this->exactly(2); $httpClientMock->expects($matcher)->method("get")->with( @@ -1196,7 +1305,10 @@ public function testGetThirdPartySegment() $this->equalTo($segmentUrl) ), $this->equalTo([]) - )->willReturnOnConsecutiveCalls(new HttpResponse(200, $campaigns, []), new HttpResponse(200, $segments, [])); + )->willReturnOnConsecutiveCalls( + new HttpResponse(200, $campaigns, []), + new HttpResponse(200, $segments, []) + ); $bucketingManager->getCampaigns($visitor); $context = $visitor->getContext(); @@ -1218,8 +1330,8 @@ public function testGetThirdPartySegmentException() false, true, [ - 'post', - 'get', + 'post', + 'get', ] ); @@ -1255,7 +1367,12 @@ public function testGetThirdPartySegmentException() $visitor = $this->getMockBuilder(VisitorDelegate::class)->setConstructorArgs([$container, $configManager, $visitorId, false, $visitorContext, true])->onlyMethods(["sendHit"])->getMock(); $segmentUrl = sprintf(FlagshipConstant::THIRD_PARTY_SEGMENT_URL, $config->getEnvId(), $visitorId); - $campaigns = ["campaigns" => []]; + $campaigns = ["campaigns" => [ + [ + "id" => "campaign_1", + "variation_groups" => [] + ] + ]]; $matcher = $this->exactly(2); $httpClientMock->expects($matcher)->method("get")->with( diff --git a/tests/Flag/FlagMetadataTest.php b/tests/Flag/FlagMetadataTest.php index 184e10b4..4a13f621 100644 --- a/tests/Flag/FlagMetadataTest.php +++ b/tests/Flag/FlagMetadataTest.php @@ -40,29 +40,29 @@ public function testConstruct() $this->assertSame($metadata->getVariationName(), $variationName); $metadataJson = json_encode([ - "campaignId" => $campaignId, - "campaignName" => $campaignName, - "variationGroupId" => $variationGroupId, - "variationGroupName" => $variationGroupName, - "variationId" => $variationId, - "variationName" => $variationName, - "isReference" => $isReferenceId, - "campaignType" => $campaignType, - "slug" => $slug, - ]); + "campaignId" => $campaignId, + "campaignName" => $campaignName, + "variationGroupId" => $variationGroupId, + "variationGroupName" => $variationGroupName, + "variationId" => $variationId, + "variationName" => $variationName, + "isReference" => $isReferenceId, + "campaignType" => $campaignType, + "slug" => $slug, + ]); $this->assertJsonStringEqualsJsonString(json_encode($metadata), $metadataJson); $metadataJson = json_encode([ - "campaignId" => "", - "campaignName" => "", - "variationGroupId" => "", - "variationGroupName" => '', - "variationId" => "", - "variationName" => "", - "isReference" => false, - "campaignType" => "", - "slug" => "", - ]); + "campaignId" => "", + "campaignName" => "", + "variationGroupId" => "", + "variationGroupName" => '', + "variationId" => "", + "variationName" => "", + "isReference" => false, + "campaignType" => "", + "slug" => "", + ]); $this->assertJsonStringEqualsJsonString(json_encode(FSFlagMetadata::getEmpty()), $metadataJson); } diff --git a/tests/FlagshipTest.php b/tests/FlagshipTest.php index 80e72c2e..10c8e56f 100644 --- a/tests/FlagshipTest.php +++ b/tests/FlagshipTest.php @@ -3,26 +3,29 @@ namespace Flagship; use Exception; -use Flagship\Api\TrackingManager; -use Flagship\Config\BucketingConfig; -use Flagship\Config\DecisionApiConfig; -use Flagship\Config\FlagshipConfig; -use Flagship\Decision\ApiManager; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FSSdkStatus; +use ReflectionException; +use Flagship\Utils\Utils; use Flagship\Enum\LogLevel; -use Flagship\Model\HttpResponse; -use Flagship\Utils\ConfigManager; +use Psr\Log\LoggerInterface; use Flagship\Utils\Container; use Flagship\Visitor\Visitor; +use Flagship\Enum\FSSdkStatus; +use Flagship\Utils\HttpClient; +use PHPUnit\Framework\TestCase; +use Flagship\Model\HttpResponse; +use Flagship\Api\TrackingManager; +use Flagship\Decision\ApiManager; +use Flagship\Utils\ConfigManager; +use Flagship\Config\FlagshipConfig; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\BucketingConfig; use Flagship\Visitor\VisitorBuilder; use Flagship\Visitor\VisitorDelegate; -use Psr\Log\LoggerInterface; -use Flagship\Utils\Utils; -use PHPUnit\Framework\TestCase; -use ReflectionException; +use Flagship\Config\DecisionApiConfig; +use Flagship\Utils\FlagshipLogManager; +use Flagship\Utils\HttpClientInterface; -class FlagshipTest extends TestCase +class FlagshipTest extends BaseTestCase { /** * @var LoggerInterface @@ -41,8 +44,8 @@ public function __construct($name = null, array $data = array(), $dataName = '') true, true, [ - 'error', - 'info', + 'error', + 'info', ] ); } @@ -54,21 +57,14 @@ public function containerInitialization(): Container { $container = new Container(); - $container->bind( - 'Flagship\Utils\HttpClientInterface', - 'Flagship\Utils\HttpClient' - ); - if (version_compare(phpversion(), '8', '>=')) { - $container->bind( - 'Psr\Log\LoggerInterface', - 'Flagship\Utils\FlagshipLogManager8' - ); - } else { - $container->bind( - 'Psr\Log\LoggerInterface', - 'Flagship\Utils\FlagshipLogManager' + $container + ->bind( + HttpClientInterface::class, + HttpClient::class + )->bind( + LoggerInterface::class, + FlagshipLogManager::class ); - } return $container; } @@ -130,6 +126,8 @@ public function testStartWithoutConfig() { //Test Start Flagship without config argument + $this->mockErrorLog($this->any()); + $instanceMethod = Utils::getMethod("Flagship\Flagship", 'getInstance'); $instance = $instanceMethod->invoke(null); @@ -166,6 +164,8 @@ public function testStartWithoutConfig() */ public function testStartWithLog() { + + $this->mockErrorLog($this->any()); //Test Start Flagship $envId = "end_id"; $apiKey = "apiKey"; @@ -342,8 +342,20 @@ public function testNewVisitor() $this->assertInstanceOf(VisitorBuilder::class, $visitor1); } + public function testNewVisitorWithoutStartFlagship() + { + $this->mockErrorLog($this->once()); + + $visitorId = "visitorId"; + + $visitor1 = Flagship::newVisitor($visitorId, true); + $this->assertInstanceOf(VisitorBuilder::class, $visitor1); + } + public function testStatusCallback() { + $this->mockErrorLog($this->any()); + $config = $this->getMockForAbstractClass( FlagshipConfig::class, [], @@ -351,7 +363,7 @@ public function testStatusCallback() true, false, true, - [ "getOnSdkStatusChanged"] + ["getOnSdkStatusChanged"] ); $callable = function ($status) { @@ -369,6 +381,8 @@ public function testStatusCallback() */ public function testGetPanicModeStatus() { + $this->mockErrorLog($this->any()); + $config = new DecisionApiConfig(); $httpClientMock = $this->getMockForAbstractClass('Flagship\Utils\HttpClientInterface', ['post'], "", false); @@ -376,17 +390,17 @@ public function testGetPanicModeStatus() $visitorId = "visitorId"; $body = [ - "visitorId" => $visitorId, - "campaigns" => [], - "panic" => true, - ]; + "visitorId" => $visitorId, + "campaigns" => [], + "panic" => true, + ]; $httpClientMock->expects($this->exactly(2))->method('post')->willReturnOnConsecutiveCalls( new HttpResponse(204, $body), new HttpResponse(204, [ - "visitorId" => $visitorId, - "campaigns" => [], - ]) + "visitorId" => $visitorId, + "campaigns" => [], + ]) ); $apiManager = new ApiManager($httpClientMock, $config); @@ -397,7 +411,7 @@ public function testGetPanicModeStatus() $visitorId = 'Visitor_1'; - $containerGetMethod = function () use ($config, $apiManager, $trackingManager, $configManager, $visitorId) { + $containerGetMethod = function () use ($config, $apiManager, $httpClientMock, $trackingManager, $configManager, $visitorId) { $args = func_get_args(); return match ($args[0]) { 'Flagship\DecisionApiConfig' => $config, @@ -405,15 +419,22 @@ public function testGetPanicModeStatus() 'Flagship\Decision\ApiManager' => $apiManager, 'Flagship\Api\TrackingManager' => $trackingManager, 'Flagship\Utils\ConfigManager' => $configManager, - 'Flagship\Visitor\VisitorDelegate' => new VisitorDelegate(new Container(), $configManager, $visitorId, - false, [], true), + 'Flagship\Visitor\VisitorDelegate' => new VisitorDelegate( + new Container(), + $configManager, + $visitorId, + false, + [], + true + ), 'Flagship\Visitor\Visitor' => new Visitor($args[1][0]), + HttpClientInterface::class => $httpClientMock, default => null, }; }; $containerMock = $this->getMockBuilder( - 'Flagship\Utils\Container' + Container::class )->onlyMethods(['get'])->disableOriginalConstructor()->getMock(); $containerMock->method('get')->will($this->returnCallback($containerGetMethod)); @@ -491,6 +512,7 @@ public function testClose() Flagship::Close(); } + /** * @throws ReflectionException */ diff --git a/tests/Hit/ActivateBatchTest.php b/tests/Hit/ActivateBatchTest.php index c7865df3..83caaa53 100644 --- a/tests/Hit/ActivateBatchTest.php +++ b/tests/Hit/ActivateBatchTest.php @@ -2,14 +2,16 @@ namespace Flagship\Hit; +use Flagship\BaseTestCase; use Flagship\Config\DecisionApiConfig; use Flagship\Enum\FlagshipConstant; -use PHPUnit\Framework\TestCase; +use Flagship\Flag\FSFlagMetadata; -class ActivateBatchTest extends TestCase +class ActivateBatchTest extends BaseTestCase { public function testToApiKeys() { + $this->mockRoundFunction(); $variationId = "varId"; $variationGroupId = "varGrId"; @@ -18,7 +20,18 @@ public function testToApiKeys() $config = new DecisionApiConfig($envId); - $activate = new Activate($variationGroupId, $variationId); + $activate = new Activate($variationGroupId, $variationId, "key", new FSFlagMetadata( + "campaignId", + $variationGroupId, + $variationId, + true, + "campaignType", + "slug", + "campaignName", + "variationGroupName", + "variationName" + )); + $activate->setConfig($config)->setVisitorId($visitorId); $activateBatch = new ActivateBatch($config, [$activate]); @@ -27,8 +40,8 @@ public function testToApiKeys() unset($apiKeys[FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM]); $this->assertSame([ - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), - FlagshipConstant::BATCH => [$apiKeys], - ], $activateBatch->toApiKeys()); + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), + FlagshipConstant::BATCH => [$apiKeys], + ], $activateBatch->toApiKeys()); } } diff --git a/tests/Hit/ActivateTest.php b/tests/Hit/ActivateTest.php index a07a9ade..5d6620db 100644 --- a/tests/Hit/ActivateTest.php +++ b/tests/Hit/ActivateTest.php @@ -2,13 +2,15 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; -use Flagship\Flag\FSFlagMetadata; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Flag\FSFlagMetadata; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class ActivateTest extends TestCase { + use PHPMock; public function testTestConstruct() { $variationId = "varId"; @@ -35,8 +37,12 @@ public function testTestConstruct() $config = new DecisionApiConfig($envId); - $activate = new Activate($variationGroupId, $variationId); - $activate->setConfig($config)->setDs(FlagshipConstant::SDK_APP)->setVisitorId($visitorId); + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(123456789); + + $activate = new Activate($variationGroupId, $variationId, $flagKey, $flagMetadata); + $activate->setConfig($config)->setDs(FlagshipConstant::SDK_APP) + ->setVisitorId($visitorId); $this->assertSame($variationId, $activate->getVariationId()); $this->assertSame($variationGroupId, $activate->getVariationGroupId()); @@ -50,13 +56,13 @@ public function testTestConstruct() $this->assertSame($variationGroupId, $activate->getVariationGroupId()); $apiKeys = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::VARIATION_ID_API_ITEM => $variationId, - FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $variationGroupId, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::ANONYMOUS_ID => null, - FlagshipConstant::QT_API_ITEM => 0, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::VARIATION_ID_API_ITEM => $variationId, + FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $variationGroupId, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::ANONYMOUS_ID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + ]; $this->assertSame($apiKeys, $activate->toApiKeys()); @@ -64,13 +70,13 @@ public function testTestConstruct() $activate->setAnonymousId($anonymousId); $apiKeys = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::VARIATION_ID_API_ITEM => $variationId, - FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $variationGroupId, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::ANONYMOUS_ID => $anonymousId, - FlagshipConstant::QT_API_ITEM => 0, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::VARIATION_ID_API_ITEM => $variationId, + FlagshipConstant::VARIATION_GROUP_ID_API_ITEM => $variationGroupId, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::ANONYMOUS_ID => $anonymousId, + FlagshipConstant::QT_API_ITEM => 0.0, + ]; $this->assertSame($apiKeys, $activate->toApiKeys()); diff --git a/tests/Hit/EventTest.php b/tests/Hit/EventTest.php index 202021a3..4cee2557 100644 --- a/tests/Hit/EventTest.php +++ b/tests/Hit/EventTest.php @@ -4,16 +4,20 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\EventCategory; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\EventCategory; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class EventTest extends TestCase { + use PHPMock; public function testConstruct() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); $visitorId = "visitorId"; $envId = "envId"; @@ -28,19 +32,19 @@ public function testConstruct() $sessionNumber = 1; $eventArray = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::T_API_ITEM => HitType::EVENT->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::USER_IP_API_ITEM => $userIp, - FlagshipConstant::SCREEN_RESOLUTION_API_ITEM => $screenResolution, - FlagshipConstant::USER_LANGUAGE => $userLanguage, - FlagshipConstant::SESSION_NUMBER => $sessionNumber, - FlagshipConstant::EVENT_CATEGORY_API_ITEM => $eventCategory, - FlagshipConstant::EVENT_ACTION_API_ITEM => $eventAction, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::T_API_ITEM => HitType::EVENT->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::USER_IP_API_ITEM => $userIp, + FlagshipConstant::SCREEN_RESOLUTION_API_ITEM => $screenResolution, + FlagshipConstant::USER_LANGUAGE => $userLanguage, + FlagshipConstant::SESSION_NUMBER => $sessionNumber, + FlagshipConstant::EVENT_CATEGORY_API_ITEM => $eventCategory, + FlagshipConstant::EVENT_ACTION_API_ITEM => $eventAction, + ]; $event = new Event($eventCategory, $eventAction); $config = new DecisionApiConfig(); diff --git a/tests/Hit/HitBatchTest.php b/tests/Hit/HitBatchTest.php index 007e8b9a..626ffe8d 100644 --- a/tests/Hit/HitBatchTest.php +++ b/tests/Hit/HitBatchTest.php @@ -2,14 +2,15 @@ namespace Flagship\Hit; +use Flagship\BaseTestCase; use Flagship\Config\DecisionApiConfig; use Flagship\Enum\FlagshipConstant; -use PHPUnit\Framework\TestCase; -class HitBatchTest extends TestCase +class HitBatchTest extends BaseTestCase { public function testToApiKeys() { + $this->mockRoundFunction(); $visitorId = "visitorId"; $config = new DecisionApiConfig(); @@ -20,19 +21,19 @@ public function testToApiKeys() $screen->setConfig($config)->setVisitorId($visitorId); $hits = [ - $page, - $screen, - ]; + $page, + $screen, + ]; $batch = new HitBatch($config, [$page, $screen]); $data = [ - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), - FlagshipConstant::T_API_ITEM => "BATCH", - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::H_API_ITEM => [], - ]; + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), + FlagshipConstant::T_API_ITEM => "BATCH", + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::H_API_ITEM => [], + ]; foreach ($hits as $hit) { $hitApiKey = $hit->toApiKeys(); diff --git a/tests/Hit/ItemTest.php b/tests/Hit/ItemTest.php index f4e65ff1..5113b2c3 100644 --- a/tests/Hit/ItemTest.php +++ b/tests/Hit/ItemTest.php @@ -4,15 +4,19 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class ItemTest extends TestCase { + use PHPMock; public function testConstruct() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); $transactionId = "transactionId"; $itemName = "itemName"; $itemCode = "itemCode"; @@ -23,16 +27,16 @@ public function testConstruct() $itemCategory = "category 1"; $itemArray = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::T_API_ITEM => HitType::ITEM->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::TID_API_ITEM => $transactionId, - FlagshipConstant::IN_API_ITEM => $itemName, - FlagshipConstant::IC_API_ITEM => $itemCode, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::T_API_ITEM => HitType::ITEM->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::TID_API_ITEM => $transactionId, + FlagshipConstant::IN_API_ITEM => $itemName, + FlagshipConstant::IC_API_ITEM => $itemCode, + ]; $item = new Item($transactionId, $itemName, $itemCode); $config = new DecisionApiConfig($envId); diff --git a/tests/Hit/PageTest.php b/tests/Hit/PageTest.php index 8b485512..732d88b1 100644 --- a/tests/Hit/PageTest.php +++ b/tests/Hit/PageTest.php @@ -4,15 +4,19 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class PageTest extends TestCase { + use PHPMock; public function testConstruct() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); $pageUrl = 'ScreenName'; $visitorId = "visitorId"; $envId = "envId"; @@ -20,17 +24,18 @@ public function testConstruct() $page = new Page($pageUrl); $config = new DecisionApiConfig($envId); - $page->setConfig($config)->setDs(FlagshipConstant::SDK_APP)->setVisitorId($visitorId); + $page->setConfig($config) + ->setDs(FlagshipConstant::SDK_APP)->setVisitorId($visitorId); $screenArray = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::T_API_ITEM => HitType::PAGE_VIEW->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::DL_API_ITEM => $pageUrl, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::T_API_ITEM => HitType::PAGE_VIEW->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::DL_API_ITEM => $pageUrl, + ]; $this->assertSame($screenArray, $page->toApiKeys()); } diff --git a/tests/Hit/ScreenTest.php b/tests/Hit/ScreenTest.php index 0abdd790..f1707ea7 100644 --- a/tests/Hit/ScreenTest.php +++ b/tests/Hit/ScreenTest.php @@ -4,15 +4,20 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class ScreenTest extends TestCase { + use PHPMock; public function testConstruct() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); + $screenName = 'screenName'; $visitorId = "visitorId"; $envId = "envId"; @@ -22,14 +27,14 @@ public function testConstruct() $screen->setConfig($config)->setDs(FlagshipConstant::SDK_APP)->setVisitorId($visitorId); $screenArray = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::T_API_ITEM => HitType::SCREEN_VIEW->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::DL_API_ITEM => $screenName, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::T_API_ITEM => HitType::SCREEN_VIEW->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::DL_API_ITEM => $screenName, + ]; $this->assertSame($screenArray, $screen->toApiKeys()); } diff --git a/tests/Hit/SegmentTest.php b/tests/Hit/SegmentTest.php index 9ec8a63a..aa79c2a4 100644 --- a/tests/Hit/SegmentTest.php +++ b/tests/Hit/SegmentTest.php @@ -2,15 +2,20 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; class SegmentTest extends TestCase { + use PHPMock; public function testConstructor() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); + $envId = "envId"; $visitorId = "visitorId"; @@ -20,6 +25,7 @@ public function testConstructor() "key3" => true, "key4" => null, "key5" => false, + "key6" => ["subKey" => "subValue"] ]; $config = new DecisionApiConfig($envId); @@ -46,6 +52,7 @@ public function testConstructor() "key3" => "true", "key4" => "null", "key5" => "false", + "key6" => '' ] ]; diff --git a/tests/Hit/TransactionTest.php b/tests/Hit/TransactionTest.php index 1b035641..2c7eb111 100644 --- a/tests/Hit/TransactionTest.php +++ b/tests/Hit/TransactionTest.php @@ -4,15 +4,22 @@ namespace Flagship\Hit; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\HitType; +use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; + class TransactionTest extends TestCase { + use PHPMock; + public function testConstruct() { + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); + $logManagerMock = $this->getMockForAbstractClass( 'Psr\Log\LoggerInterface', [], @@ -35,15 +42,15 @@ public function testConstruct() $transaction->setVisitorId($visitorId)->setDs($ds)->setConfig($config); $transactionArray = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => $ds, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, - FlagshipConstant::T_API_ITEM => HitType::TRANSACTION->value, - FlagshipConstant::CUSTOMER_UID => null, - FlagshipConstant::QT_API_ITEM => 0.0, - FlagshipConstant::TID_API_ITEM => $transactionId, - FlagshipConstant::TA_API_ITEM => $transactionAffiliation, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => $ds, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $envId, + FlagshipConstant::T_API_ITEM => HitType::TRANSACTION->value, + FlagshipConstant::CUSTOMER_UID => null, + FlagshipConstant::QT_API_ITEM => 0.0, + FlagshipConstant::TID_API_ITEM => $transactionId, + FlagshipConstant::TA_API_ITEM => $transactionAffiliation, + ]; $taxesAmount = 76.0; $transaction->setTaxes($taxesAmount); diff --git a/tests/Hit/TroubleshootingTest.php b/tests/Hit/TroubleshootingTest.php index 265ad61b..5848f78d 100644 --- a/tests/Hit/TroubleshootingTest.php +++ b/tests/Hit/TroubleshootingTest.php @@ -3,21 +3,25 @@ namespace Flagship\Hit; use DateTime; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\CacheStrategy; -use Flagship\Enum\DecisionMode; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FSSdkStatus; +use Flagship\BaseTestCase; use Flagship\Enum\HitType; use Flagship\Enum\LogLevel; -use Flagship\Enum\TroubleshootingLabel; use Flagship\Model\FlagDTO; +use Flagship\Enum\FSSdkStatus; +use Flagship\Enum\DecisionMode; use PHPUnit\Framework\TestCase; +use Flagship\Enum\CacheStrategy; +use Flagship\Flag\FSFlagMetadata; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; +use Flagship\Enum\TroubleshootingLabel; -class TroubleshootingTest extends TestCase +class TroubleshootingTest extends BaseTestCase { public function testConstruct() { + $this->mockRoundFunction(); + $config = new DecisionApiConfig(); $config->setTimeout(5000); @@ -43,123 +47,179 @@ public function testConstruct() $httpResponseCode = 200; $httpResponseBody = ['key' => "value"]; $visitorContext = [ - "key1" => "value1", - "key2" => "value2", - ]; + "key1" => "value1", + "key2" => "value2", + ]; $visitorAssignmentHistory = [ - "key1" => "value1", - "key2" => "value2", - ]; + "key1" => "value1", + "key2" => "value2", + ]; $flagDto = new FlagDTO(); - $flagDto->setKey("key")->setValue("value")->setCampaignId("campaignId")->setCampaignType("ab")->setCampaignName("campaignName")->setVariationId("varId")->setVariationName("variationName")->setVariationGroupId("varGroupId")->setIsReference(false)->setSlug("slug")->setVariationGroupName("varGroupName"); + $flagDto->setKey("key")->setValue("value") + ->setCampaignId("campaignId")->setCampaignType("ab") + ->setCampaignName("campaignName")->setVariationId("varId") + ->setVariationName("variationName")->setVariationGroupId("varGroupId") + ->setIsReference(false)->setSlug("slug") + ->setVariationGroupName("varGroupName"); $flagDto2 = new FlagDTO(); - $flagDto2->setKey("key2")->setValue([])->setCampaignId("campaignId")->setCampaignType("ab")->setCampaignName("campaignName")->setVariationId("varId")->setVariationName("variationName")->setVariationGroupId("varGroupId")->setIsReference(false)->setVariationGroupName("varGroupName"); + $flagDto2->setKey("key2")->setValue([]) + ->setCampaignId("campaignId") + ->setCampaignType("ab") + ->setCampaignName("campaignName")->setVariationId("varId") + ->setVariationName("variationName") + ->setVariationGroupId("varGroupId") + ->setIsReference(false) + ->setVariationGroupName("varGroupName"); $visitorFlag = [ - $flagDto, - $flagDto2, - ]; + $flagDto, + $flagDto2, + ]; - $activateHit = new Activate("varGroupId", "varId"); + $activateHit = new Activate( + "varGroupId", + "varId", + "key", + new FSFlagMetadata( + "campaignId", + "varGroupId", + "varId", + false, + "ab", + "slug", + "campaignName", + "varGroupName", + "variationName" + ) + ); $activateHit->setConfig($config)->setVisitorId($visitorId); $sdkConfigBucketingUrl = 'http://localhost'; - $troubleshooting->setVisitorId($visitorId)->setAnonymousId($anonymousId)->setConfig($config)->setLogLevel(LogLevel::INFO)->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS)->setFlagshipInstanceId($flagshipInstanceId)->setVisitorSessionId($visitorInstanceId)->setStackOriginName($stackOriginName)->setStackOriginVersion($stackOriginVersion)->setSdkStatus($sdkStatus)->setSdkConfigMode($sdkConfigMode)->setSdkConfigLogLevel($config->getLogLevel())->setSdkConfigCustomLogManager(true)->setSdkConfigCustomCacheManager(true)->setSdkConfigStatusListener(false)->setSdkConfigBucketingUrl($sdkConfigBucketingUrl)->setSdkConfigUsingCustomHitCache(true)->setSdkConfigUsingOnVisitorExposed(true)->setSdkConfigUsingCustomVisitorCache(true)->setSdkConfigFetchThirdPartyData(true)->setSdkConfigTimeout($config->getTimeout())->setSdkConfigTrackingManagerConfigStrategy($cacheStrategy)->setHttpRequestUrl($httpRequestUrl)->setHttpRequestMethod($httpRequestMethod)->setHttpRequestHeaders($httpRequestHeaders)->setHttpRequestBody($httpRequestBody)->setHttpResponseUrl($httpResponseUrl)->setHttpResponseMethod($httpResponseMethod)->setHttpResponseHeaders($httpResponseHeaders)->setHttpResponseTime($httpResponseTime)->setHttpResponseCode($httpResponseCode)->setHttpResponseBody($httpResponseBody)->setVisitorConsent(true)->setVisitorContext($visitorContext)->setVisitorAssignmentHistory($visitorAssignmentHistory)->setVisitorFlags($visitorFlag)->setVisitorIsAuthenticated(true)->setVisitorCampaigns([])->setFlagKey($flagDto->getKey())->setFlagValue($flagDto->getValue())->setFlagMetadataCampaignIsReference($flagDto->getIsReference())->setFlagMetadataVariationId($flagDto->getVariationId())->setFlagMetadataVariationName($flagDto->getVariationName())->setFlagMetadataVariationGroupId($flagDto->getVariationGroupId())->setFlagMetadataVariationGroupName($flagDto->getVariationGroupName())->setFlagMetadataCampaignId($flagDto->getCampaignId())->setFlagMetadataCampaignName($flagDto->getCampaignName())->setFlagMetadataCampaignType($flagDto->getCampaignType())->setFlagDefault("default")->setFlagMetadataCampaignSlug($flagDto->getSlug())->setVisitorExposed(true)->setHitContent($activateHit->toApiKeys()); + $troubleshooting + ->setLogLevel(LogLevel::INFO) + ->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS) + ->setFlagshipInstanceId($flagshipInstanceId) + ->setVisitorSessionId($visitorInstanceId) + ->setStackOriginName($stackOriginName) + ->setStackOriginVersion($stackOriginVersion) + ->setSdkStatus($sdkStatus)->setSdkConfigMode($sdkConfigMode) + ->setSdkConfigLogLevel($config->getLogLevel())->setSdkConfigCustomLogManager(true) + ->setSdkConfigCustomCacheManager(true)->setSdkConfigStatusListener(false) + ->setSdkConfigBucketingUrl($sdkConfigBucketingUrl)->setSdkConfigUsingCustomHitCache(true) + ->setSdkConfigUsingOnVisitorExposed(true)->setSdkConfigUsingCustomVisitorCache(true) + ->setSdkConfigFetchThirdPartyData(true)->setSdkConfigTimeout($config->getTimeout()) + ->setSdkConfigTrackingManagerConfigStrategy($cacheStrategy)->setHttpRequestUrl($httpRequestUrl) + ->setHttpRequestMethod($httpRequestMethod)->setHttpRequestHeaders($httpRequestHeaders) + ->setHttpRequestBody($httpRequestBody)->setHttpResponseUrl($httpResponseUrl) + ->setHttpResponseMethod($httpResponseMethod)->setHttpResponseHeaders($httpResponseHeaders) + ->setHttpResponseTime($httpResponseTime)->setHttpResponseCode($httpResponseCode) + ->setHttpResponseBody($httpResponseBody)->setVisitorConsent(true) + ->setVisitorContext($visitorContext)->setVisitorAssignmentHistory($visitorAssignmentHistory) + ->setVisitorFlags($visitorFlag)->setVisitorIsAuthenticated(true)->setVisitorCampaigns([]) + ->setFlagKey($flagDto->getKey())->setFlagValue($flagDto->getValue()) + ->setFlagMetadataCampaignIsReference($flagDto->getIsReference()) + ->setFlagMetadataVariationId($flagDto->getVariationId()) + ->setFlagMetadataVariationName($flagDto->getVariationName()) + ->setFlagMetadataVariationGroupId($flagDto->getVariationGroupId()) + ->setFlagMetadataVariationGroupName($flagDto->getVariationGroupName())->setFlagMetadataCampaignId($flagDto->getCampaignId())->setFlagMetadataCampaignName($flagDto->getCampaignName())->setFlagMetadataCampaignType($flagDto->getCampaignType())->setFlagDefault("default")->setFlagMetadataCampaignSlug($flagDto->getSlug())->setVisitorExposed(true) + ->setHitContent($activateHit->toApiKeys())->setVisitorId($visitorId) + ->setAnonymousId($anonymousId) + ->setConfig($config); $customVariable = [ - 'version' => FlagshipConstant::TROUBLESHOOTING_VERSION, - 'logLevel' => LogLevel::INFO->name, - 'envId' => $config->getEnvId(), - 'timeZone' => (new DateTime())->getTimezone()->getName(), - 'label' => TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS->value, - 'stack.type' => FlagshipConstant::SDK, - 'stack.name' => FlagshipConstant::SDK_LANGUAGE, - 'stack.version' => FlagshipConstant::SDK_VERSION, - 'visitor.visitorId' => $visitorId, - 'visitor.anonymousId' => $anonymousId, - 'visitor.sessionId' => $visitorInstanceId, - 'flagshipInstanceId' => $flagshipInstanceId, - 'stack.origin.name' => $stackOriginName, - 'stack.origin.version' => $stackOriginVersion, - 'sdk.status' => $sdkStatus->name, - 'sdk.config.logLevel' => $config->getLogLevel()->name, - 'sdk.config.mode' => $sdkConfigMode->name, - 'sdk.config.customLogManager' => 'true', - 'sdk.config.customCacheManager' => 'true', - 'sdk.config.custom.StatusListener' => 'false', - 'sdk.config.timeout' => (string) $config->getTimeout(), - 'sdk.config.trackingManager.strategy' => $cacheStrategy->name, - 'sdk.config.bucketingUrl' => $sdkConfigBucketingUrl, - 'sdk.config.fetchThirdPartyData' => 'true', - 'sdk.config.usingOnVisitorExposed' => 'true', - 'sdk.config.usingCustomHitCache' => 'true', - 'sdk.config.usingCustomVisitorCache' => 'true', - 'http.request.url' => $httpRequestUrl, - 'http.request.method' => $httpRequestMethod, - 'http.request.headers' => json_encode($httpRequestHeaders), - 'http.request.body' => json_encode($httpRequestBody), - 'http.response.url' => $httpResponseUrl, - 'http.response.method' => $httpResponseMethod, - 'http.response.headers' => json_encode($httpResponseHeaders), - 'http.response.code' => (string)$httpResponseCode, - "http.response.body" => json_encode($httpResponseBody), - 'http.response.time' => (string)$httpResponseTime, - 'visitor.context.[key1]' => 'value1', - 'visitor.context.[key2]' => 'value2', - 'visitor.consent' => 'true', - 'visitor.assignments.key1' => 'value1', - 'visitor.assignments.key2' => 'value2', - 'visitor.flags.[key].key' => $flagDto->getKey(), - 'visitor.flags.[key].value' => $flagDto->getValue(), - 'visitor.flags.[key].metadata.variationId' => $flagDto->getVariationId(), - 'visitor.flags.[key].metadata.variationName' => $flagDto->getVariationName(), - 'visitor.flags.[key].metadata.variationGroupId' => $flagDto->getVariationGroupId(), - 'visitor.flags.[key].metadata.variationGroupName' => $flagDto->getVariationGroupName(), - 'visitor.flags.[key].metadata.campaignId' => $flagDto->getCampaignId(), - 'visitor.flags.[key].metadata.campaignName' => $flagDto->getCampaignName(), - 'visitor.flags.[key].metadata.campaignType' => $flagDto->getCampaignType(), - 'visitor.flags.[key].metadata.slug' => $flagDto->getSlug(), - 'visitor.flags.[key].metadata.isReference' => json_encode($flagDto->getIsReference()), - 'visitor.flags.[key2].key' => $flagDto2->getKey(), - 'visitor.flags.[key2].value' => json_encode($flagDto2->getValue()), - 'visitor.flags.[key2].metadata.variationId' => $flagDto2->getVariationId(), - 'visitor.flags.[key2].metadata.variationName' => $flagDto2->getVariationName(), - 'visitor.flags.[key2].metadata.variationGroupId' => $flagDto2->getVariationGroupId(), - 'visitor.flags.[key2].metadata.variationGroupName' => $flagDto2->getVariationGroupName(), - 'visitor.flags.[key2].metadata.campaignId' => $flagDto2->getCampaignId(), - 'visitor.flags.[key2].metadata.campaignName' => $flagDto2->getCampaignName(), - 'visitor.flags.[key2].metadata.campaignType' => $flagDto2->getCampaignType(), - 'visitor.flags.[key2].metadata.slug' => '', - 'visitor.flags.[key2].metadata.isReference' => json_encode($flagDto2->getIsReference()), - 'visitor.isAuthenticated' => 'true', - 'visitor.campaigns' => '[]', - 'flag.key' => $flagDto->getKey(), - 'flag.value' => $flagDto->getValue(), - 'flag.default' => "default", - 'flag.visitorExposed' => "true", - 'flag.metadata.campaignId' => $flagDto->getCampaignId(), - 'flag.metadata.campaignName' => $flagDto->getCampaignName(), - 'flag.metadata.variationGroupId' => $flagDto->getVariationGroupId(), - 'flag.metadata.variationGroupName' => $flagDto->getVariationGroupName(), - 'flag.metadata.variationId' => $flagDto->getVariationId(), - 'flag.metadata.variationName' => $flagDto->getVariationName(), - 'flag.metadata.campaignSlug' => $flagDto->getSlug(), - 'flag.metadata.campaignType' => $flagDto->getCampaignType(), - 'flag.metadata.isReference' => json_encode($flagDto->getIsReference()), - ]; + 'version' => FlagshipConstant::TROUBLESHOOTING_VERSION, + 'logLevel' => LogLevel::INFO->name, + 'envId' => $config->getEnvId(), + 'timeZone' => (new DateTime())->getTimezone()->getName(), + 'label' => TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS->value, + 'stack.type' => FlagshipConstant::SDK, + 'stack.name' => FlagshipConstant::SDK_LANGUAGE, + 'stack.version' => FlagshipConstant::SDK_VERSION, + 'visitor.visitorId' => $visitorId, + 'visitor.anonymousId' => $anonymousId, + 'visitor.sessionId' => $visitorInstanceId, + 'flagshipInstanceId' => $flagshipInstanceId, + 'stack.origin.name' => $stackOriginName, + 'stack.origin.version' => $stackOriginVersion, + 'sdk.status' => $sdkStatus->name, + 'sdk.config.logLevel' => $config->getLogLevel()->name, + 'sdk.config.mode' => $sdkConfigMode->name, + 'sdk.config.customLogManager' => 'true', + 'sdk.config.customCacheManager' => 'true', + 'sdk.config.custom.StatusListener' => 'false', + 'sdk.config.timeout' => (string) $config->getTimeout(), + 'sdk.config.trackingManager.strategy' => $cacheStrategy->name, + 'sdk.config.bucketingUrl' => $sdkConfigBucketingUrl, + 'sdk.config.fetchThirdPartyData' => 'true', + 'sdk.config.usingOnVisitorExposed' => 'true', + 'sdk.config.usingCustomHitCache' => 'true', + 'sdk.config.usingCustomVisitorCache' => 'true', + 'http.request.url' => $httpRequestUrl, + 'http.request.method' => $httpRequestMethod, + 'http.request.headers' => json_encode($httpRequestHeaders), + 'http.request.body' => json_encode($httpRequestBody), + 'http.response.url' => $httpResponseUrl, + 'http.response.method' => $httpResponseMethod, + 'http.response.headers' => json_encode($httpResponseHeaders), + 'http.response.code' => (string)$httpResponseCode, + "http.response.body" => json_encode($httpResponseBody), + 'http.response.time' => (string)$httpResponseTime, + 'visitor.context.[key1]' => 'value1', + 'visitor.context.[key2]' => 'value2', + 'visitor.consent' => 'true', + 'visitor.assignments.key1' => 'value1', + 'visitor.assignments.key2' => 'value2', + 'visitor.flags.[key].key' => $flagDto->getKey(), + 'visitor.flags.[key].value' => $flagDto->getValue(), + 'visitor.flags.[key].metadata.variationId' => $flagDto->getVariationId(), + 'visitor.flags.[key].metadata.variationName' => $flagDto->getVariationName(), + 'visitor.flags.[key].metadata.variationGroupId' => $flagDto->getVariationGroupId(), + 'visitor.flags.[key].metadata.variationGroupName' => $flagDto->getVariationGroupName(), + 'visitor.flags.[key].metadata.campaignId' => $flagDto->getCampaignId(), + 'visitor.flags.[key].metadata.campaignName' => $flagDto->getCampaignName(), + 'visitor.flags.[key].metadata.campaignType' => $flagDto->getCampaignType(), + 'visitor.flags.[key].metadata.slug' => $flagDto->getSlug(), + 'visitor.flags.[key].metadata.isReference' => json_encode($flagDto->getIsReference()), + 'visitor.flags.[key2].key' => $flagDto2->getKey(), + 'visitor.flags.[key2].value' => json_encode($flagDto2->getValue()), + 'visitor.flags.[key2].metadata.variationId' => $flagDto2->getVariationId(), + 'visitor.flags.[key2].metadata.variationName' => $flagDto2->getVariationName(), + 'visitor.flags.[key2].metadata.variationGroupId' => $flagDto2->getVariationGroupId(), + 'visitor.flags.[key2].metadata.variationGroupName' => $flagDto2->getVariationGroupName(), + 'visitor.flags.[key2].metadata.campaignId' => $flagDto2->getCampaignId(), + 'visitor.flags.[key2].metadata.campaignName' => $flagDto2->getCampaignName(), + 'visitor.flags.[key2].metadata.campaignType' => $flagDto2->getCampaignType(), + 'visitor.flags.[key2].metadata.slug' => '', + 'visitor.flags.[key2].metadata.isReference' => json_encode($flagDto2->getIsReference()), + 'visitor.isAuthenticated' => 'true', + 'visitor.campaigns' => '[]', + 'flag.key' => $flagDto->getKey(), + 'flag.value' => $flagDto->getValue(), + 'flag.default' => "default", + 'flag.visitorExposed' => "true", + 'flag.metadata.campaignId' => $flagDto->getCampaignId(), + 'flag.metadata.campaignName' => $flagDto->getCampaignName(), + 'flag.metadata.variationGroupId' => $flagDto->getVariationGroupId(), + 'flag.metadata.variationGroupName' => $flagDto->getVariationGroupName(), + 'flag.metadata.variationId' => $flagDto->getVariationId(), + 'flag.metadata.variationName' => $flagDto->getVariationName(), + 'flag.metadata.campaignSlug' => $flagDto->getSlug(), + 'flag.metadata.campaignType' => $flagDto->getCampaignType(), + 'flag.metadata.isReference' => json_encode($flagDto->getIsReference()), + ]; foreach ($activateHit->toApiKeys() as $key => $item) { $customVariable["hit." . $key] = is_string($item) ? $item : json_encode($item); } $expectedApiKey = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), - FlagshipConstant::T_API_ITEM => HitType::TROUBLESHOOTING->value, - 'cv' => $customVariable, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), + FlagshipConstant::T_API_ITEM => HitType::TROUBLESHOOTING->value, + 'cv' => $customVariable, + ]; $apiKey = $troubleshooting->toApiKeys(); unset($apiKey['cv']['timestamp']); @@ -167,44 +227,56 @@ public function testConstruct() $flagDto = new FlagDTO(); - $flagDto->setKey("key")->setValue([])->setCampaignId("campaignId")->setCampaignType("ab")->setCampaignName("campaignName")->setVariationId("varId")->setVariationName("variationName")->setVariationGroupId("varGroupId")->setIsReference(false)->setSlug("slug")->setVariationGroupName("varGroupName"); + $flagDto->setKey("key")->setValue([]) + ->setCampaignId("campaignId")->setCampaignType("ab") + ->setCampaignName("campaignName")->setVariationId("varId") + ->setVariationName("variationName") + ->setVariationGroupId("varGroupId") + ->setIsReference(false)->setSlug("slug") + ->setVariationGroupName("varGroupName"); $troubleshooting = new Troubleshooting(); - $troubleshooting->setVisitorId($visitorId)->setAnonymousId($anonymousId)->setConfig($config)->setLogLevel(LogLevel::INFO)->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS)->setFlagshipInstanceId($flagshipInstanceId)->setVisitorSessionId($visitorInstanceId)->setFlagKey($flagDto->getKey())->setFlagValue($flagDto->getValue())->setFlagMetadataCampaignIsReference($flagDto->getIsReference())->setFlagMetadataVariationId($flagDto->getVariationId())->setFlagMetadataVariationGroupId($flagDto->getVariationGroupId())->setFlagMetadataCampaignId($flagDto->getCampaignId())->setFlagMetadataCampaignType($flagDto->getCampaignType())->setFlagDefault([])->setFlagMetadataCampaignSlug($flagDto->getSlug())->setVisitorExposed(true); + $troubleshooting->setLogLevel(LogLevel::INFO) + ->setLabel(TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS)->setFlagshipInstanceId($flagshipInstanceId)->setVisitorSessionId($visitorInstanceId)->setFlagKey($flagDto->getKey())->setFlagValue($flagDto->getValue())->setFlagMetadataCampaignIsReference($flagDto->getIsReference())->setFlagMetadataVariationId($flagDto->getVariationId())->setFlagMetadataVariationGroupId($flagDto->getVariationGroupId())->setFlagMetadataCampaignId($flagDto->getCampaignId())->setFlagMetadataCampaignType($flagDto->getCampaignType())->setFlagDefault([]) + ->setFlagMetadataCampaignSlug($flagDto->getSlug()) + ->setVisitorExposed(true) + ->setVisitorId($visitorId) + ->setAnonymousId($anonymousId) + ->setConfig($config); $customVariable = [ - 'version' => FlagshipConstant::TROUBLESHOOTING_VERSION, - 'logLevel' => LogLevel::INFO->name, - 'envId' => $config->getEnvId(), - 'timeZone' => (new DateTime())->getTimezone()->getName(), - 'label' => TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS->value, - 'stack.type' => FlagshipConstant::SDK, - 'stack.name' => FlagshipConstant::SDK_LANGUAGE, - 'stack.version' => FlagshipConstant::SDK_VERSION, - 'visitor.visitorId' => $visitorId, - 'visitor.anonymousId' => $anonymousId, - 'visitor.sessionId' => $visitorInstanceId, - 'flagshipInstanceId' => $flagshipInstanceId, - 'flag.key' => $flagDto->getKey(), - 'flag.value' => json_encode($flagDto->getValue()), - 'flag.default' => json_encode([]), - 'flag.visitorExposed' => "true", - 'flag.metadata.campaignId' => $flagDto->getCampaignId(), - 'flag.metadata.variationGroupId' => $flagDto->getVariationGroupId(), - 'flag.metadata.variationId' => $flagDto->getVariationId(), - 'flag.metadata.campaignSlug' => $flagDto->getSlug(), - 'flag.metadata.campaignType' => $flagDto->getCampaignType(), - 'flag.metadata.isReference' => json_encode($flagDto->getIsReference()), - ]; + 'version' => FlagshipConstant::TROUBLESHOOTING_VERSION, + 'logLevel' => LogLevel::INFO->name, + 'envId' => $config->getEnvId(), + 'timeZone' => (new DateTime())->getTimezone()->getName(), + 'label' => TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS->value, + 'stack.type' => FlagshipConstant::SDK, + 'stack.name' => FlagshipConstant::SDK_LANGUAGE, + 'stack.version' => FlagshipConstant::SDK_VERSION, + 'visitor.visitorId' => $visitorId, + 'visitor.anonymousId' => $anonymousId, + 'visitor.sessionId' => $visitorInstanceId, + 'flagshipInstanceId' => $flagshipInstanceId, + 'flag.key' => $flagDto->getKey(), + 'flag.value' => json_encode($flagDto->getValue()), + 'flag.default' => json_encode([]), + 'flag.visitorExposed' => "true", + 'flag.metadata.campaignId' => $flagDto->getCampaignId(), + 'flag.metadata.variationGroupId' => $flagDto->getVariationGroupId(), + 'flag.metadata.variationId' => $flagDto->getVariationId(), + 'flag.metadata.campaignSlug' => $flagDto->getSlug(), + 'flag.metadata.campaignType' => $flagDto->getCampaignType(), + 'flag.metadata.isReference' => json_encode($flagDto->getIsReference()), + ]; $expectedApiKey = [ - FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, - FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, - FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), - FlagshipConstant::T_API_ITEM => HitType::TROUBLESHOOTING->value, - 'cv' => $customVariable, - ]; + FlagshipConstant::VISITOR_ID_API_ITEM => $visitorId, + FlagshipConstant::DS_API_ITEM => FlagshipConstant::SDK_APP, + FlagshipConstant::CUSTOMER_ENV_ID_API_ITEM => $config->getEnvId(), + FlagshipConstant::T_API_ITEM => HitType::TROUBLESHOOTING->value, + 'cv' => $customVariable, + ]; $apiKey = $troubleshooting->toApiKeys(); diff --git a/tests/Hit/UsageHitTest.php b/tests/Hit/UsageHitTest.php index 60275917..525c0d07 100644 --- a/tests/Hit/UsageHitTest.php +++ b/tests/Hit/UsageHitTest.php @@ -14,7 +14,9 @@ public function testToApiKeys() { $config = new DecisionApiConfig(); $analyticHit = new UsageHit(); - $analyticHit->setVisitorId("visitor")->setLogLevel(LogLevel::INFO)->setLabel(TroubleshootingLabel::FLAG_VALUE_NOT_CALLED)->setConfig($config); + $analyticHit->setLogLevel(LogLevel::INFO) + ->setLabel(TroubleshootingLabel::FLAG_VALUE_NOT_CALLED) + ->setConfig($config)->setVisitorId("visitor"); $this->assertSame('USAGE', $analyticHit->toApiKeys()['t']); } diff --git a/tests/Model/AccountSettingsDTOTest.php b/tests/Model/AccountSettingsDTOTest.php new file mode 100644 index 00000000..8176f38c --- /dev/null +++ b/tests/Model/AccountSettingsDTOTest.php @@ -0,0 +1,135 @@ +assertNull($dto->getEnabledXPC()); + $this->assertNull($dto->getTroubleshooting()); + $this->assertNull($dto->getEaiCollectEnabled()); + $this->assertNull($dto->getEaiActivationEnabled()); + + $instance1 = $dto->setEnabledXPC(true); + $this->assertTrue($dto->getEnabledXPC()); + $this->assertSame($dto, $instance1); + + $troubleshooting = new TroubleshootingDTO('2025-01-01', '2025-12-31', 100.0, 'UTC'); + $instance2 = $dto->setTroubleshooting($troubleshooting); + $this->assertSame($troubleshooting, $dto->getTroubleshooting()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setEaiCollectEnabled(false); + $this->assertFalse($dto->getEaiCollectEnabled()); + $this->assertSame($dto, $instance3); + + $instance4 = $dto->setEaiActivationEnabled(true); + $this->assertTrue($dto->getEaiActivationEnabled()); + $this->assertSame($dto, $instance4); + } + + public function testIsTroubleshootingEnabled() + { + $dto = new AccountSettingsDTO(); + $this->assertFalse($dto->isTroubleshootingEnabled()); + + $troubleshooting = new TroubleshootingDTO('2025-01-01', '2025-12-31', 100.0, 'UTC'); + $dto->setTroubleshooting($troubleshooting); + $this->assertTrue($dto->isTroubleshootingEnabled()); + + $dto->setTroubleshooting(null); + $this->assertFalse($dto->isTroubleshootingEnabled()); + } + + public function testFromArray() + { + $troubleshootingData = [ + FlagshipField::START_DATE => '2025-01-01', + FlagshipField::END_DATE => '2025-12-31', + FlagshipField::TRAFFIC => 75.5, + FlagshipField::TIMEZONE => 'UTC' + ]; + + $data = [ + FlagshipField::ENABLED_XPC => true, + FlagshipField::TROUBLESHOOTING => $troubleshootingData, + FlagshipField::EAI_COLLECT_ENABLED => true, + FlagshipField::EAI_ACTIVATION_ENABLED => false + ]; + + $dto = AccountSettingsDTO::fromArray($data); + + $this->assertTrue($dto->getEnabledXPC()); + $this->assertNotNull($dto->getTroubleshooting()); + $this->assertTrue($dto->getEaiCollectEnabled()); + $this->assertFalse($dto->getEaiActivationEnabled()); + } + + public function testFromArrayWithMissingFields() + { + $dto = AccountSettingsDTO::fromArray([]); + + $this->assertNull($dto->getEnabledXPC()); + $this->assertNull($dto->getTroubleshooting()); + $this->assertNull($dto->getEaiCollectEnabled()); + $this->assertNull($dto->getEaiActivationEnabled()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::ENABLED_XPC => 'not a bool', + FlagshipField::TROUBLESHOOTING => 'not an array', + FlagshipField::EAI_COLLECT_ENABLED => 123, + FlagshipField::EAI_ACTIVATION_ENABLED => [] + ]; + + $dto = AccountSettingsDTO::fromArray($data); + + $this->assertNull($dto->getEnabledXPC()); + $this->assertNull($dto->getTroubleshooting()); + $this->assertNull($dto->getEaiCollectEnabled()); + $this->assertNull($dto->getEaiActivationEnabled()); + } + + public function testToArray() + { + $dto = new AccountSettingsDTO(); + $dto->setEnabledXPC(true); + $dto->setEaiCollectEnabled(false); + $dto->setEaiActivationEnabled(true); + + $troubleshooting = new TroubleshootingDTO('2025-01-01', '2025-12-31', 50.0, 'America/New_York'); + $dto->setTroubleshooting($troubleshooting); + + $array = $dto->toArray(); + + $this->assertArrayHasKey(FlagshipField::ENABLED_XPC, $array); + $this->assertTrue($array[FlagshipField::ENABLED_XPC]); + + $this->assertArrayHasKey(FlagshipField::TROUBLESHOOTING, $array); + $this->assertIsArray($array[FlagshipField::TROUBLESHOOTING]); + + $this->assertArrayHasKey(FlagshipField::EAI_COLLECT_ENABLED, $array); + $this->assertFalse($array[FlagshipField::EAI_COLLECT_ENABLED]); + + $this->assertArrayHasKey(FlagshipField::EAI_ACTIVATION_ENABLED, $array); + $this->assertTrue($array[FlagshipField::EAI_ACTIVATION_ENABLED]); + } + + public function testToArrayWithNullValues() + { + $dto = new AccountSettingsDTO(); + $array = $dto->toArray(); + + $this->assertEmpty($array); + } +} diff --git a/tests/Model/BucketingCampaignDTOTest.php b/tests/Model/BucketingCampaignDTOTest.php new file mode 100644 index 00000000..7fb371e0 --- /dev/null +++ b/tests/Model/BucketingCampaignDTOTest.php @@ -0,0 +1,100 @@ +assertSame('campaign123', $dto->getId()); + $this->assertSame('ab', $dto->getType()); + $this->assertSame($variationGroups, $dto->getVariationGroups()); + } + + public function testGettersAndSetters() + { + $dto = new BucketingCampaignDTO('id1', 'type1', []); + + $instance1 = $dto->setId('newId'); + $this->assertSame('newId', $dto->getId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setName('Campaign Name'); + $this->assertSame('Campaign Name', $dto->getName()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setType('newType'); + $this->assertSame('newType', $dto->getType()); + $this->assertSame($dto, $instance3); + + $instance4 = $dto->setSlug('campaign-slug'); + $this->assertSame('campaign-slug', $dto->getSlug()); + $this->assertSame($dto, $instance4); + + $variationGroups = [ + new VariationGroupDTO('vg1', new TargetingDTO([]), []) + ]; + $instance5 = $dto->setVariationGroups($variationGroups); + $this->assertSame($variationGroups, $dto->getVariationGroups()); + $this->assertSame($dto, $instance5); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_ID => 'campaign123', + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_NANE => 'My Campaign', + FlagshipField::FIELD_SLUG => 'my-campaign', + FlagshipField::FIELD_VARIATION_GROUPS => [] + ]; + + $dto = BucketingCampaignDTO::fromArray($data); + + $this->assertSame('campaign123', $dto->getId()); + $this->assertSame('ab', $dto->getType()); + $this->assertSame('My Campaign', $dto->getName()); + $this->assertSame('my-campaign', $dto->getSlug()); + $this->assertIsArray($dto->getVariationGroups()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::FIELD_ID => 123, + FlagshipField::FIELD_CAMPAIGN_TYPE => [], + FlagshipField::FIELD_NANE => 456, + FlagshipField::FIELD_SLUG => null + ]; + + $dto = BucketingCampaignDTO::fromArray($data); + + $this->assertSame('', $dto->getId()); + $this->assertSame('', $dto->getType()); + $this->assertNull($dto->getName()); + $this->assertNull($dto->getSlug()); + } + + public function testToArray() + { + $variationGroup = new VariationGroupDTO('vg1', new TargetingDTO([]), []); + $dto = new BucketingCampaignDTO('campaign123', 'ab', [$variationGroup]); + $dto->setName('Campaign Name'); + $dto->setSlug('campaign-slug'); + + $array = $dto->toArray(); + + $this->assertSame('campaign123', $array[FlagshipField::FIELD_ID]); + $this->assertSame('ab', $array[FlagshipField::FIELD_CAMPAIGN_TYPE]); + $this->assertIsArray($array[FlagshipField::FIELD_VARIATION_GROUPS]); + $this->assertCount(1, $array[FlagshipField::FIELD_VARIATION_GROUPS]); + } +} diff --git a/tests/Model/BucketingDTOTest.php b/tests/Model/BucketingDTOTest.php new file mode 100644 index 00000000..7074c20f --- /dev/null +++ b/tests/Model/BucketingDTOTest.php @@ -0,0 +1,96 @@ +assertNull($dto->getPanic()); + $this->assertNull($dto->getCampaigns()); + $this->assertNull($dto->getAccountSettings()); + + $instance1 = $dto->setPanic(true); + $this->assertTrue($dto->getPanic()); + $this->assertSame($dto, $instance1); + + $campaigns = [ + new BucketingCampaignDTO('c1', 'ab', []) + ]; + $instance2 = $dto->setCampaigns($campaigns); + $this->assertSame($campaigns, $dto->getCampaigns()); + $this->assertSame($dto, $instance2); + + $accountSettings = new AccountSettingsDTO(); + $instance3 = $dto->setAccountSettings($accountSettings); + $this->assertSame($accountSettings, $dto->getAccountSettings()); + $this->assertSame($dto, $instance3); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_PANIC => true, + FlagshipField::FIELD_CAMPAIGNS => [ + [ + FlagshipField::FIELD_ID => 'c1', + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VARIATION_GROUPS => [] + ] + ], + FlagshipField::ACCOUNT_SETTINGS => [ + FlagshipField::ENABLED_XPC => true + ] + ]; + + $dto = BucketingDTO::fromArray($data); + + $this->assertTrue($dto->getPanic()); + $this->assertIsArray($dto->getCampaigns()); + $this->assertCount(1, $dto->getCampaigns()); + $this->assertNotNull($dto->getAccountSettings()); + } + + public function testFromArrayWithMissingFields() + { + $dto = BucketingDTO::fromArray([]); + + $this->assertNull($dto->getPanic()); + $this->assertNull($dto->getCampaigns()); + $this->assertNull($dto->getAccountSettings()); + } + + public function testToArray() + { + $dto = new BucketingDTO(); + $dto->setPanic(false); + + $campaign = new BucketingCampaignDTO('c1', 'ab', []); + $dto->setCampaigns([$campaign]); + + $accountSettings = new AccountSettingsDTO(); + $accountSettings->setEnabledXPC(true); + $dto->setAccountSettings($accountSettings); + + $array = $dto->toArray(); + + $this->assertFalse($array[FlagshipField::FIELD_PANIC]); + $this->assertIsArray($array[FlagshipField::FIELD_CAMPAIGNS]); + $this->assertIsArray($array[FlagshipField::ACCOUNT_SETTINGS]); + } + + public function testToArrayWithNullValues() + { + $dto = new BucketingDTO(); + $array = $dto->toArray(); + + $this->assertArrayNotHasKey(FlagshipField::FIELD_PANIC, $array); + } +} diff --git a/tests/Model/BucketingVariationDTOTest.php b/tests/Model/BucketingVariationDTOTest.php new file mode 100644 index 00000000..6839cf80 --- /dev/null +++ b/tests/Model/BucketingVariationDTOTest.php @@ -0,0 +1,101 @@ + 'value1']); + $dto = new BucketingVariationDTO('var123', $modifications); + + $this->assertNull($dto->getAllocation()); + + $instance = $dto->setAllocation(50.5); + $this->assertSame(50.5, $dto->getAllocation()); + $this->assertSame($dto, $instance); + + $dto->setAllocation(null); + $this->assertNull($dto->getAllocation()); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_ID => 'var123', + FlagshipField::FIELD_NANE => 'Variation Name', + FlagshipField::FIELD_REFERENCE => true, + FlagshipField::FIELD_ALLOCATION => 75.5, + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => ['key1' => 'value1'] + ] + ]; + + $dto = BucketingVariationDTO::fromArray($data); + + $this->assertSame('var123', $dto->getId()); + $this->assertSame('Variation Name', $dto->getName()); + $this->assertTrue($dto->getReference()); + $this->assertSame(75.5, $dto->getAllocation()); + $this->assertInstanceOf(ModificationsDTO::class, $dto->getModifications()); + } + + public function testFromArrayWithStringAllocation() + { + $data = [ + FlagshipField::FIELD_ID => 'var123', + FlagshipField::FIELD_ALLOCATION => '50', + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => [] + ] + ]; + + $dto = BucketingVariationDTO::fromArray($data); + $this->assertSame(50.0, $dto->getAllocation()); + } + + public function testFromArrayWithInvalidAllocation() + { + $data = [ + FlagshipField::FIELD_ID => 'var123', + FlagshipField::FIELD_ALLOCATION => 'invalid', + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => [] + ] + ]; + + $dto = BucketingVariationDTO::fromArray($data); + $this->assertNull($dto->getAllocation()); + } + + public function testToArray() + { + $modifications = new ModificationsDTO('ab', ['key1' => 'value1']); + $dto = new BucketingVariationDTO('var123', $modifications); + $dto->setName('Variation Name'); + $dto->setReference(false); + $dto->setAllocation(25.0); + + $array = $dto->toArray(); + + $this->assertSame('var123', $array[FlagshipField::FIELD_ID]); + $this->assertSame(25.0, $array[FlagshipField::FIELD_ALLOCATION]); + $this->assertIsArray($array[FlagshipField::FIELD_MODIFICATIONS]); + } + + public function testInheritsFromVariationDTO() + { + $modifications = new ModificationsDTO('ab', []); + $dto = new BucketingVariationDTO('var123', $modifications); + + $this->assertInstanceOf(VariationDTO::class, $dto); + } +} diff --git a/tests/Model/CampaignCacheDTOTest.php b/tests/Model/CampaignCacheDTOTest.php new file mode 100644 index 00000000..2e352c3b --- /dev/null +++ b/tests/Model/CampaignCacheDTOTest.php @@ -0,0 +1,124 @@ + 'value1']); + $dto = new CampaignCacheDTO('c1', 'vg1', 'v1', $flags); + + $this->assertSame('c1', $dto->getCampaignId()); + $this->assertSame('vg1', $dto->getVariationGroupId()); + $this->assertSame('v1', $dto->getVariationId()); + $this->assertSame($flags, $dto->getFlags()); + } + + public function testGettersAndSetters() + { + $flags = new ModificationsDTO('ab', []); + $dto = new CampaignCacheDTO('c1', 'vg1', 'v1', $flags); + + $instance1 = $dto->setCampaignId('c2'); + $this->assertSame('c2', $dto->getCampaignId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setVariationGroupId('vg2'); + $this->assertSame('vg2', $dto->getVariationGroupId()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setVariationId('v2'); + $this->assertSame('v2', $dto->getVariationId()); + $this->assertSame($dto, $instance3); + + $instance4 = $dto->setType('ab'); + $this->assertSame('ab', $dto->getType()); + $this->assertSame($dto, $instance4); + + $instance5 = $dto->setSlug('campaign-slug'); + $this->assertSame('campaign-slug', $dto->getSlug()); + $this->assertSame($dto, $instance5); + + $instance6 = $dto->setName('Campaign Name'); + $this->assertSame('Campaign Name', $dto->getName()); + $this->assertSame($dto, $instance6); + + $instance7 = $dto->setIsReference(true); + $this->assertTrue($dto->getIsReference()); + $this->assertSame($dto, $instance7); + + $instance8 = $dto->setActivated(false); + $this->assertFalse($dto->getActivated()); + $this->assertSame($dto, $instance8); + + $newFlags = new ModificationsDTO('toggle', ['key2' => 'value2']); + $instance9 = $dto->setFlags($newFlags); + $this->assertSame($newFlags, $dto->getFlags()); + $this->assertSame($dto, $instance9); + } + + public function testFromArray() + { + $data = [ + StrategyAbstract::CAMPAIGN_ID => 'c1', + StrategyAbstract::VARIATION_GROUP_ID => 'vg1', + StrategyAbstract::VARIATION_ID => 'v1', + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_SLUG => 'slug', + FlagshipField::FIELD_CAMPAIGN_NAME => 'Campaign', + FlagshipField::FIELD_IS_REFERENCE => true, + FlagshipField::FIELD_ACTIVATED => true, + FlagshipField::FIELD_FLAGS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => ['key' => 'value'] + ] + ]; + + $dto = CampaignCacheDTO::fromArray($data); + + $this->assertSame('c1', $dto->getCampaignId()); + $this->assertSame('vg1', $dto->getVariationGroupId()); + $this->assertSame('v1', $dto->getVariationId()); + $this->assertSame('ab', $dto->getType()); + $this->assertSame('Campaign', $dto->getName()); + $this->assertTrue($dto->getIsReference()); + $this->assertTrue($dto->getActivated()); + $this->assertInstanceOf(ModificationsDTO::class, $dto->getFlags()); + } + + public function testFromArrayWithMissingFields() + { + $dto = CampaignCacheDTO::fromArray([]); + + $this->assertSame('', $dto->getCampaignId()); + $this->assertSame('', $dto->getVariationGroupId()); + $this->assertSame('', $dto->getVariationId()); + $this->assertInstanceOf(ModificationsDTO::class, $dto->getFlags()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + StrategyAbstract::CAMPAIGN_ID => 123, + StrategyAbstract::VARIATION_GROUP_ID => [], + StrategyAbstract::VARIATION_ID => null, + FlagshipField::FIELD_CAMPAIGN_TYPE => 456, + FlagshipField::FIELD_IS_REFERENCE => 'true', + FlagshipField::FIELD_ACTIVATED => 1 + ]; + + $dto = CampaignCacheDTO::fromArray($data); + + $this->assertSame('', $dto->getCampaignId()); + $this->assertNull($dto->getType()); + $this->assertNull($dto->getIsReference()); + $this->assertNull($dto->getActivated()); + } +} diff --git a/tests/Model/CampaignDTOTest.php b/tests/Model/CampaignDTOTest.php new file mode 100644 index 00000000..386ef1a8 --- /dev/null +++ b/tests/Model/CampaignDTOTest.php @@ -0,0 +1,118 @@ +assertSame('c1', $dto->getId()); + $this->assertSame('vg1', $dto->getVariationGroupId()); + $this->assertSame($variation, $dto->getVariation()); + } + + public function testGettersAndSetters() + { + $variation = new VariationDTO('v1', new ModificationsDTO('ab', [])); + $dto = new CampaignDTO('c1', 'vg1', $variation); + + $instance1 = $dto->setId('c2'); + $this->assertSame('c2', $dto->getId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setName('Campaign Name'); + $this->assertSame('Campaign Name', $dto->getName()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setSlug('campaign-slug'); + $this->assertSame('campaign-slug', $dto->getSlug()); + $this->assertSame($dto, $instance3); + + $instance4 = $dto->setVariationGroupId('vg2'); + $this->assertSame('vg2', $dto->getVariationGroupId()); + $this->assertSame($dto, $instance4); + + $instance5 = $dto->setVariationGroupName('VG Name'); + $this->assertSame('VG Name', $dto->getVariationGroupName()); + $this->assertSame($dto, $instance5); + + $newVariation = new VariationDTO('v2', new ModificationsDTO('toggle', [])); + $instance6 = $dto->setVariation($newVariation); + $this->assertSame($newVariation, $dto->getVariation()); + $this->assertSame($dto, $instance6); + + $instance7 = $dto->setType('ab'); + $this->assertSame('ab', $dto->getType()); + $this->assertSame($dto, $instance7); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_ID => 'c1', + FlagshipField::FIELD_VARIATION_GROUP_ID => 'vg1', + FlagshipField::FIELD_NANE => 'Campaign Name', + FlagshipField::FIELD_SLUG => 'campaign-slug', + FlagshipField::FIELD_VARIATION_GROUP_NAME => 'VG Name', + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VARIATION => [ + FlagshipField::FIELD_ID => 'v1', + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => [] + ] + ] + ]; + + $dto = CampaignDTO::fromArray($data); + + $this->assertSame('c1', $dto->getId()); + $this->assertSame('vg1', $dto->getVariationGroupId()); + $this->assertSame('Campaign Name', $dto->getName()); + $this->assertSame('campaign-slug', $dto->getSlug()); + $this->assertSame('VG Name', $dto->getVariationGroupName()); + $this->assertSame('ab', $dto->getType()); + $this->assertInstanceOf(VariationDTO::class, $dto->getVariation()); + } + + public function testFromArrayWithMissingFields() + { + $dto = CampaignDTO::fromArray([]); + + $this->assertSame('', $dto->getId()); + $this->assertSame('', $dto->getVariationGroupId()); + $this->assertNull($dto->getName()); + $this->assertNull($dto->getSlug()); + $this->assertNull($dto->getVariationGroupName()); + $this->assertNull($dto->getType()); + $this->assertInstanceOf(VariationDTO::class, $dto->getVariation()); + } + + public function testToArray() + { + $variation = new VariationDTO('v1', new ModificationsDTO('ab', ['key' => 'value'])); + $dto = new CampaignDTO('c1', 'vg1', $variation); + $dto->setName('Campaign Name'); + $dto->setSlug('slug'); + $dto->setVariationGroupName('VG Name'); + $dto->setType('ab'); + + $array = $dto->toArray(); + + $this->assertSame('c1', $array[FlagshipField::FIELD_ID]); + $this->assertSame('Campaign Name', $array[FlagshipField::FIELD_NANE]); + $this->assertSame('slug', $array[FlagshipField::FIELD_SLUG]); + $this->assertSame('vg1', $array[FlagshipField::FIELD_VARIATION_GROUP_ID]); + $this->assertSame('VG Name', $array[FlagshipField::FIELD_VARIATION_GROUP_NAME]); + $this->assertSame('ab', $array[FlagshipField::FIELD_CAMPAIGN_TYPE]); + $this->assertIsArray($array[FlagshipField::FIELD_VARIATION]); + } +} diff --git a/tests/Model/ExposedVisitorTest.php b/tests/Model/ExposedVisitorTest.php new file mode 100644 index 00000000..18be44b0 --- /dev/null +++ b/tests/Model/ExposedVisitorTest.php @@ -0,0 +1,69 @@ + 'value1', 'key2' => 123]; + $visitor = new ExposedVisitor('visitor123', 'anon456', $context); + + $this->assertSame('visitor123', $visitor->getId()); + $this->assertSame('anon456', $visitor->getAnonymousId()); + $this->assertSame($context, $visitor->getContext()); + } + + public function testConstructorWithNullAnonymousId() + { + $context = ['key1' => 'value1']; + $visitor = new ExposedVisitor('visitor123', null, $context); + + $this->assertSame('visitor123', $visitor->getId()); + $this->assertNull($visitor->getAnonymousId()); + $this->assertSame($context, $visitor->getContext()); + } + + public function testConstructorWithEmptyContext() + { + $visitor = new ExposedVisitor('visitor123', 'anon456', []); + + $this->assertSame('visitor123', $visitor->getId()); + $this->assertSame('anon456', $visitor->getAnonymousId()); + $this->assertSame([], $visitor->getContext()); + } + + public function testGetId() + { + $visitor = new ExposedVisitor('user-id-123', null, []); + $this->assertSame('user-id-123', $visitor->getId()); + } + + public function testGetAnonymousId() + { + $visitor = new ExposedVisitor('user-id', 'anonymous-id-789', []); + $this->assertSame('anonymous-id-789', $visitor->getAnonymousId()); + } + + public function testGetContext() + { + $context = [ + 'browser' => 'chrome', + 'version' => '120', + 'age' => 25, + 'premium' => true + ]; + $visitor = new ExposedVisitor('user-id', null, $context); + $this->assertSame($context, $visitor->getContext()); + } + + public function testImplementsInterface() + { + $visitor = new ExposedVisitor('id', null, []); + $this->assertInstanceOf(ExposedVisitorInterface::class, $visitor); + } +} diff --git a/tests/Model/FetchFlagsStatusTest.php b/tests/Model/FetchFlagsStatusTest.php new file mode 100644 index 00000000..042806ad --- /dev/null +++ b/tests/Model/FetchFlagsStatusTest.php @@ -0,0 +1,61 @@ +assertSame($status, $fetchStatus->getStatus()); + $this->assertSame($reason, $fetchStatus->getReason()); + } + + public function testGetStatus() + { + $status = FSFetchStatus::FETCH_REQUIRED; + $reason = FSFetchReason::VISITOR_CREATED; + $fetchStatus = new FetchFlagsStatus($status, $reason); + + $this->assertSame($status, $fetchStatus->getStatus()); + } + + public function testGetReason() + { + $status = FSFetchStatus::PANIC; + $reason = FSFetchReason::NONE; + $fetchStatus = new FetchFlagsStatus($status, $reason); + + $this->assertSame($reason, $fetchStatus->getReason()); + } + + public function testImplementsInterface() + { + $fetchStatus = new FetchFlagsStatus(FSFetchStatus::FETCHED, FSFetchReason::NONE); + $this->assertInstanceOf(FetchFlagsStatusInterface::class, $fetchStatus); + } + + public function testWithDifferentStatusCombinations() + { + $combinations = [ + [FSFetchStatus::FETCHED, FSFetchReason::NONE], + [FSFetchStatus::FETCH_REQUIRED, FSFetchReason::VISITOR_CREATED], + [FSFetchStatus::PANIC, FSFetchReason::NONE], + ]; + + foreach ($combinations as [$status, $reason]) { + $fetchStatus = new FetchFlagsStatus($status, $reason); + $this->assertSame($status, $fetchStatus->getStatus()); + $this->assertSame($reason, $fetchStatus->getReason()); + } + } +} diff --git a/tests/Model/ModificationsDTOTest.php b/tests/Model/ModificationsDTOTest.php new file mode 100644 index 00000000..9a7b70a8 --- /dev/null +++ b/tests/Model/ModificationsDTOTest.php @@ -0,0 +1,90 @@ + 'value1', 'key2' => 123]; + $dto = new ModificationsDTO($type, $value); + + $this->assertSame($type, $dto->getType()); + $this->assertSame($value, $dto->getValue()); + } + + public function testGettersAndSetters() + { + $dto = new ModificationsDTO('ab', []); + + $instance1 = $dto->setType('toggle'); + $this->assertSame('toggle', $dto->getType()); + $this->assertSame($dto, $instance1); + + $value = ['key' => 'value', 'number' => 42]; + $instance2 = $dto->setValue($value); + $this->assertSame($value, $dto->getValue()); + $this->assertSame($dto, $instance2); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => ['key1' => 'value1', 'key2' => true] + ]; + + $dto = ModificationsDTO::fromArray($data); + + $this->assertSame('ab', $dto->getType()); + $this->assertSame(['key1' => 'value1', 'key2' => true], $dto->getValue()); + } + + public function testFromArrayWithMissingFields() + { + $dto = ModificationsDTO::fromArray([]); + + $this->assertSame('', $dto->getType()); + $this->assertSame([], $dto->getValue()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 123, + FlagshipField::FIELD_VALUE => 'not an array' + ]; + + $dto = ModificationsDTO::fromArray($data); + + $this->assertSame('', $dto->getType()); + $this->assertSame([], $dto->getValue()); + } + + public function testToArray() + { + $type = 'toggle'; + $value = ['feature1' => true, 'feature2' => false]; + $dto = new ModificationsDTO($type, $value); + + $array = $dto->toArray(); + + $this->assertSame($type, $array[FlagshipField::FIELD_CAMPAIGN_TYPE]); + $this->assertSame($value, $array[FlagshipField::FIELD_VALUE]); + } + + public function testToArrayWithEmptyValue() + { + $dto = new ModificationsDTO('ab', []); + $array = $dto->toArray(); + + $this->assertSame('ab', $array[FlagshipField::FIELD_CAMPAIGN_TYPE]); + $this->assertSame([], $array[FlagshipField::FIELD_VALUE]); + } +} diff --git a/tests/Model/TargetingDTOTest.php b/tests/Model/TargetingDTOTest.php new file mode 100644 index 00000000..0baac69f --- /dev/null +++ b/tests/Model/TargetingDTOTest.php @@ -0,0 +1,100 @@ +assertSame($targetingGroups, $dto->getTargetingGroups()); + } + + public function testGettersAndSetters() + { + $dto = new TargetingDTO([]); + + $targetingGroups = [ + new TargetingGroupDTO([]), + new TargetingGroupDTO([]) + ]; + $instance = $dto->setTargetingGroups($targetingGroups); + + $this->assertSame($targetingGroups, $dto->getTargetingGroups()); + $this->assertSame($dto, $instance); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_TARGETING_GROUPS => [ + [ + FlagshipField::FIELD_TARGETINGS => [] + ], + [ + FlagshipField::FIELD_TARGETINGS => [] + ] + ] + ]; + + $dto = TargetingDTO::fromArray($data); + + $this->assertIsArray($dto->getTargetingGroups()); + $this->assertCount(2, $dto->getTargetingGroups()); + $this->assertContainsOnlyInstancesOf(TargetingGroupDTO::class, $dto->getTargetingGroups()); + } + + public function testFromArrayWithEmptyData() + { + $dto = TargetingDTO::fromArray([]); + + $this->assertIsArray($dto->getTargetingGroups()); + $this->assertEmpty($dto->getTargetingGroups()); + } + + public function testFromArrayWithInvalidData() + { + $data = [ + FlagshipField::FIELD_TARGETING_GROUPS => 'not an array' + ]; + + $dto = TargetingDTO::fromArray($data); + + $this->assertIsArray($dto->getTargetingGroups()); + $this->assertEmpty($dto->getTargetingGroups()); + } + + public function testToArray() + { + $targetingGroups = [ + new TargetingGroupDTO([]), + new TargetingGroupDTO([]) + ]; + $dto = new TargetingDTO($targetingGroups); + + $array = $dto->toArray(); + + $this->assertArrayHasKey(FlagshipField::FIELD_TARGETING_GROUPS, $array); + $this->assertIsArray($array[FlagshipField::FIELD_TARGETING_GROUPS]); + $this->assertCount(2, $array[FlagshipField::FIELD_TARGETING_GROUPS]); + } + + public function testToArrayWithEmptyGroups() + { + $dto = new TargetingDTO([]); + $array = $dto->toArray(); + + $this->assertArrayHasKey(FlagshipField::FIELD_TARGETING_GROUPS, $array); + $this->assertIsArray($array[FlagshipField::FIELD_TARGETING_GROUPS]); + $this->assertEmpty($array[FlagshipField::FIELD_TARGETING_GROUPS]); + } +} diff --git a/tests/Model/TargetingGroupDTOTest.php b/tests/Model/TargetingGroupDTOTest.php new file mode 100644 index 00000000..2638c598 --- /dev/null +++ b/tests/Model/TargetingGroupDTOTest.php @@ -0,0 +1,104 @@ +assertSame($targetings, $dto->getTargetings()); + } + + public function testGettersAndSetters() + { + $dto = new TargetingGroupDTO([]); + + $targetings = [ + new TargetingsDTO(\Flagship\Enum\TargetingOperator::EQUALS, 'key1', 'value1'), + new TargetingsDTO(\Flagship\Enum\TargetingOperator::NOT_EQUALS, 'key2', 'value2') + ]; + $instance = $dto->setTargetings($targetings); + + $this->assertSame($targetings, $dto->getTargetings()); + $this->assertSame($dto, $instance); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_TARGETINGS => [ + [ + FlagshipField::FIELD_OPERATOR => 'EQUALS', + FlagshipField::FIELD_KEY => 'browser', + FlagshipField::FIELD_VALUE => 'chrome' + ], + [ + FlagshipField::FIELD_OPERATOR => 'NOT_EQUALS', + FlagshipField::FIELD_KEY => 'country', + FlagshipField::FIELD_VALUE => 'US' + ] + ] + ]; + + $dto = TargetingGroupDTO::fromArray($data); + + $this->assertIsArray($dto->getTargetings()); + $this->assertCount(2, $dto->getTargetings()); + $this->assertContainsOnlyInstancesOf(TargetingsDTO::class, $dto->getTargetings()); + } + + public function testFromArrayWithEmptyData() + { + $dto = TargetingGroupDTO::fromArray([]); + + $this->assertIsArray($dto->getTargetings()); + $this->assertEmpty($dto->getTargetings()); + } + + public function testFromArrayWithInvalidData() + { + $data = [ + FlagshipField::FIELD_TARGETINGS => 'not an array' + ]; + + $dto = TargetingGroupDTO::fromArray($data); + + $this->assertIsArray($dto->getTargetings()); + $this->assertEmpty($dto->getTargetings()); + } + + public function testToArray() + { + $targetings = [ + new TargetingsDTO(\Flagship\Enum\TargetingOperator::EQUALS, 'key1', 'value1'), + new TargetingsDTO(\Flagship\Enum\TargetingOperator::CONTAINS, 'key2', ['a', 'b']) + ]; + $dto = new TargetingGroupDTO($targetings); + + $array = $dto->toArray(); + + $this->assertArrayHasKey(FlagshipField::FIELD_TARGETINGS, $array); + $this->assertIsArray($array[FlagshipField::FIELD_TARGETINGS]); + $this->assertCount(2, $array[FlagshipField::FIELD_TARGETINGS]); + } + + public function testToArrayWithEmptyTargetings() + { + $dto = new TargetingGroupDTO([]); + $array = $dto->toArray(); + + $this->assertArrayHasKey(FlagshipField::FIELD_TARGETINGS, $array); + $this->assertIsArray($array[FlagshipField::FIELD_TARGETINGS]); + $this->assertEmpty($array[FlagshipField::FIELD_TARGETINGS]); + } +} diff --git a/tests/Model/TargetingsDTOTest.php b/tests/Model/TargetingsDTOTest.php new file mode 100644 index 00000000..fe80d855 --- /dev/null +++ b/tests/Model/TargetingsDTOTest.php @@ -0,0 +1,138 @@ +assertSame($operator, $dto->getOperator()); + $this->assertSame($key, $dto->getKey()); + $this->assertSame($value, $dto->getValue()); + } + + public function testGettersAndSetters() + { + $dto = new TargetingsDTO(TargetingOperator::EQUALS, 'key1', 'value1'); + + $instance1 = $dto->setOperator(TargetingOperator::NOT_EQUALS); + $this->assertSame(TargetingOperator::NOT_EQUALS, $dto->getOperator()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setKey('newKey'); + $this->assertSame('newKey', $dto->getKey()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setValue(['array', 'value']); + $this->assertSame(['array', 'value'], $dto->getValue()); + $this->assertSame($dto, $instance3); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_OPERATOR => 'EQUALS', + FlagshipField::FIELD_KEY => 'country', + FlagshipField::FIELD_VALUE => 'US' + ]; + + $dto = TargetingsDTO::fromArray($data); + + $this->assertSame(TargetingOperator::EQUALS, $dto->getOperator()); + $this->assertSame('country', $dto->getKey()); + $this->assertSame('US', $dto->getValue()); + } + + public function testFromArrayWithInvalidOperator() + { + $data = [ + FlagshipField::FIELD_OPERATOR => 'INVALID_OPERATOR', + FlagshipField::FIELD_KEY => 'key', + FlagshipField::FIELD_VALUE => 'value' + ]; + + $dto = TargetingsDTO::fromArray($data); + + $this->assertSame(TargetingOperator::EQUALS, $dto->getOperator()); + } + + public function testFromArrayWithMissingFields() + { + $dto = TargetingsDTO::fromArray([]); + + $this->assertSame(TargetingOperator::EQUALS, $dto->getOperator()); + $this->assertSame('', $dto->getKey()); + $this->assertNull($dto->getValue()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::FIELD_OPERATOR => 123, + FlagshipField::FIELD_KEY => [], + FlagshipField::FIELD_VALUE => 'any value' + ]; + + $dto = TargetingsDTO::fromArray($data); + + $this->assertSame(TargetingOperator::EQUALS, $dto->getOperator()); + $this->assertSame('', $dto->getKey()); + $this->assertSame('any value', $dto->getValue()); + } + + public function testToArray() + { + $dto = new TargetingsDTO(TargetingOperator::CONTAINS, 'tags', ['premium', 'vip']); + + $array = $dto->toArray(); + + $this->assertSame('CONTAINS', $array[FlagshipField::FIELD_OPERATOR]); + $this->assertSame('tags', $array[FlagshipField::FIELD_KEY]); + $this->assertSame(['premium', 'vip'], $array[FlagshipField::FIELD_VALUE]); + } + + public function testWithDifferentOperators() + { + $operators = [ + TargetingOperator::EQUALS, + TargetingOperator::NOT_EQUALS, + TargetingOperator::CONTAINS, + ]; + + foreach ($operators as $operator) { + $dto = new TargetingsDTO($operator, 'key', 'value'); + $this->assertSame($operator, $dto->getOperator()); + + $array = $dto->toArray(); + $this->assertSame($operator->value, $array[FlagshipField::FIELD_OPERATOR]); + } + } + + public function testWithDifferentValueTypes() + { + $values = [ + 'string value', + 123, + 45.67, + true, + ['array', 'value'], + null + ]; + + foreach ($values as $value) { + $dto = new TargetingsDTO(TargetingOperator::EQUALS, 'key', $value); + $this->assertSame($value, $dto->getValue()); + } + } +} diff --git a/tests/Model/TroubleshootingDTOTest.php b/tests/Model/TroubleshootingDTOTest.php new file mode 100644 index 00000000..0bb36415 --- /dev/null +++ b/tests/Model/TroubleshootingDTOTest.php @@ -0,0 +1,179 @@ +assertSame($startDate, $dto->getStartDate()); + $this->assertSame($endDate, $dto->getEndDate()); + $this->assertSame($traffic, $dto->getTraffic()); + $this->assertSame($timezone, $dto->getTimezone()); + } + + public function testGettersAndSetters() + { + $dto = new TroubleshootingDTO('2025-01-01', '2025-12-31', 50.0, 'UTC'); + + $instance1 = $dto->setStartDate('2025-02-01'); + $this->assertSame('2025-02-01', $dto->getStartDate()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setEndDate('2025-11-30'); + $this->assertSame('2025-11-30', $dto->getEndDate()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setTraffic(100.0); + $this->assertSame(100.0, $dto->getTraffic()); + $this->assertSame($dto, $instance3); + + $instance4 = $dto->setTimezone('America/New_York'); + $this->assertSame('America/New_York', $dto->getTimezone()); + $this->assertSame($dto, $instance4); + } + + public function testGetStartDateAsDateTime() + { + $dateString = '2025-06-15T10:30:00Z'; + $dto = new TroubleshootingDTO($dateString, '2025-12-31', 50.0, 'UTC'); + + $dateTime = $dto->getStartDateAsDateTime(); + + $this->assertInstanceOf(DateTime::class, $dateTime); + $this->assertSame('2025-06-15', $dateTime->format('Y-m-d')); + } + + public function testGetStartDateAsDateTimeWithInvalidDate() + { + $dto = new TroubleshootingDTO('invalid-date', '2025-12-31', 50.0, 'UTC'); + + $dateTime = $dto->getStartDateAsDateTime(); + + $this->assertNull($dateTime); + } + + public function testGetEndDateAsDateTime() + { + $dateString = '2025-12-31T23:59:59Z'; + $dto = new TroubleshootingDTO('2025-01-01', $dateString, 50.0, 'UTC'); + + $dateTime = $dto->getEndDateAsDateTime(); + + $this->assertInstanceOf(DateTime::class, $dateTime); + $this->assertSame('2025-12-31', $dateTime->format('Y-m-d')); + } + + public function testGetEndDateAsDateTimeWithInvalidDate() + { + $dto = new TroubleshootingDTO('2025-01-01', 'not-a-date', 50.0, 'UTC'); + + $dateTime = $dto->getEndDateAsDateTime(); + + $this->assertNull($dateTime); + } + + public function testFromArray() + { + $data = [ + FlagshipField::START_DATE => '2025-03-01', + FlagshipField::END_DATE => '2025-09-30', + FlagshipField::TRAFFIC => 85.5, + FlagshipField::TIMEZONE => 'Europe/Paris' + ]; + + $dto = TroubleshootingDTO::fromArray($data); + + $this->assertSame('2025-03-01', $dto->getStartDate()); + $this->assertSame('2025-09-30', $dto->getEndDate()); + $this->assertSame(85.5, $dto->getTraffic()); + $this->assertSame('Europe/Paris', $dto->getTimezone()); + } + + public function testFromArrayWithMissingFields() + { + $dto = TroubleshootingDTO::fromArray([]); + + $this->assertSame('', $dto->getStartDate()); + $this->assertSame('', $dto->getEndDate()); + $this->assertSame(0.0, $dto->getTraffic()); + $this->assertSame('', $dto->getTimezone()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::START_DATE => 123, + FlagshipField::END_DATE => [], + FlagshipField::TRAFFIC => 'not a number', + FlagshipField::TIMEZONE => null + ]; + + $dto = TroubleshootingDTO::fromArray($data); + + $this->assertSame('', $dto->getStartDate()); + $this->assertSame('', $dto->getEndDate()); + $this->assertSame(0.0, $dto->getTraffic()); + $this->assertSame('', $dto->getTimezone()); + } + + public function testFromArrayWithStringTraffic() + { + $data = [ + FlagshipField::START_DATE => '2025-01-01', + FlagshipField::END_DATE => '2025-12-31', + FlagshipField::TRAFFIC => '50', + FlagshipField::TIMEZONE => 'UTC' + ]; + + $dto = TroubleshootingDTO::fromArray($data); + $this->assertSame(50.0, $dto->getTraffic()); + } + + public function testToArray() + { + $dto = new TroubleshootingDTO('2025-01-01', '2025-12-31', 60.0, 'Asia/Tokyo'); + + $array = $dto->toArray(); + + $this->assertSame('2025-01-01', $array[FlagshipField::START_DATE]); + $this->assertSame('2025-12-31', $array[FlagshipField::END_DATE]); + $this->assertSame(60.0, $array[FlagshipField::TRAFFIC]); + $this->assertSame('Asia/Tokyo', $array[FlagshipField::TIMEZONE]); + } + + public function testToTroubleshootingData() + { + $dto = new TroubleshootingDTO('2025-01-01', '2025-12-31', 75.0, 'UTC'); + + $data = $dto->toTroubleshootingData(); + + $this->assertInstanceOf(TroubleshootingData::class, $data); + $this->assertInstanceOf(DateTime::class, $data->getStartDate()); + $this->assertInstanceOf(DateTime::class, $data->getEndDate()); + $this->assertSame(75.0, $data->getTraffic()); + $this->assertSame('UTC', $data->getTimezone()); + } + + public function testToTroubleshootingDataWithInvalidDates() + { + $dto = new TroubleshootingDTO('invalid', 'dates', 50.0, 'UTC'); + + $data = $dto->toTroubleshootingData(); + + $this->assertNull($data); + } +} diff --git a/tests/Model/TroubleshootingDataTest.php b/tests/Model/TroubleshootingDataTest.php new file mode 100644 index 00000000..4e7d8b8c --- /dev/null +++ b/tests/Model/TroubleshootingDataTest.php @@ -0,0 +1,84 @@ +setStartDate($startDate); + $this->assertSame($startDate, $data->getStartDate()); + $this->assertSame($data, $instance1); + + $endDate = new DateTime('2025-12-31'); + $instance2 = $data->setEndDate($endDate); + $this->assertSame($endDate, $data->getEndDate()); + $this->assertSame($data, $instance2); + + $instance3 = $data->setTraffic(75.5); + $this->assertSame(75.5, $data->getTraffic()); + $this->assertSame($data, $instance3); + + $instance4 = $data->setTimezone('UTC'); + $this->assertSame('UTC', $data->getTimezone()); + $this->assertSame($data, $instance4); + } + + public function testSetTrafficWithInt() + { + $data = new TroubleshootingData(); + $data->setTraffic(100); + + $this->assertSame(100, $data->getTraffic()); + } + + public function testSetTrafficWithFloat() + { + $data = new TroubleshootingData(); + $data->setTraffic(50.5); + + $this->assertSame(50.5, $data->getTraffic()); + } + + public function testChainingSetter() + { + $data = new TroubleshootingData(); + $startDate = new DateTime('2025-01-01'); + $endDate = new DateTime('2025-12-31'); + + $result = $data + ->setStartDate($startDate) + ->setEndDate($endDate) + ->setTraffic(80.0) + ->setTimezone('America/New_York'); + + $this->assertSame($data, $result); + $this->assertSame($startDate, $data->getStartDate()); + $this->assertSame($endDate, $data->getEndDate()); + $this->assertSame(80.0, $data->getTraffic()); + $this->assertSame('America/New_York', $data->getTimezone()); + } + + public function testDateTimeObjects() + { + $data = new TroubleshootingData(); + $startDate = new DateTime('2025-06-15 10:30:00'); + $endDate = new DateTime('2025-12-25 23:59:59'); + + $data->setStartDate($startDate); + $data->setEndDate($endDate); + + $this->assertSame('2025-06-15', $data->getStartDate()->format('Y-m-d')); + $this->assertSame('10:30:00', $data->getStartDate()->format('H:i:s')); + $this->assertSame('2025-12-25', $data->getEndDate()->format('Y-m-d')); + $this->assertSame('23:59:59', $data->getEndDate()->format('H:i:s')); + } +} diff --git a/tests/Model/VariationDTOTest.php b/tests/Model/VariationDTOTest.php new file mode 100644 index 00000000..6f294fb0 --- /dev/null +++ b/tests/Model/VariationDTOTest.php @@ -0,0 +1,115 @@ + 'value1']); + $dto = new VariationDTO('var123', $modifications); + + $this->assertSame('var123', $dto->getId()); + $this->assertSame($modifications, $dto->getModifications()); + } + + public function testGettersAndSetters() + { + $modifications = new ModificationsDTO('ab', []); + $dto = new VariationDTO('var1', $modifications); + + $instance1 = $dto->setId('var2'); + $this->assertSame('var2', $dto->getId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setName('Variation Name'); + $this->assertSame('Variation Name', $dto->getName()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setReference(true); + $this->assertTrue($dto->getReference()); + $this->assertSame($dto, $instance3); + + $newModifications = new ModificationsDTO('toggle', ['key' => 'value']); + $instance4 = $dto->setModifications($newModifications); + $this->assertSame($newModifications, $dto->getModifications()); + $this->assertSame($dto, $instance4); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_ID => 'var123', + FlagshipField::FIELD_NANE => 'Control', + FlagshipField::FIELD_REFERENCE => true, + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => ['feature1' => true] + ] + ]; + + $dto = VariationDTO::fromArray($data); + + $this->assertSame('var123', $dto->getId()); + $this->assertSame('Control', $dto->getName()); + $this->assertTrue($dto->getReference()); + $this->assertInstanceOf(ModificationsDTO::class, $dto->getModifications()); + } + + public function testFromArrayWithMissingFields() + { + $dto = VariationDTO::fromArray([]); + + $this->assertSame('', $dto->getId()); + $this->assertNull($dto->getName()); + $this->assertNull($dto->getReference()); + $this->assertInstanceOf(ModificationsDTO::class, $dto->getModifications()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::FIELD_ID => 123, + FlagshipField::FIELD_NANE => [], + FlagshipField::FIELD_REFERENCE => 'true', + FlagshipField::FIELD_MODIFICATIONS => 'not an array' + ]; + + $dto = VariationDTO::fromArray($data); + + $this->assertSame('', $dto->getId()); + $this->assertNull($dto->getName()); + $this->assertNull($dto->getReference()); + } + + public function testToArray() + { + $modifications = new ModificationsDTO('ab', ['key1' => 'value1']); + $dto = new VariationDTO('var123', $modifications); + $dto->setName('Variation A'); + $dto->setReference(false); + + $array = $dto->toArray(); + + $this->assertSame('var123', $array[FlagshipField::FIELD_ID]); + $this->assertSame('Variation A', $array[FlagshipField::FIELD_NANE]); + $this->assertFalse($array[FlagshipField::FIELD_REFERENCE]); + $this->assertIsArray($array[FlagshipField::FIELD_MODIFICATIONS]); + } + + public function testToArrayWithNullValues() + { + $modifications = new ModificationsDTO('ab', []); + $dto = new VariationDTO('var123', $modifications); + + $array = $dto->toArray(); + + $this->assertSame('var123', $array[FlagshipField::FIELD_ID]); + $this->assertArrayHasKey(FlagshipField::FIELD_MODIFICATIONS, $array); + } +} diff --git a/tests/Model/VariationGroupDTOTest.php b/tests/Model/VariationGroupDTOTest.php new file mode 100644 index 00000000..116f0ac6 --- /dev/null +++ b/tests/Model/VariationGroupDTOTest.php @@ -0,0 +1,135 @@ +assertSame('vg123', $dto->getId()); + $this->assertSame($targeting, $dto->getTargeting()); + $this->assertSame($variations, $dto->getVariations()); + } + + public function testGettersAndSetters() + { + $targeting = new TargetingDTO([]); + $dto = new VariationGroupDTO('vg1', $targeting, []); + + $instance1 = $dto->setId('vg2'); + $this->assertSame('vg2', $dto->getId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setName('VG Name'); + $this->assertSame('VG Name', $dto->getName()); + $this->assertSame($dto, $instance2); + + $newTargeting = new TargetingDTO([new TargetingGroupDTO([])]); + $instance3 = $dto->setTargeting($newTargeting); + $this->assertSame($newTargeting, $dto->getTargeting()); + $this->assertSame($dto, $instance3); + + $variations = [ + new BucketingVariationDTO('v1', new ModificationsDTO('ab', [])), + new BucketingVariationDTO('v2', new ModificationsDTO('ab', [])) + ]; + $instance4 = $dto->setVariations($variations); + $this->assertSame($variations, $dto->getVariations()); + $this->assertSame($dto, $instance4); + } + + public function testFromArray() + { + $data = [ + FlagshipField::FIELD_ID => 'vg123', + FlagshipField::FIELD_NANE => 'Group Name', + FlagshipField::FIELD_TARGETING => [ + FlagshipField::FIELD_TARGETING_GROUPS => [] + ], + FlagshipField::FIELD_VARIATIONS => [ + [ + FlagshipField::FIELD_ID => 'v1', + FlagshipField::FIELD_ALLOCATION => 50.0, + FlagshipField::FIELD_MODIFICATIONS => [ + FlagshipField::FIELD_CAMPAIGN_TYPE => 'ab', + FlagshipField::FIELD_VALUE => [] + ] + ] + ] + ]; + + $dto = VariationGroupDTO::fromArray($data); + + $this->assertSame('vg123', $dto->getId()); + $this->assertSame('Group Name', $dto->getName()); + $this->assertInstanceOf(TargetingDTO::class, $dto->getTargeting()); + $this->assertIsArray($dto->getVariations()); + $this->assertCount(1, $dto->getVariations()); + $this->assertContainsOnlyInstancesOf(BucketingVariationDTO::class, $dto->getVariations()); + } + + public function testFromArrayWithMissingFields() + { + $dto = VariationGroupDTO::fromArray([]); + + $this->assertSame('', $dto->getId()); + $this->assertNull($dto->getName()); + $this->assertInstanceOf(TargetingDTO::class, $dto->getTargeting()); + $this->assertIsArray($dto->getVariations()); + $this->assertEmpty($dto->getVariations()); + } + + public function testFromArrayWithInvalidTypes() + { + $data = [ + FlagshipField::FIELD_ID => 123, + FlagshipField::FIELD_NANE => [], + FlagshipField::FIELD_TARGETING => 'not an array', + FlagshipField::FIELD_VARIATIONS => 'not an array' + ]; + + $dto = VariationGroupDTO::fromArray($data); + + $this->assertSame('', $dto->getId()); + $this->assertNull($dto->getName()); + $this->assertInstanceOf(TargetingDTO::class, $dto->getTargeting()); + } + + public function testToArray() + { + $targeting = new TargetingDTO([]); + $variations = [ + new BucketingVariationDTO('v1', new ModificationsDTO('ab', [])), + new BucketingVariationDTO('v2', new ModificationsDTO('ab', [])) + ]; + $dto = new VariationGroupDTO('vg123', $targeting, $variations); + $dto->setName('My Group'); + + $array = $dto->toArray(); + + $this->assertSame('vg123', $array[FlagshipField::FIELD_ID]); + $this->assertSame('My Group', $array[FlagshipField::FIELD_NANE]); + $this->assertIsArray($array[FlagshipField::FIELD_TARGETING]); + $this->assertIsArray($array[FlagshipField::FIELD_VARIATIONS]); + $this->assertCount(2, $array[FlagshipField::FIELD_VARIATIONS]); + } + + public function testToArrayWithNullName() + { + $targeting = new TargetingDTO([]); + $dto = new VariationGroupDTO('vg123', $targeting, []); + + $array = $dto->toArray(); + + $this->assertArrayNotHasKey(FlagshipField::FIELD_NANE, $array); + } +} diff --git a/tests/Model/VisitorCacheDTOTest.php b/tests/Model/VisitorCacheDTOTest.php new file mode 100644 index 00000000..6a3e5af5 --- /dev/null +++ b/tests/Model/VisitorCacheDTOTest.php @@ -0,0 +1,111 @@ +assertSame(1, $dto->getVersion()); + $this->assertSame($data, $dto->getData()); + } + + public function testGettersAndSetters() + { + $data = new VisitorCacheDataDTO('visitor1', null); + $dto = new VisitorCacheDTO(1, $data); + + $instance1 = $dto->setVersion(2); + $this->assertSame(2, $dto->getVersion()); + $this->assertSame($dto, $instance1); + + $newData = new VisitorCacheDataDTO('visitor2', 'anon123'); + $instance2 = $dto->setData($newData); + $this->assertSame($newData, $dto->getData()); + $this->assertSame($dto, $instance2); + } + + public function testFromArray() + { + $array = [ + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => 'anon456', + StrategyAbstract::CONSENT => true + ] + ]; + + $dto = VisitorCacheDTO::fromArray($array); + + $this->assertSame(1, $dto->getVersion()); + $this->assertInstanceOf(VisitorCacheDataDTO::class, $dto->getData()); + $this->assertSame('visitor123', $dto->getData()->getVisitorId()); + } + + public function testFromArrayWithMissingVersion() + { + $array = [ + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => null + ] + ]; + + $dto = VisitorCacheDTO::fromArray($array); + + $this->assertSame(StrategyAbstract::CURRENT_VERSION, $dto->getVersion()); + } + + public function testFromArrayWithInvalidTypes() + { + $array = [ + StrategyAbstract::VERSION => 'not an int', + StrategyAbstract::DATA => 'not an array' + ]; + + $dto = VisitorCacheDTO::fromArray($array); + + $this->assertSame(StrategyAbstract::CURRENT_VERSION, $dto->getVersion()); + $this->assertInstanceOf(VisitorCacheDataDTO::class, $dto->getData()); + } + + public function testToArray() + { + $data = new VisitorCacheDataDTO('visitor123', 'anon456'); + $data->setConsent(true); + $dto = new VisitorCacheDTO(1, $data); + + $array = $dto->toArray(); + + $this->assertSame(1, $array[StrategyAbstract::VERSION]); + $this->assertIsArray($array[StrategyAbstract::DATA]); + $this->assertArrayHasKey(StrategyAbstract::VISITOR_ID, $array[StrategyAbstract::DATA]); + } + + public function testRoundTripConversion() + { + $originalArray = [ + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => 'anon456', + StrategyAbstract::CONSENT => true, + StrategyAbstract::CONTEXT => ['key' => 'value'] + ] + ]; + + $dto = VisitorCacheDTO::fromArray($originalArray); + $resultArray = $dto->toArray(); + + $this->assertSame($originalArray[StrategyAbstract::VERSION], $resultArray[StrategyAbstract::VERSION]); + } +} diff --git a/tests/Model/VisitorCacheDataDTOTest.php b/tests/Model/VisitorCacheDataDTOTest.php new file mode 100644 index 00000000..c161c4f4 --- /dev/null +++ b/tests/Model/VisitorCacheDataDTOTest.php @@ -0,0 +1,192 @@ +assertSame('visitor123', $dto->getVisitorId()); + $this->assertSame('anon456', $dto->getAnonymousId()); + } + + public function testConstructorWithNullAnonymousId() + { + $dto = new VisitorCacheDataDTO('visitor123', null); + + $this->assertSame('visitor123', $dto->getVisitorId()); + $this->assertNull($dto->getAnonymousId()); + } + + public function testGettersAndSetters() + { + $dto = new VisitorCacheDataDTO('visitor1', null); + + $instance1 = $dto->setVisitorId('visitor2'); + $this->assertSame('visitor2', $dto->getVisitorId()); + $this->assertSame($dto, $instance1); + + $instance2 = $dto->setAnonymousId('anon123'); + $this->assertSame('anon123', $dto->getAnonymousId()); + $this->assertSame($dto, $instance2); + + $instance3 = $dto->setConsent(true); + $this->assertTrue($dto->getConsent()); + $this->assertSame($dto, $instance3); + + $context = ['browser' => 'chrome', 'version' => '120']; + $instance4 = $dto->setContext($context); + $this->assertSame($context, $dto->getContext()); + $this->assertSame($dto, $instance4); + + $history = ['campaign1' => 'variation1']; + $instance5 = $dto->setAssignmentsHistory($history); + $this->assertSame($history, $dto->getAssignmentsHistory()); + $this->assertSame($dto, $instance5); + + $campaigns = [ + new CampaignCacheDTO('c1', 'vg1', 'v1', new ModificationsDTO('ab', [])) + ]; + $instance6 = $dto->setCampaigns($campaigns); + $this->assertSame($campaigns, $dto->getCampaigns()); + $this->assertSame($dto, $instance6); + } + + public function testFromArray() + { + $array = [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => 'anon456', + StrategyAbstract::CONSENT => true, + StrategyAbstract::CONTEXT => ['key1' => 'value1', 'key2' => 123], + StrategyAbstract::ASSIGNMENTS_HISTORY => ['c1' => 'v1'], + StrategyAbstract::CAMPAIGNS => [ + [ + StrategyAbstract::CAMPAIGN_ID => 'c1', + StrategyAbstract::VARIATION_GROUP_ID => 'vg1', + StrategyAbstract::VARIATION_ID => 'v1', + 'flags' => [ + 'type' => 'ab', + 'value' => [] + ] + ] + ] + ]; + + $dto = VisitorCacheDataDTO::fromArray($array); + + $this->assertSame('visitor123', $dto->getVisitorId()); + $this->assertSame('anon456', $dto->getAnonymousId()); + $this->assertTrue($dto->getConsent()); + $this->assertIsArray($dto->getContext()); + $this->assertIsArray($dto->getAssignmentsHistory()); + $this->assertIsArray($dto->getCampaigns()); + } + + public function testFromArrayWithMissingFields() + { + $dto = VisitorCacheDataDTO::fromArray([]); + + $this->assertSame('', $dto->getVisitorId()); + $this->assertNull($dto->getAnonymousId()); + $this->assertNull($dto->getConsent()); + $this->assertNull($dto->getContext()); + } + + public function testFromArrayWithInvalidTypes() + { + $array = [ + StrategyAbstract::VISITOR_ID => 123, + StrategyAbstract::ANONYMOUS_ID => [], + StrategyAbstract::CONSENT => 'true', + StrategyAbstract::CONTEXT => 'not an array', + StrategyAbstract::ASSIGNMENTS_HISTORY => 'invalid', + StrategyAbstract::CAMPAIGNS => 'invalid' + ]; + + $dto = VisitorCacheDataDTO::fromArray($array); + + $this->assertSame('', $dto->getVisitorId()); + $this->assertNull($dto->getAnonymousId()); + $this->assertNull($dto->getConsent()); + } + + public function testFromArrayFiltersInvalidContextValues() + { + $array = [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => null, + StrategyAbstract::CONTEXT => [ + 'valid1' => 'string', + 'valid2' => 123, + 'invalid1' => ['array'], + 'invalid2' => new \stdClass(), + ] + ]; + + $dto = VisitorCacheDataDTO::fromArray($array); + $context = $dto->getContext(); + + $this->assertArrayHasKey('valid1', $context); + $this->assertArrayHasKey('valid2', $context); + $this->assertArrayNotHasKey('invalid1', $context); + $this->assertArrayNotHasKey('invalid2', $context); + } + + public function testToArray() + { + $dto = new VisitorCacheDataDTO('visitor123', 'anon456'); + $dto->setConsent(true); + $dto->setContext(['key' => 'value']); + $dto->setAssignmentsHistory(['c1' => 'v1']); + $dto->setCampaigns([ + new CampaignCacheDTO('c1', 'vg1', 'v1', new ModificationsDTO('ab', [])) + ]); + + $array = $dto->toArray(); + + $this->assertSame('visitor123', $array[StrategyAbstract::VISITOR_ID]); + $this->assertSame('anon456', $array[StrategyAbstract::ANONYMOUS_ID]); + $this->assertTrue($array[StrategyAbstract::CONSENT]); + $this->assertIsArray($array[StrategyAbstract::CONTEXT]); + $this->assertIsArray($array[StrategyAbstract::ASSIGNMENTS_HISTORY]); + $this->assertIsArray($array[StrategyAbstract::CAMPAIGNS]); + } + + public function testToArrayWithNullValues() + { + $dto = new VisitorCacheDataDTO('visitor123', 'anon456'); + + $array = $dto->toArray(); + + $this->assertArrayHasKey(StrategyAbstract::VISITOR_ID, $array); + $this->assertArrayHasKey(StrategyAbstract::ANONYMOUS_ID, $array); + $this->assertArrayNotHasKey(StrategyAbstract::CONSENT, $array); + $this->assertArrayNotHasKey(StrategyAbstract::CONTEXT, $array); + } + + public function testRoundTripConversion() + { + $originalArray = [ + StrategyAbstract::VISITOR_ID => 'visitor123', + StrategyAbstract::ANONYMOUS_ID => 'anon456', + StrategyAbstract::CONSENT => true, + StrategyAbstract::CONTEXT => ['key' => 'value'], + StrategyAbstract::ASSIGNMENTS_HISTORY => ['c1' => 'v1'] + ]; + + $dto = VisitorCacheDataDTO::fromArray($originalArray); + $resultArray = $dto->toArray(); + + $this->assertSame($originalArray[StrategyAbstract::VISITOR_ID], $resultArray[StrategyAbstract::VISITOR_ID]); + $this->assertSame($originalArray[StrategyAbstract::ANONYMOUS_ID], $resultArray[StrategyAbstract::ANONYMOUS_ID]); + $this->assertSame($originalArray[StrategyAbstract::CONSENT], $resultArray[StrategyAbstract::CONSENT]); + } +} diff --git a/tests/Traits/CommonLogManagerTraitTest.php b/tests/Traits/CommonLogManagerTraitTest.php index 2113a236..8eed3fad 100644 --- a/tests/Traits/CommonLogManagerTraitTest.php +++ b/tests/Traits/CommonLogManagerTraitTest.php @@ -2,14 +2,239 @@ namespace Flagship\Traits; +use DateTime; +use Flagship\BaseTestCase; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; +use phpmock\phpunit\PHPMock; -class CommonLogManagerTraitTest extends TestCase +class ConcreteLogManager { - public function testGetDateTime() + use CommonLogManagerTrait; +} + +class StringableObject +{ + public function __toString(): string + { + return 'stringable'; + } +} + +class CommonLogManagerTraitTest extends BaseTestCase +{ + use PHPMock; + + private ConcreteLogManager $logManager; + + + protected function setUp(): void { - $logManagerTraitMock = $this->getMockForTrait("Flagship\Traits\CommonLogManagerTrait"); - $value = $logManagerTraitMock->getDateTime(); - $this->assertMatchesRegularExpression("/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+/", $value); + $this->logManager = new ConcreteLogManager(); + $this->capturedLogs = []; + } + + + + public function testGetDateTimeReturnsCorrectFormat(): void + { + $dateTime = $this->logManager->getDateTime(); + + // Verify format: Y-m-d H:i:s.u (e.g., 2024-01-15 14:30:45.123456) + $this->assertMatchesRegularExpression( + '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}$/', + $dateTime + ); + } + + public function testCustomLogWithStringLevel(): void + { + $this->mockErrorLog(); + + $level = 'INFO'; + $message = 'Test message'; + + $this->logManager->customLog($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[.*\] \[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[INFO\] Test message/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithNumericLevel(): void + { + $this->mockErrorLog(); + + $level = 200; + $message = 'Numeric level test'; + + $this->logManager->customLog($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[.*\] \[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[200\] Numeric level test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithStringableObject(): void + { + $this->mockErrorLog(); + + $level = new StringableObject(); + $message = 'Stringable object test'; + + $this->logManager->customLog($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[.*\] \[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[stringable\] Stringable object test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithNonStringableObject(): void + { + $this->mockErrorLog(); + + $level = new \stdClass(); + $message = 'Non-stringable object test'; + + $this->logManager->customLog($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[.*\] \[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[object\] Non-stringable object test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithEmptyContext(): void + { + $this->mockErrorLog(); + + $level = 'DEBUG'; + $message = 'Empty context test'; + + $this->logManager->customLog($level, $message, []); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[.*\] \[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[DEBUG\] Empty context test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithScalarContext(): void + { + $this->mockErrorLog(); + + $level = 'INFO'; + $message = 'Context test'; + $context = ['key1' => 'value1', 'key2' => 42, 'key3' => true]; + + $this->logManager->customLog($level, $message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[key1 => value1, key2 => 42, key3 => 1\] Context test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithArrayInContext(): void + { + $this->mockErrorLog(); + + $level = 'WARNING'; + $message = 'Array context test'; + $context = ['data' => ['nested' => 'value']]; + + $this->logManager->customLog($level, $message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[data => \{"nested":"value"\}\] Array context test/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithStringableObjectInContext(): void + { + $this->mockErrorLog(); + + $level = 'ERROR'; + $message = 'Stringable in context'; + $context = ['obj' => new StringableObject()]; + + $this->logManager->customLog($level, $message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[obj => stringable\] Stringable in context/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogWithMixedContext(): void + { + $this->mockErrorLog(); + + $level = 'INFO'; + $message = 'Mixed context'; + $context = [ + 'string' => 'test', + 'number' => 123, + 'array' => [1, 2, 3], + 'stringable' => new StringableObject() + ]; + + $this->logManager->customLog($level, $message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[string => test, number => 123, array => \[1,2,3\], stringable => stringable\] Mixed context/', + $this->capturedLogs[0] + ); + } + + public function testCustomLogFormatStructure(): void + { + $this->mockErrorLog(); + + $level = 'INFO'; + $message = 'Structure test'; + + $this->logManager->customLog($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $output = $this->capturedLogs[0]; + + // Verify the log contains all required components + $this->assertStringContainsString('[' . FlagshipConstant::FLAGSHIP_SDK . ']', $output); + $this->assertStringContainsString('[INFO]', $output); + $this->assertStringContainsString('Structure test', $output); + + // Verify date format is present + $this->assertMatchesRegularExpression('/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\]/', $output); + } + + public function testCustomLogWithSingleContextItem(): void + { + $this->mockErrorLog(); + + $level = 'DEBUG'; + $message = 'Single item'; + $context = ['key' => 'value']; + + $this->logManager->customLog($level, $message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[key => value\] Single item/', + $this->capturedLogs[0] + ); } } diff --git a/tests/Traits/LogTraitTest.php b/tests/Traits/LogTraitTest.php index 977dbdc6..1c0ecf55 100644 --- a/tests/Traits/LogTraitTest.php +++ b/tests/Traits/LogTraitTest.php @@ -2,311 +2,592 @@ namespace Flagship\Traits; -use Flagship\Config\DecisionApiConfig; -use Flagship\Enum\FlagshipConstant; use Flagship\Enum\LogLevel; -use Flagship\Utils\Utils; +use Psr\Log\LoggerInterface; use PHPUnit\Framework\TestCase; +use Flagship\Config\FlagshipConfig; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\DecisionApiConfig; +use PHPUnit\Framework\MockObject\MockObject; -class LogTraitTest extends TestCase +// Test helper classes +class ConcreteLogTraitClass { - /** - * @throws \ReflectionException - */ - public function testLoginError() - { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + use LogTrait; - $message = "hello"; - $context = ['exception' => 'hello Exception']; + public function publicFormatArgs(array $args = []): array + { + return $this->formatArgs($args); + } - $logManagerMock->expects($this->exactly(3))->method('error')->with( + public function publicGetLogFormat( + ?string $message, + string $url, + ?array $requestBody, + ?array $headers, + float|int|null $duration, + $responseHeader = null, + $responseBody = null, + $responseStatus = null + ): array { + return $this->getLogFormat( $message, - $context + $url, + $requestBody, + $headers, + $duration, + $responseHeader, + $responseBody, + $responseStatus ); + } - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); + // Expose protected log methods as public for testing + public function publicLogDebugSprintf(?FlagshipConfig $config, string $tag, string $message, array $args = []): void + { + $this->logDebugSprintf($config, $tag, $message, $args); + } - $logError = Utils::getMethod($logTraitMock, "logError"); - $logError->invokeArgs($logTraitMock, [$config, $message, $context]); + public function publicLogDebug(FlagshipConfig $config, string $message, array $context = []): void + { + $this->logDebug($config, $message, $context); + } - $config->setLogLevel(LogLevel::CRITICAL); - $logError->invokeArgs($logTraitMock, [$config, $message, $context]); + public function publicLogErrorSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void + { + $this->logErrorSprintf($config, $tag, $message, $args); + } - $config->setLogLevel(LogLevel::ERROR); - $logError->invokeArgs($logTraitMock, [$config, $message, $context]); + public function publicLogError(?FlagshipConfig $config, string $message, array $context = []): void + { + $this->logError($config, $message, $context); + } + public function publicLogInfoSprintf(FlagshipConfig $config, string $tag, string $message, array $args = []): void + { + $this->logInfoSprintf($config, $tag, $message, $args); + } - $config->setLogLevel(LogLevel::WARNING); - $logError->invokeArgs($logTraitMock, [$config, $message, $context]); + public function publicLogInfo(FlagshipConfig $config, string $message, array $context = []): void + { + $this->logInfo($config, $message, $context); + } - $config = new DecisionApiConfig(); - $config->setLogLevel(LogLevel::INFO); - $logError->invokeArgs($logTraitMock, [$config, $message, $context]); + public function publicLogWarningSprintf(?FlagshipConfig $config, string $tag, string $message, array $args = []): void + { + $this->logWarningSprintf($config, $tag, $message, $args); } - /** - * @throws \ReflectionException - */ - public function testLogErrorSprintf() + public function publicLogWarning(FlagshipConfig $config, string $message, array $context = []): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $this->logWarning($config, $message, $context); + } +} - $message = "hello"; - $tag = __FUNCTION__; - $context = [FlagshipConstant::TAG => $tag]; +class StringableTestObject +{ + public function __toString(): string + { + return 'stringable-object'; + } +} - $logManagerMock->expects($this->exactly(2))->method('error')->with( - $message, - $context - ); +class JsonSerializableTestObject implements \JsonSerializable +{ + public function jsonSerialize(): mixed + { + return ['key' => 'value', 'number' => 42]; + } +} - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); +class PlainTestObject +{ + public string $property = 'value'; +} - $logError = Utils::getMethod($logTraitMock, "logErrorSprintf"); +class LogTraitTest extends TestCase +{ + private ConcreteLogTraitClass $logTrait; + private FlagshipConfig $config; + private LoggerInterface|MockObject $logger; - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + protected function setUp(): void + { + $this->logTrait = new ConcreteLogTraitClass(); + $this->logger = $this->getMockForAbstractClass( + LoggerInterface::class, + [], + '', + false, + true, true, + ['debug', 'info', 'warning', 'error'] + ); + $this->config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::ALL); + } - $config->setLogLevel(LogLevel::CRITICAL); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + // formatArgs() tests + public function testFormatArgsWithEmptyArray(): void + { + $result = $this->logTrait->publicFormatArgs([]); + $this->assertIsArray($result); + $this->assertEmpty($result); + } - $config->setLogLevel(LogLevel::ERROR); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + public function testFormatArgsWithScalarValues(): void + { + $args = ['string', 42, 3.14, true, false, null]; + $result = $this->logTrait->publicFormatArgs($args); + + $this->assertEquals('string', $result[0]); + $this->assertEquals(42, $result[1]); + $this->assertEquals(3.14, $result[2]); + $this->assertTrue($result[3]); + $this->assertFalse($result[4]); + $this->assertNull($result[5]); } + public function testFormatArgsWithArray(): void + { + $args = [['key' => 'value', 'number' => 123]]; + $result = $this->logTrait->publicFormatArgs($args); + + $this->assertIsString($result[0]); + $decoded = json_decode($result[0], true); + $this->assertEquals('value', $decoded['key']); + $this->assertEquals(123, $decoded['number']); + } - /** - * @throws \ReflectionException - */ - public function testLoginInfo() + public function testFormatArgsWithStringableObject(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $args = [new StringableTestObject()]; + $result = $this->logTrait->publicFormatArgs($args); - $message = "hello"; - $context = ['exception' => 'hello Exception']; + $this->assertEquals('stringable-object', $result[0]); + } - $logManagerMock->expects($this->exactly(3))->method('info')->with( - $message, - $context - ); + public function testFormatArgsWithJsonSerializable(): void + { + $args = [new JsonSerializableTestObject()]; + $result = $this->logTrait->publicFormatArgs($args); - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); + $this->assertIsString($result[0]); + $decoded = json_decode($result[0], true); + $this->assertEquals('value', $decoded['key']); + $this->assertEquals(42, $decoded['number']); + } - $logInfo = Utils::getMethod($logTraitMock, "logInfo"); - $logInfo->invokeArgs($logTraitMock, [$config, $message, $context]); + public function testFormatArgsWithPlainObject(): void + { + $args = [new PlainTestObject()]; + $result = $this->logTrait->publicFormatArgs($args); - $config->setLogLevel(LogLevel::DEBUG); - $logInfo->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->assertEquals(PlainTestObject::class, $result[0]); + } - $config->setLogLevel(LogLevel::INFO); - $logInfo->invokeArgs($logTraitMock, [$config, $message, $context]); + public function testFormatArgsWithResource(): void + { + $resource = fopen('php://memory', 'r'); + $args = [$resource]; + $result = $this->logTrait->publicFormatArgs($args); - $config->setLogLevel(LogLevel::NOTICE); - $logInfo->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->assertStringContainsString('[Resource:', $result[0]); + $this->assertStringContainsString('stream', $result[0]); - $config = new DecisionApiConfig(); - $config->setLogLevel(LogLevel::INFO); - $logInfo->invokeArgs($logTraitMock, [$config, $message, $context]); + fclose($resource); } - /** - * @throws \ReflectionException - */ - public function testLogInfoSprintf() + public function testFormatArgsWithMixedTypes(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $args = [ + 'text', + 123, + ['nested' => 'array'], + new StringableTestObject(), + true, + null + ]; + $result = $this->logTrait->publicFormatArgs($args); + + $this->assertCount(6, $result); + $this->assertEquals('text', $result[0]); + $this->assertEquals(123, $result[1]); + $this->assertIsString($result[2]); + $this->assertEquals('stringable-object', $result[3]); + $this->assertTrue($result[4]); + $this->assertNull($result[5]); + } - $message = "hello"; - $tag = __FUNCTION__; - $context = [FlagshipConstant::TAG => $tag]; + public function testFormatArgsWithNestedArrays(): void + { + $args = [ + [ + 'level1' => [ + 'level2' => [ + 'level3' => 'deep value' + ] + ] + ] + ]; + $result = $this->logTrait->publicFormatArgs($args); + + $decoded = json_decode($result[0], true); + $this->assertEquals('deep value', $decoded['level1']['level2']['level3']); + } - $logManagerMock->expects($this->exactly(2))->method('info')->with( - $message, - $context + // logDebugSprintf() tests + public function testLogDebugSprintfCallsLoggerWhenLevelIsDebug(): void + { + $this->logger->expects($this->once()) + ->method('debug') + ->with( + $this->equalTo('Test message: value1, 42'), + $this->equalTo([FlagshipConstant::TAG => 'TEST_TAG']) + ); + + $this->logTrait->publicLogDebugSprintf( + $this->config, + 'TEST_TAG', + 'Test message: %s, %d', + ['value1', 42] ); + } - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); + public function testLogDebugSprintfDoesNotLogWhenLevelIsTooHigh(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::INFO); - $logError = Utils::getMethod($logTraitMock, "logInfoSprintf"); + $this->logger->expects($this->never())->method('debug'); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + $this->logTrait->publicLogDebugSprintf($config, 'TAG', 'Message', []); + } - $config->setLogLevel(LogLevel::CRITICAL); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + public function testLogDebugSprintfDoesNotLogWhenLogManagerIsNull(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogLevel(LogLevel::DEBUG); - $config->setLogLevel(LogLevel::INFO); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + // Should not throw exception + $this->logTrait->publicLogDebugSprintf($config, 'TAG', 'Message', []); + $this->assertTrue(true); } - /** - * @throws \ReflectionException - */ - public function testWarning() + public function testLogDebugSprintfWithNullConfig(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); - - $message = "hello"; - $context = ['exception' => 'hello Exception']; + // Should not throw exception + $this->logTrait->publicLogDebugSprintf(null, 'TAG', 'Message', []); + $this->assertTrue(true); + } - $logManagerMock->expects($this->exactly(3))->method('warning')->with( - $message, - $context - ); + // logDebug() tests + public function testLogDebugCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('debug') + ->with( + $this->equalTo('Debug message'), + $this->equalTo(['context' => 'value']) + ); + + $this->logTrait->publicLogDebug($this->config, 'Debug message', ['context' => 'value']); + } - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); + public function testLogDebugWithEmptyContext(): void + { + $this->logger->expects($this->once()) + ->method('debug') + ->with($this->equalTo('Message'), $this->equalTo([])); - $logWarning = Utils::getMethod($logTraitMock, "logWarning"); - $logWarning->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->logTrait->publicLogDebug($this->config, 'Message'); + } - $config->setLogLevel(LogLevel::DEBUG); - $logWarning->invokeArgs($logTraitMock, [$config, $message, $context]); + public function testLogDebugDoesNotLogWhenLevelIsTooHigh(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::INFO); - $config->setLogLevel(LogLevel::WARNING); - $logWarning->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->logger->expects($this->never())->method('debug'); - $config->setLogLevel(LogLevel::CRITICAL); - $logWarning->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->logTrait->publicLogDebug($config, 'Message', []); + } - $config = new DecisionApiConfig(); - $config->setLogLevel(LogLevel::WARNING); - $logWarning->invokeArgs($logTraitMock, [$config, $message, $context]); + // logErrorSprintf() tests + public function testLogErrorSprintfCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('error') + ->with( + $this->equalTo('Error: 404'), + $this->equalTo([FlagshipConstant::TAG => 'ERROR_TAG']) + ); + + $this->logTrait->publicLogErrorSprintf($this->config, 'ERROR_TAG', 'Error: %d', [404]); } - /** - * @throws \ReflectionException - */ - public function testWarningSprintf() + public function testLogErrorSprintfDoesNotLogWhenLevelIsTooHigh(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::NONE); - $message = "hello"; - $tag = __FUNCTION__; - $context = [FlagshipConstant::TAG => $tag]; + $this->logger->expects($this->never())->method('error'); - $logManagerMock->expects($this->exactly(2))->method('warning')->with( - $message, - $context - ); + $this->logTrait->publicLogErrorSprintf($config, 'TAG', 'Message', []); + } - $config = new DecisionApiConfig(); - $config->setLogManager($logManagerMock); + // logError() tests + public function testLogErrorCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('error') + ->with( + $this->equalTo('Error occurred'), + $this->equalTo(['code' => 500]) + ); + + $this->logTrait->publicLogError($this->config, 'Error occurred', ['code' => 500]); + } - $logWarning = Utils::getMethod($logTraitMock, "logWarningSprintf"); + public function testLogErrorWithNullConfig(): void + { + // Should not throw exception + $this->logTrait->publicLogError(null, 'Message', []); + $this->assertTrue(true); + } - $logWarning->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + // logInfoSprintf() tests + public function testLogInfoSprintfCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('info') + ->with( + $this->equalTo('Info: user123'), + $this->equalTo([FlagshipConstant::TAG => 'INFO_TAG']) + ); + + $this->logTrait->publicLogInfoSprintf($this->config, 'INFO_TAG', 'Info: %s', ['user123']); + } - $config->setLogLevel(LogLevel::CRITICAL); - $logWarning->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + public function testLogInfoSprintfDoesNotLogWhenLevelIsTooHigh(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::WARNING); + + $this->logger->expects($this->never())->method('info'); - $config->setLogLevel(LogLevel::WARNING); - $logWarning->invokeArgs($logTraitMock, [$config, $tag, $message, []]); + $this->logTrait->publicLogInfoSprintf($config, 'TAG', 'Message', []); } - /** - * @throws \ReflectionException - */ - public function testLogDebug() + // logInfo() tests + public function testLogInfoCallsLogger(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $this->logger->expects($this->once()) + ->method('info') + ->with( + $this->equalTo('Information'), + $this->equalTo(['data' => 'test']) + ); + + $this->logTrait->publicLogInfo($this->config, 'Information', ['data' => 'test']); + } - $message = "hello"; - $context = ['exception' => 'hello Exception']; + public function testLogInfoDoesNotLogWhenLevelIsTooHigh(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::ERROR); - $logManagerMock->expects($this->exactly(2))->method('debug')->with( - $message, - $context + $this->logger->expects($this->never())->method('info'); + + $this->logTrait->publicLogInfo($config, 'Message', []); + } + + // logWarningSprintf() tests + public function testLogWarningSprintfCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('warning') + ->with( + $this->equalTo('Warning: timeout 30s'), + $this->equalTo([FlagshipConstant::TAG => 'WARN_TAG']) + ); + + $this->logTrait->publicLogWarningSprintf( + $this->config, + 'WARN_TAG', + 'Warning: timeout %ds', + [30] ); + } - $config = new DecisionApiConfig(); - $config->setLogLevel(LogLevel::DEBUG); - $config->setLogManager($logManagerMock); + public function testLogWarningSprintfWithNullConfig(): void + { + // Should not throw exception + $this->logTrait->publicLogWarningSprintf(null, 'TAG', 'Message', []); + $this->assertTrue(true); + } - $logDebug = Utils::getMethod($logTraitMock, "logDebug"); - $logDebug->invokeArgs($logTraitMock, [$config, $message, $context]); + // logWarning() tests + public function testLogWarningCallsLogger(): void + { + $this->logger->expects($this->once()) + ->method('warning') + ->with( + $this->equalTo('Warning message'), + $this->equalTo(['type' => 'timeout']) + ); + + $this->logTrait->publicLogWarning($this->config, 'Warning message', ['type' => 'timeout']); + } - $config->setLogLevel(LogLevel::DEBUG); - $logDebug->invokeArgs($logTraitMock, [$config, $message, $context]); + public function testLogWarningDoesNotLogWhenLevelIsTooHigh(): void + { + $config = DecisionApiConfig::decisionApi() + ->setLogManager($this->logger) + ->setLogLevel(LogLevel::ERROR); - $config->setLogLevel(LogLevel::INFO); - $logDebug->invokeArgs($logTraitMock, [$config, $message, $context]); + $this->logger->expects($this->never())->method('warning'); + + $this->logTrait->publicLogWarning($config, 'Message', []); } - /** - * @throws \ReflectionException - */ - public function testLogDebugSprintf() + // getLogFormat() tests + public function testGetLogFormatWithAllParameters(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $logManagerMock = $this->getMockForAbstractClass('Psr\Log\LoggerInterface'); + $result = $this->logTrait->publicGetLogFormat( + 'Test message', + 'https://api.example.com', + ['key' => 'value'], + ['Authorization' => 'Bearer token'], + 123.45, + ['Content-Type' => 'application/json'], + ['result' => 'success'], + 200 + ); - $message = "hello %s %s"; - $tag = __FUNCTION__; - $args = [ - "there", - ["key" => "value"], - ]; - $context = [FlagshipConstant::TAG => $tag]; - - $logArgs = [ - $args[0], - json_encode($args[1]), - ]; - - $logManagerMock->expects($this->exactly(2))->method('debug')->with( - vsprintf($message, $logArgs), - $context + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_MESSAGE, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_URL, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_REQUEST_BODY, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_REQUEST_HEADERS, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_DURATION, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_RESPONSE_BODY, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_RESPONSE_STATUS, $result); + + $this->assertEquals('Test message', $result[FlagshipConstant::LOG_FORMAT_MESSAGE]); + $this->assertEquals('https://api.example.com', $result[FlagshipConstant::LOG_FORMAT_URL]); + $this->assertEquals(123.45, $result[FlagshipConstant::LOG_FORMAT_DURATION]); + $this->assertEquals(200, $result[FlagshipConstant::LOG_FORMAT_RESPONSE_STATUS]); + } + + public function testGetLogFormatWithMinimalParameters(): void + { + $result = $this->logTrait->publicGetLogFormat( + null, + 'https://api.example.com', + null, + null, + null + ); + + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_URL, $result); + $this->assertArrayNotHasKey(FlagshipConstant::LOG_FORMAT_MESSAGE, $result); + $this->assertArrayNotHasKey(FlagshipConstant::LOG_FORMAT_REQUEST_BODY, $result); + $this->assertArrayNotHasKey(FlagshipConstant::LOG_FORMAT_DURATION, $result); + } + + public function testGetLogFormatWithEmptyMessage(): void + { + $result = $this->logTrait->publicGetLogFormat( + '', + 'https://api.example.com', + null, + null, + null ); - $config = new DecisionApiConfig(); - $config->setLogLevel(LogLevel::DEBUG); - $config->setLogManager($logManagerMock); + $this->assertArrayNotHasKey(FlagshipConstant::LOG_FORMAT_MESSAGE, $result); + } - $logError = Utils::getMethod($logTraitMock, "logDebugSprintf"); + public function testGetLogFormatWithZeroDuration(): void + { + $result = $this->logTrait->publicGetLogFormat( + null, + 'https://api.example.com', + null, + null, + 0 + ); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, $args]); + $this->assertArrayNotHasKey(FlagshipConstant::LOG_FORMAT_DURATION, $result); + } - $config->setLogLevel(LogLevel::CRITICAL); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, $args]); + public function testGetLogFormatWithEmptyArrays(): void + { + $result = $this->logTrait->publicGetLogFormat( + 'Message', + 'https://api.example.com', + [], + [], + null + ); - $config->setLogLevel(LogLevel::DEBUG); - $logError->invokeArgs($logTraitMock, [$config, $tag, $message, $args]); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_REQUEST_BODY, $result); + $this->assertArrayHasKey(FlagshipConstant::LOG_FORMAT_REQUEST_HEADERS, $result); + $this->assertEmpty($result[FlagshipConstant::LOG_FORMAT_REQUEST_BODY]); + $this->assertEmpty($result[FlagshipConstant::LOG_FORMAT_REQUEST_HEADERS]); } - /** - * @throws \ReflectionException - */ - public function testGetLogFormat() + // Integration tests + public function testCompleteLoggingWorkflow(): void { - $logTraitMock = $this->getMockForTrait('Flagship\Traits\LogTrait'); - $getLogFormat = Utils::getMethod($logTraitMock, "getLogFormat"); + $this->logger->expects($this->exactly(4)) + ->method($this->anything()); + $this->logTrait->publicLogDebug($this->config, 'Debug message'); + $this->logTrait->publicLogInfo($this->config, 'Info message'); + $this->logTrait->publicLogWarning($this->config, 'Warning message'); + $this->logTrait->publicLogError($this->config, 'Error message'); + } - $message = "message"; - $url = "http://localhost"; - $requestBody = ["key" => "value"]; - $headers = ["key" => "value"]; - $duration = 300; - $value = $getLogFormat->invokeArgs($logTraitMock, [$message, $url, $requestBody, $headers, $duration]); - $expectedValue = [ - FlagshipConstant::LOG_FORMAT_MESSAGE => $message, - FlagshipConstant::LOG_FORMAT_URL => $url, - FlagshipConstant::LOG_FORMAT_REQUEST_BODY => $requestBody, - FlagshipConstant::LOG_FORMAT_REQUEST_HEADERS => $headers, - FlagshipConstant::LOG_FORMAT_DURATION => $duration, - ]; + public function testSprintfMethodsFormatCorrectly(): void + { + $this->logger->expects($this->once()) + ->method('info') + ->with( + $this->equalTo('User user123 performed action with status 200'), + $this->anything() + ); + + $this->logTrait->publicLogInfoSprintf( + $this->config, + 'TAG', + 'User %s performed %s with status %d', + ['user123', 'action', 200] + ); + } - $this->assertSame($expectedValue, $value); + public function testFormatArgsWithComplexObjectGraph(): void + { + $complex = [ + 'string' => 'value', + 'number' => 42, + 'nested' => [ + 'object' => new StringableTestObject(), + 'array' => [1, 2, 3] + ], + 'json' => new JsonSerializableTestObject() + ]; + + $result = $this->logTrait->publicFormatArgs([$complex]); + $this->assertIsString($result[0]); + + $decoded = json_decode($result[0], true); + $this->assertEquals('value', $decoded['string']); + $this->assertEquals(42, $decoded['number']); } } diff --git a/tests/Traits/Round.php b/tests/Traits/Round.php deleted file mode 100644 index 0f945a7c..00000000 --- a/tests/Traits/Round.php +++ /dev/null @@ -1,17 +0,0 @@ -getMockForTrait( - 'Flagship\Traits\ValidatorTrait', - [], - "", - false, - true, - true - ); - $isKeyValid = Utils::getMethod($validatorTraitMock, "isKeyValid"); - // Key is empty - $this->assertFalse($isKeyValid->invokeArgs($validatorTraitMock, [''])); - // Key is null - $this->assertFalse($isKeyValid->invokeArgs($validatorTraitMock, [null])); - // Key is not string - $this->assertFalse($isKeyValid->invokeArgs($validatorTraitMock, [44])); - $this->assertFalse($isKeyValid->invokeArgs($validatorTraitMock, [[]])); - - //Key is valid - $this->assertTrue($isKeyValid->invokeArgs($validatorTraitMock, ['validKey'])); - } - - - /** - * @throws ReflectionException - */ - public function testIsKeyValid() - { - $validatorTraitMock = $this->getMockForTrait( - 'Flagship\Traits\ValidatorTrait', - [], - "", - false, - true, - true - ); - - $isValueValid = Utils::getMethod($validatorTraitMock, "isValueValid"); - // Value is empty - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [''])); - - // Value is null - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [null])); - - //Value is not valid - $this->assertFalse($isValueValid->invokeArgs($validatorTraitMock, [[]])); - - //Test value is numeric - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [14])); - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [14.5])); - - //Test value is string - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, ['abc'])); - - //Test value is boolean - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [true])); - - //Test value is boolean - $this->assertTrue($isValueValid->invokeArgs($validatorTraitMock, [false])); - } /** * @throws ReflectionException @@ -190,43 +126,4 @@ public function testIsJsonObject() $this->assertTrue($isJsonObject->invokeArgs($validatorTraitMock, ["{}"])); $this->assertFalse($isJsonObject->invokeArgs($validatorTraitMock, ["[]"])); } - - /** - * @throws ReflectionException - */ - public function testIsNumeric() - { - //Mock logManger - $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', - [], - "", - true, - true, - true, - ['error'] - ); - - $validatorTraitMock = $this->getMockForTrait( - 'Flagship\Traits\ValidatorTrait', - [], - "", - false, - true, - true - ); - $itemName = "test"; - - $isNumeric = Utils::getMethod($validatorTraitMock, "isNumeric"); - $config = new BucketingConfig("http://127.0.0.1:3000"); - $config->setLogManager($logManagerStub); - $this->assertTrue($isNumeric->invokeArgs($validatorTraitMock, [1, $itemName, $config])); - - $sdk = FlagshipConstant::FLAGSHIP_SDK; - $logManagerStub->expects($this->once())->method('error')->with( - sprintf(FlagshipConstant::TYPE_ERROR, $itemName, 'numeric') - ); - - $this->assertFalse($isNumeric->invokeArgs($validatorTraitMock, ["abc", $itemName, $config])); - } } diff --git a/tests/Utils/ContainerExceptionTest.php b/tests/Utils/ContainerExceptionTest.php new file mode 100644 index 00000000..70889058 --- /dev/null +++ b/tests/Utils/ContainerExceptionTest.php @@ -0,0 +1,30 @@ +assertEquals('Unable to resolve class "stdClass"', $exception->getMessage()); + } + + public function testCircularDependency(): void + { + $chain = [\stdClass::class, \DateTime::class, \Exception::class]; + $exception = ContainerException::circularDependency($chain); + $this->assertEquals( + 'Circular dependency detected: stdClass -> DateTime -> Exception', + $exception->getMessage() + ); + } + + public function testNotInstantiable(): void + { + $exception = ContainerException::notInstantiable(\stdClass::class); + $this->assertEquals('Class "stdClass" is not instantiable', $exception->getMessage()); + } +} diff --git a/tests/Utils/ContainerTest.php b/tests/Utils/ContainerTest.php index 6dafd7d5..fc851c3e 100644 --- a/tests/Utils/ContainerTest.php +++ b/tests/Utils/ContainerTest.php @@ -2,153 +2,443 @@ namespace Flagship\Utils; -use Exception; -use Flagship\Config\DecisionApiConfig; -use Flagship\Config\FlagshipConfig; -use Flagship\Decision\ApiManager; -use Flagship\Decision\DecisionManagerAbstract; -use Flagship\Flag\FSFlagMetadata; use PHPUnit\Framework\TestCase; -use ReflectionException; +use stdClass; + +// Test helper classes +interface TestInterface {} + +class ConcreteTestClass implements TestInterface +{ + public function __construct(public string $value = 'default') {} +} + +class SimpleClass { + public ?string $custom = null; +} + +class ClassWithDependency +{ + public function __construct(public SimpleClass $dependency) {} +} + +class ClassWithMultipleDependencies +{ + public function __construct( + public SimpleClass $dep1, + public ConcreteTestClass $dep2 + ) {} +} + +class ClassWithDefaultValue +{ + public function __construct(public string $name = 'test', public int $value = 42) {} +} + +class ClassWithMixedParameters +{ + public function __construct( + public string $required, + public SimpleClass $dependency, + public string $optional = 'default' + ) {} +} + +class ClassWithNullableParameter +{ + public function __construct(public ?SimpleClass $dependency) {} +} + +class ClassWithNullableAndDefaultParameter +{ + public function __construct(public ?SimpleClass $dependency = null) {} +} + +class ClassWithBuiltInTypes +{ + public function __construct( + public string $str, + public int $num , + public bool $flag, + public float $decimal, + public array $arr + ) {} +} + +abstract class AbstractTestClass {} + +class ClassWithUnionType +{ + public function __construct(public SimpleClass|ConcreteTestClass|null $dependency, public int|bool $value, public ?bool $value2) {} +} + +class ClassWithNoTypeHint +{ + public function __construct(public $untyped) {} +} + +class ClassWithUntypedButDefault +{ + public function __construct(public $value = 'default') {} +} class ContainerTest extends TestCase { - /** - * @throws ReflectionException - * @throws Exception - */ - public function testGet() + private Container $container; + + protected function setUp(): void + { + $this->container = new Container(); + } + + // Test bind() method + public function testBindCreatesBinding(): void { - $container = new Container(); - $alias = HttpClientInterface::class; - $className = HttpClient::class; + $this->container->bind(TestInterface::class, ConcreteTestClass::class); - $container->bind($alias, $className); + $bindings = $this->container->getBindings(); + $this->assertArrayHasKey(TestInterface::class, $bindings); + $this->assertEquals(ConcreteTestClass::class, $bindings[TestInterface::class]); + } - //Test constructor without argument - $instanceAlias1 = $container->get($alias); - $this->assertInstanceOf($alias, $instanceAlias1); + public function testBindReturnsSelf(): void + { + $result = $this->container->bind(TestInterface::class, ConcreteTestClass::class); + $this->assertSame($this->container, $result); + } - //Test constructor with default argument - $container->bind( - FlagshipConfig::class, - DecisionApiConfig::class - ); + public function testBindThrowsExceptionForDuplicateAlias(): void + { + $this->container->bind(TestInterface::class, ConcreteTestClass::class); - $instanceAlias2 = $container->get(FlagshipConfig::class); - $this->assertInstanceOf(FlagshipConfig::class, $instanceAlias2); + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('Alias "Flagship\Utils\TestInterface" is already bound'); - $alias = DecisionManagerAbstract::class; - $className = ApiManager::class; - $container->bind($alias, $className); + $this->container->bind(TestInterface::class, SimpleClass::class); + } - $instanceAlias = $container->get($alias); - $this->assertInstanceOf($alias, $instanceAlias); + public function testBindThrowsExceptionForNonExistentClass(): void + { + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('Class "NonExistentClass" does not exist'); - //Test without constructor - $instanceAlias = $container->get('stdClass'); - $this->assertInstanceOf('stdClass', $instanceAlias); + $this->container->bind(TestInterface::class, 'NonExistentClass'); + } - $this->expectException('ReflectionException'); + // Test factory() method + public function testFactoryRegistersCallback(): void + { + $this->container->factory(SimpleClass::class, function ($container) { + return new SimpleClass(); + }); - $container->get('NotExist'); + $instance = $this->container->get(SimpleClass::class); + $this->assertInstanceOf(SimpleClass::class, $instance); } - /** - * @throws ReflectionException - */ - public function testGetWithDefaultArgs() + public function testFactoryReturnsSelf(): void { - $className = DecisionApiConfig::class; - $container = new Container(); - $instanceAlias = $container->get($className); - $this->assertInstanceOf($className, $instanceAlias); - $this->assertNull($instanceAlias->getEnvId()); - $this->assertNull($instanceAlias->getApiKey()); + $result = $this->container->factory(SimpleClass::class, fn() => new SimpleClass()); + $this->assertSame($this->container, $result); } - /** - * @throws ReflectionException - */ - public function testGetWithoutDefaultArgs() + public function testFactoryReceivesContainerAndArgs(): void { - $className = FSFlagMetadata::class; - $container = new Container(); - $instanceAlias = $container->get($className); - $this->assertInstanceOf($className, $instanceAlias); + $this->container->factory(ConcreteTestClass::class, function ($container, $args) { + $this->assertInstanceOf(Container::class, $container); + $value = $args[0] ?? 'factory'; + return new ConcreteTestClass($value); + }); - $this->assertEmpty($instanceAlias->getCampaignId()); + $instance = $this->container->get(ConcreteTestClass::class, ['custom']); + $this->assertEquals('custom', $instance->value); } - /** - * @throws ReflectionException - */ - public function testGetNotInstantiable() + public function testFactoryThrowsExceptionWhenNotReturningObject(): void { - $container = new Container(); - $alias = 'Flagship\Decision\DecisionManagerAbstract'; + $this->container->factory(SimpleClass::class, fn() => 'not an object'); - $this->expectException('Exception'); + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('Factory for "Flagship\Utils\SimpleClass" must return an object'); - $container->get($alias); + $this->container->get(SimpleClass::class); } - /** - * @throws ReflectionException - */ - public function testGetWithCustomArgument() + // Test instance() method + public function testInstanceRegistersSingleton(): void { - //Test constructor with custom argument - $container = new Container(); - $className = 'Flagship\Config\DecisionApiConfig'; - $envId = 'envId'; - $apiKey = 'apiKey'; - $instanceAlias = $container->get($className, [$envId, $apiKey]); - $this->assertInstanceOf($className, $instanceAlias); - $this->assertSame($envId, $instanceAlias->getEnvId()); - $this->assertSame($apiKey, $instanceAlias->getApiKey()); + $instance = new SimpleClass(); + $this->container->instance(SimpleClass::class, $instance); + + $retrieved = $this->container->get(SimpleClass::class); + $this->assertSame($instance, $retrieved); } - /** - * @throws ReflectionException - */ - public function testFactory() + public function testInstanceReturnsSelf(): void { - $className = 'Flagship\Config\DecisionApiConfig'; - $container = new Container(); - $envId = 'envId'; - $apiKey = 'apiKey'; - $instanceAlias = $container->get($className, [$envId, $apiKey], true); - $this->assertInstanceOf($className, $instanceAlias); - $this->assertSame($envId, $instanceAlias->getEnvId()); - $this->assertSame($apiKey, $instanceAlias->getApiKey()); + $result = $this->container->instance(SimpleClass::class, new SimpleClass()); + $this->assertSame($this->container, $result); } - /** - * @throws Exception - */ - public function testBind() + // Test has() method + public function testHasReturnsTrueForRegisteredInstance(): void { - $container = new Container(); - $alias1 = 'Flagship\Utils\HttpClientInterface'; - $className1 = 'Flagship\Utils\HttpClient'; + $this->container->instance(SimpleClass::class, new SimpleClass()); + $this->assertTrue($this->container->has(SimpleClass::class)); + } - $container->bind($alias1, $className1); - $binding = Utils::getProperty($container, 'bindings')->getValue($container); - $this->assertCount(1, $binding); - $this->assertSame($binding[$alias1], $className1); + public function testHasReturnsTrueForBinding(): void + { + $this->container->bind(TestInterface::class, ConcreteTestClass::class); + $this->assertTrue($this->container->has(TestInterface::class)); + } - $alias2 = 'Flagship\Decision\DecisionManagerAbstract'; - $className2 = 'Flagship\Utils\ApiManager'; + public function testHasReturnsTrueForFactory(): void + { + $this->container->factory(SimpleClass::class, fn() => new SimpleClass()); + $this->assertTrue($this->container->has(SimpleClass::class)); + } - $container->bind($alias2, $className2); + public function testHasReturnsTrueForExistingClass(): void + { + $this->assertTrue($this->container->has(SimpleClass::class)); + } - $binding = Utils::getProperty($container, 'bindings')->getValue($container); - $this->assertCount(2, $binding); - $this->assertSame($binding[$alias2], $className2); + public function testHasReturnsFalseForNonExistent(): void + { + $this->assertFalse($this->container->has('NonExistentClass')); + } - $this->expectException('Exception'); + // Test get() method + public function testGetCreatesSimpleInstance(): void + { + $instance = $this->container->get(SimpleClass::class); + $this->assertInstanceOf(SimpleClass::class, $instance); + } + + public function testGetReturnsSameInstanceForSingleton(): void + { + $first = $this->container->get(SimpleClass::class); + $second = $this->container->get(SimpleClass::class); + $this->assertSame($first, $second); + } + + public function testGetWithFactoryModeCreatesNewInstance(): void + { + $first = $this->container->get(SimpleClass::class, null, true); + $second = $this->container->get(SimpleClass::class, null, true); + $this->assertNotSame($first, $second); + } + + public function testGetResolvesBinding(): void + { + $this->container->bind(TestInterface::class, ConcreteTestClass::class); + $instance = $this->container->get(TestInterface::class); + $this->assertInstanceOf(ConcreteTestClass::class, $instance); + } + + public function testGetWithConstructorArgs(): void + { + $instance = $this->container->get(ConcreteTestClass::class, ['custom_value']); + $this->assertEquals('custom_value', $instance->value); + } + + public function testGetAutoResolvesDependencies(): void + { + $instance = $this->container->get(ClassWithDependency::class); + $this->assertInstanceOf(ClassWithDependency::class, $instance); + $this->assertInstanceOf(SimpleClass::class, $instance->dependency); + } + + public function testGetResolvesMultipleDependencies(): void + { + $instance = $this->container->get(ClassWithMultipleDependencies::class); + $this->assertInstanceOf(SimpleClass::class, $instance->dep1); + $this->assertInstanceOf(ConcreteTestClass::class, $instance->dep2); + } + + public function testGetUsesDefaultValues(): void + { + $instance = $this->container->get(ClassWithDefaultValue::class); + $this->assertEquals('test', $instance->name); + $this->assertEquals(42, $instance->value); + } + + public function testGetHandlesNullableParameters(): void + { + $instance = $this->container->get(ClassWithNullableParameter::class); + $this->assertInstanceOf(SimpleClass::class, $instance->dependency); + } + + public function testGetHandlesNullableWithDefaultParameters(): void + { + $instance = $this->container->get(ClassWithNullableAndDefaultParameter::class); + $this->assertNull($instance->dependency); + } + + public function testGetResolvesBuiltInTypes(): void + { + $instance = $this->container->get(ClassWithBuiltInTypes::class); + $this->assertSame('', $instance->str); + $this->assertSame(0, $instance->num); + $this->assertFalse($instance->flag); + $this->assertSame(0.0, $instance->decimal); + $this->assertSame([], $instance->arr); + } + + public function testGetThrowsExceptionForAbstractClass(): void + { + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('is not instantiable'); + + $this->container->get(AbstractTestClass::class); + } + + public function testGetThrowsExceptionForNonExistentClass(): void + { + $this->expectException(ContainerException::class); + $this->expectExceptionMessage('Class "NonExistentClass" does not exist'); + + $this->container->get('NonExistentClass'); + } + + public function testGetResolvesUnionType(): void + { + $instance = $this->container->get(ClassWithUnionType::class); + $this->assertInstanceOf(ClassWithUnionType::class, $instance); + $this->assertInstanceOf(SimpleClass::class, $instance->dependency); + $this->assertIsInt($instance->value); + $this->assertNull($instance->value2); + } + + public function testGetForUntypedParameterWithoutDefault(): void + { + $instance = $this->container->get(ClassWithNoTypeHint::class); + $this->assertInstanceOf(ClassWithNoTypeHint::class, $instance); + } + + public function testGetHandlesUntypedParameterWithDefault(): void + { + + $instance = $this->container->get(ClassWithUntypedButDefault::class); + + $this->assertEquals('default', $instance->value); + } + + // Test make() method + public function testMakeCreatesNewInstance(): void + { + $first = $this->container->make(SimpleClass::class); + $second = $this->container->make(SimpleClass::class); + $this->assertNotSame($first, $second); + } + + public function testMakeWithArgs(): void + { + $instance = $this->container->make(ConcreteTestClass::class, ['made']); + $this->assertEquals('made', $instance->value); + } + + // Test flush() method + public function testFlushClearsAllInstances(): void + { + $first = $this->container->get(SimpleClass::class); + $this->container->flush(); + $second = $this->container->get(SimpleClass::class); + + $this->assertNotSame($first, $second); + } + + public function testFlushReturnsSelf(): void + { + $result = $this->container->flush(); + $this->assertSame($this->container, $result); + } + + // Test forget() method + public function testForgetClearsSpecificInstance(): void + { + $first = $this->container->get(SimpleClass::class); + $this->container->forget(SimpleClass::class); + $second = $this->container->get(SimpleClass::class); + + $this->assertNotSame($first, $second); + } + + public function testForgetReturnsSelf(): void + { + $this->container->get(SimpleClass::class); + $result = $this->container->forget(SimpleClass::class); + $this->assertSame($this->container, $result); + } + + public function testForgetDoesNotAffectOtherInstances(): void + { + $simple = $this->container->get(SimpleClass::class); + $concrete = $this->container->get(ConcreteTestClass::class); + + $this->container->forget(SimpleClass::class); + + $newSimple = $this->container->get(SimpleClass::class); + $sameConcrete = $this->container->get(ConcreteTestClass::class); + + $this->assertNotSame($simple, $newSimple); + $this->assertSame($concrete, $sameConcrete); + } + + // Test getBindings() method + public function testGetBindingsReturnsEmptyArrayInitially(): void + { + $bindings = $this->container->getBindings(); + $this->assertIsArray($bindings); + $this->assertEmpty($bindings); + } + + public function testGetBindingsReturnsAllBindings(): void + { + $this->container->bind(TestInterface::class, ConcreteTestClass::class); + $this->container->bind('AnotherAlias', SimpleClass::class); + + $bindings = $this->container->getBindings(); + $this->assertCount(2, $bindings); + $this->assertEquals(ConcreteTestClass::class, $bindings[TestInterface::class]); + $this->assertEquals(SimpleClass::class, $bindings['AnotherAlias']); + } + + // Integration tests + public function testChainedMethodCalls(): void + { + $instance = new SimpleClass(); + + $this->container + ->bind(TestInterface::class, ConcreteTestClass::class) + ->factory('test', fn() => new stdClass()) + ->instance(SimpleClass::class, $instance); + + $this->assertSame($instance, $this->container->get(SimpleClass::class)); + $this->assertInstanceOf(ConcreteTestClass::class, $this->container->get(TestInterface::class)); + } + + public function testComplexDependencyResolution(): void + { + $this->container->bind(TestInterface::class, ConcreteTestClass::class); + + $instance = $this->container->get(ClassWithMultipleDependencies::class); + + $this->assertInstanceOf(SimpleClass::class, $instance->dep1); + $this->assertInstanceOf(ConcreteTestClass::class, $instance->dep2); + } + + public function testFactoryTakesPrecedenceOverAutoResolution(): void + { + $this->container->factory(SimpleClass::class, function () { + $instance = new SimpleClass(); + $instance->custom = 'factory-created'; + return $instance; + }); - $container->bind($alias2, $className2); + $instance = $this->container->get(SimpleClass::class); + $this->assertTrue(property_exists($instance, 'custom')); + $this->assertEquals('factory-created', $instance->custom); } } diff --git a/tests/Utils/FlagshipLogManagerTest.php b/tests/Utils/FlagshipLogManagerTest.php index 82c685b3..0ba74077 100644 --- a/tests/Utils/FlagshipLogManagerTest.php +++ b/tests/Utils/FlagshipLogManagerTest.php @@ -2,153 +2,275 @@ namespace Flagship\Utils; -require_once __DIR__ . "/../Traits/ErrorLog.php"; - -use Flagship\Enum\FlagshipConstant; -use PHPUnit\Framework\TestCase; use Psr\Log\LogLevel; -use Flagship\Traits\ErrorLog; +use phpmock\phpunit\PHPMock; +use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; + +class StringableTestObject +{ + public function __toString(): string + { + return 'stringable-test'; + } +} class FlagshipLogManagerTest extends TestCase { - public function contextDataProvider(): array + use PHPMock; + + private FlagshipLogManager $logManager; + private array $capturedLogs = []; + + protected function setUp(): void + { + $this->logManager = new FlagshipLogManager(); + $this->capturedLogs = []; + } + + private function mockErrorLog(): void + { + $errorLog = $this->getFunctionMock('Flagship\Traits', 'error_log'); + $errorLog->expects($this->once()) + ->willReturnCallback(function ($message) { + $this->capturedLogs[] = $message; + return true; + }); + } + + public function testEmergencyLogsWithCorrectLevel(): void { - return - [ - 'flagshipSdk' => FlagshipConstant::FLAGSHIP_SDK, - 'context' => [ - 'process' => 'testError', - 'context2' => 'value 2', - ], - 'contextString' => '[process => testError, context2 => value 2]', - ]; + $this->mockErrorLog(); + + $message = 'Emergency message'; + $this->logManager->emergency($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::EMERGENCY, '/') . '\].*Emergency message/', + $this->capturedLogs[0] + ); } - public function getMessageError($formatDate, $level, $message, $tag): string + public function testAlertLogsWithCorrectLevel(): void { - $flagshipSdk = FlagshipConstant::FLAGSHIP_SDK; - return "[$formatDate] [$flagshipSdk] [$level] {$tag} $message"; + $this->mockErrorLog(); + + $message = 'Alert message'; + $this->logManager->alert($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::ALERT, '/') . '\].*Alert message/', + $this->capturedLogs[0] + ); } - public function testError() + public function testCriticalLogsWithCorrectLevel(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); + $this->mockErrorLog(); - $logManager->error($message, $data['context']); - $level = LogLevel::ERROR; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $message = 'Critical message'; + $this->logManager->critical($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::CRITICAL, '/') . '\].*Critical message/', + $this->capturedLogs[0] + ); } + public function testErrorLogsWithCorrectLevel(): void + { + $this->mockErrorLog(); + + $message = 'Error message'; + $this->logManager->error($message); - public function testInfo() + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::ERROR, '/') . '\].*Error message/', + $this->capturedLogs[0] + ); + } + + public function testWarningLogsWithCorrectLevel(): void + { + $this->mockErrorLog(); + + $message = 'Warning message'; + $this->logManager->warning($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::WARNING, '/') . '\].*Warning message/', + $this->capturedLogs[0] + ); + } + + public function testNoticeLogsWithCorrectLevel(): void { - $data = $this->contextDataProvider(); - $message = 'Test info'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->info($message, $data['context']); - $level = LogLevel::INFO; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $message = 'Notice message'; + $this->logManager->notice($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::NOTICE, '/') . '\].*Notice message/', + $this->capturedLogs[0] + ); } - public function testAlert() + public function testInfoLogsWithCorrectLevel(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->alert($message, $data['context']); - $level = LogLevel::ALERT; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $message = 'Info message'; + $this->logManager->info($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::INFO, '/') . '\].*Info message/', + $this->capturedLogs[0] + ); } - public function testEmergency() + public function testDebugLogsWithCorrectLevel(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->emergency($message, $data['context']); - $level = LogLevel::EMERGENCY; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $message = 'Debug message'; + $this->logManager->debug($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::DEBUG, '/') . '\].*Debug message/', + $this->capturedLogs[0] + ); } + public function testLogWithStringableMessage(): void + { + $this->mockErrorLog(); - public function testLog() + $message = new StringableTestObject(); + $this->logManager->info($message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertStringContainsString('stringable-test', $this->capturedLogs[0]); + } + + public function testLogWithContext(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $level = LogLevel::EMERGENCY; - $logManager->log($level, $message, $data['context']); - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $message = 'Test with context'; + $context = ['user_id' => 123, 'action' => 'login']; + + $this->logManager->info($message, $context); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[user_id => 123, action => login\].*Test with context/', + $this->capturedLogs[0] + ); } + public function testLogWithComplexContext(): void + { + $this->mockErrorLog(); + + $message = 'Complex context'; + $context = [ + 'string' => 'value', + 'number' => 42, + 'bool' => true, + 'array' => ['nested' => 'data'], + 'stringable' => new StringableTestObject() + ]; - public function testWarning() + $this->logManager->debug($message, $context); + + $this->assertCount(1, $this->capturedLogs); + $log = $this->capturedLogs[0]; + $this->assertStringContainsString('string => value', $log); + $this->assertStringContainsString('number => 42', $log); + $this->assertStringContainsString('bool => 1', $log); + $this->assertStringContainsString('stringable => stringable-test', $log); + } + + public function testLogMethodDirectly(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->warning($message, $data['context']); + $this->mockErrorLog(); + $level = LogLevel::WARNING; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $message = 'Direct log call'; + + $this->logManager->log($level, $message); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(LogLevel::WARNING, '/') . '\].*Direct log call/', + $this->capturedLogs[0] + ); } - public function testCritical() + public function testLogFormatIncludesFlagshipSDK(): void { - $data = $this->contextDataProvider(); - $message = 'Test Error'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->critical($message, $data['context']); - $level = LogLevel::CRITICAL; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $this->logManager->info('Test message'); + + $this->assertCount(1, $this->capturedLogs); + $this->assertStringContainsString('[' . FlagshipConstant::FLAGSHIP_SDK . ']', $this->capturedLogs[0]); } - public function testNotice() + public function testLogFormatIncludesTimestamp(): void { - $data = $this->contextDataProvider(); - $message = 'Test Notice'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->notice($message, $data['context']); - $level = LogLevel::NOTICE; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $this->logManager->info('Test message'); + + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}\]/', + $this->capturedLogs[0] + ); } + public function testLogWithEmptyMessage(): void + { + $this->mockErrorLog(); + + $this->logManager->info(''); - public function testDebug() + $this->assertCount(1, $this->capturedLogs); + $this->assertMatchesRegularExpression( + '/\[' . preg_quote(FlagshipConstant::FLAGSHIP_SDK, '/') . '\] \[info\]/', + $this->capturedLogs[0] + ); + } + + public function testLogWithEmptyContext(): void + { + $this->mockErrorLog(); + + $this->logManager->warning('Warning', []); + + $this->assertCount(1, $this->capturedLogs); + $this->assertStringContainsString('Warning', $this->capturedLogs[0]); + $this->assertStringNotContainsString('[]', $this->capturedLogs[0]); + } + + public function testLogWithCustomLevel(): void { - $data = $this->contextDataProvider(); - $message = 'Test Debug'; - $logManager = $this->getMockBuilder(FlagshipLogManager8::class)->onlyMethods(["getDateTime"])->getMock(); - $formatDate = "2023-02-15 11:08:10.455"; - $logManager->expects($this->once())->method("getDateTime")->willReturn($formatDate); - $logManager->debug($message, $data['context']); - $level = LogLevel::DEBUG; - $messageError = $this->getMessageError($formatDate, $level, $message, $data['contextString']); - $this->assertSame($messageError, ErrorLog::$error); + $this->mockErrorLog(); + + $this->logManager->log('CUSTOM_LEVEL', 'Custom message'); + + $this->assertCount(1, $this->capturedLogs); + $this->assertStringContainsString('[CUSTOM_LEVEL]', $this->capturedLogs[0]); + $this->assertStringContainsString('Custom message', $this->capturedLogs[0]); } } diff --git a/tests/Utils/HttpClientTest.php b/tests/Utils/HttpClientTest.php index 98e51805..69203dbf 100644 --- a/tests/Utils/HttpClientTest.php +++ b/tests/Utils/HttpClientTest.php @@ -2,106 +2,396 @@ namespace Flagship\Utils; -require_once __dir__ . "/../Assets/Curl.php"; - -use Flagship\Assets\Curl; -use Flagship\Enum\FlagshipConstant; +use Exception; +use ErrorException; +use phpmock\phpunit\PHPMock; +use Flagship\Model\HttpResponse; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipConstant; class HttpClientTest extends TestCase { - public function testConstructorFailed() + use PHPMock; + + private HttpClient $client; + + protected function setUp(): void {} + + public function testConstructorThrowsExceptionWhenCurlNotLoaded(): void { - Curl::$extension = false; - $this->expectException('Exception', FlagshipConstant::CURL_LIBRARY_IS_NOT_LOADED); - $client = new HttpClient(); + $extensionLoaded = $this->getFunctionMock('Flagship\Utils', 'extension_loaded'); + $extensionLoaded->expects($this->once()) + ->with('curl') + ->willReturn(false); + + $this->expectException(ErrorException::class); + $this->expectExceptionMessage(FlagshipConstant::CURL_LIBRARY_IS_NOT_LOADED); + + new HttpClient(); } - public function testSetOption() + public function testConstructorSucceedsWhenCurlIsLoaded(): void { + $extensionLoaded = $this->getFunctionMock('Flagship\Utils', 'extension_loaded'); + $extensionLoaded->expects($this->once()) + ->with('curl') + ->willReturn(true); + $client = new HttpClient(); - $optionKey = CURLOPT_TIMEOUT; - $optionValue = 2000; - $client->setOpt($optionKey, $optionValue); - $this->assertSame($client->getOptions()[$optionKey], $optionValue); + $this->assertInstanceOf(HttpClient::class, $client); } - public function testSetHeaders() + public function testCurlInit(): void { - $client = new HttpClient(); - $this->assertCount(0, $client->getHeaders()); - $client->setHeaders(['x-sdk-client' => 'PHP']); - $this->assertCount(1, $client->getHeaders()); - $client->setHeaders(['x-sdk-version' => 'v1']); - $this->assertCount(2, $client->getHeaders()); + $this->mockExtensionLoaded(); + + // Only mock curl_init, let it create real handle + $this->client = new HttpClient(); + + $curlInit = $this->getFunctionMock('Flagship\Utils', 'curl_init'); + + $curlInit->expects($this->any()) + ->willReturnCallback(function () { + return \curl_init(); + }); + + // Use reflection to access private method + $reflection = new \ReflectionClass(HttpClient::class); + $method = $reflection->getMethod('curlInit'); + $method->setAccessible(true); + + // Invoke the private method + $method->invoke($this->client); + + // If no exception is thrown, the test passes + $this->assertTrue(true); + } + + public function testCurlInitThrowsExceptionOnFailure(): void + { + $this->mockExtensionLoaded(); + + $curlInit = $this->getFunctionMock('Flagship\Utils', 'curl_init'); + $curlInit->expects($this->any()) + ->willReturn(false); + + $this->client = new HttpClient(); + + $reflection = new \ReflectionClass(HttpClient::class); + $method = $reflection->getMethod('curlInit'); + $method->setAccessible(true); + + $this->expectException(ErrorException::class); + $this->expectExceptionMessage('Failed to initialize cURL'); + + $method->invoke($this->client); + } + + // setOpt() tests + public function testSetOptInitializesCurlAndSetsOption(): void + { + $this->mockExtensionLoaded(); + + $curlSetopt = $this->getFunctionMock('Flagship\Utils', 'curl_setopt'); + $curlSetopt->expects($this->any()) + ->willReturn(true); + + $this->client = new HttpClient(); + $result = $this->client->setOpt(CURLOPT_VERBOSE, true); + + $this->assertTrue($result); + $options = $this->client->getOptions(); + $this->assertArrayHasKey(CURLOPT_VERBOSE, $options); } - public function testBuildUrl() + public function testSetOptReturnsTrueOnSuccess(): void { + $this->setupClientWithMockedCurl(); + + $result = $this->client->setOpt(CURLOPT_FOLLOWLOCATION, true); + $this->assertTrue($result); + } + + public function testSetOptStoresMultipleOptions(): void + { + $this->setupClientWithMockedCurl(); + + $this->client->setOpt(CURLOPT_VERBOSE, true); + $this->client->setOpt(CURLOPT_MAXREDIRS, 10); + $this->client->setOpt(CURLOPT_FOLLOWLOCATION, false); + + $options = $this->client->getOptions(); + $this->assertTrue($options[CURLOPT_VERBOSE]); + $this->assertEquals(10, $options[CURLOPT_MAXREDIRS]); + $this->assertFalse($options[CURLOPT_FOLLOWLOCATION]); + } + + // getOptions() tests + public function testGetOptionsReturnsEmptyArrayInitially(): void + { + $this->mockExtensionLoaded(); $client = new HttpClient(); - $buildMethod = Utils::getMethod($client, 'buildUrl'); - $visitorid = 'visitorId'; - $visitoKey = "visitor"; - $versionSDkKey = 'sdk'; - $versionSDkValue = '1'; - $urlOriginal = "https://localhost"; - $urlExpected = $urlOriginal . '?' . $visitoKey . '=' . $visitorid . '&' . - $versionSDkKey . '=' . $versionSDkValue; - $urlBuild = $buildMethod->invokeArgs( - $client, - [ - $urlOriginal, - [ - $visitoKey => $visitorid, - $versionSDkKey => $versionSDkValue, - ], - ] + + $options = $client->getOptions(); + $this->assertIsArray($options); + $this->assertEmpty($options); + } + + // setHeaders() tests + public function testSetHeadersStoresSingleHeader(): void + { + $this->mockExtensionLoaded(); + $this->client = new HttpClient(); + + $this->client->setHeaders(['Content-Type' => 'application/json']); + + $headers = $this->client->getHeaders(); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertEquals('application/json', $headers['Content-Type']); + } + + public function testSetHeadersStoresMultipleHeaders(): void + { + $this->mockExtensionLoaded(); + $this->client = new HttpClient(); + + $headers = [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer token123', + 'X-Custom-Header' => 'custom-value' + ]; + + $this->client->setHeaders($headers); + + $storedHeaders = $this->client->getHeaders(); + $this->assertCount(3, $storedHeaders); + $this->assertEquals('application/json', $storedHeaders['Content-Type']); + $this->assertEquals('Bearer token123', $storedHeaders['Authorization']); + } + + + public function testSetHeadersMergesWithExistingHeaders(): void + { + $this->mockExtensionLoaded(); + $this->client = new HttpClient(); + + $this->client->setHeaders(['Content-Type' => 'application/json']); + $this->client->setHeaders(['Authorization' => 'Bearer token']); + + $headers = $this->client->getHeaders(); + $this->assertCount(2, $headers); + } + + public function testSetHeadersOverwritesExistingHeader(): void + { + $this->mockExtensionLoaded(); + $this->client = new HttpClient(); + + $this->client->setHeaders(['Content-Type' => 'application/json']); + $this->client->setHeaders(['Content-Type' => 'text/html']); + + $headers = $this->client->getHeaders(); + $this->assertEquals('text/html', $headers['Content-Type']); + } + + // setTimeout() tests + public function testSetTimeoutWithDefaultValue(): void + { + $this->setupClientWithMockedCurl(); + + $result = $this->client->setTimeout(); + + $options = $this->client->getOptions(); + $this->assertEquals(FlagshipConstant::REQUEST_TIME_OUT, $options[CURLOPT_TIMEOUT]); + $this->assertEquals(FlagshipConstant::REQUEST_TIME_OUT, $options[CURLOPT_CONNECTTIMEOUT]); + $this->assertSame($this->client, $result); + } + + public function testSetTimeoutWithCustomValue(): void + { + $this->setupClientWithMockedCurl(); + + $this->client->setTimeout(30.5); + + $options = $this->client->getOptions(); + $this->assertEquals(30.5, $options[CURLOPT_TIMEOUT]); + $this->assertEquals(30.5, $options[CURLOPT_CONNECTTIMEOUT]); + } + + + // get() tests + public function testGetMakesSuccessfulRequest(): void + { + $this->setupClientWithMockedCurl(); + + $responseBody = json_encode(['data' => 'test']); + + $curlExec = $this->getFunctionMock('Flagship\Utils', 'curl_exec'); + $curlExec->expects($this->once()) + ->willReturn($responseBody); + + $lastModified = time(); + $curlGetinfo = $this->getFunctionMock('Flagship\Utils', 'curl_getinfo'); + $curlGetinfo->expects($this->any()) + ->willReturnCallback(function ($handle, $opt = null) use ($lastModified) { + + return match ($opt) { + CURLINFO_EFFECTIVE_URL => 'https://example.com/api', + CURLINFO_HTTP_CODE => 200, + CURLINFO_HEADER_SIZE => 0, + CURLINFO_CONTENT_TYPE => 'application/json', + CURLINFO_FILETIME => $lastModified, + default => null, + }; + }); + + + $response = $this->client->get('https://example.com/api', ['key' => 'value']); + + $this->assertInstanceOf(HttpResponse::class, $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertArrayHasKey('last-modified', $response->getHeaders()); + $this->assertEquals(date('Y-m-d H:i:s', $lastModified), $response->getHeaders()['last-modified']); + $this->assertEquals(['data' => 'test'], $response->getBody()); + } + + public function testGetWithParameters(): void + { + $this->setupClientWithMockedCurl(); + + $curlExec = $this->getFunctionMock('Flagship\Utils', 'curl_exec'); + $curlExec->expects($this->once()) + ->willReturn("HTTP/1.1 200 OK\r\n\r\n{}"); + + $curlGetinfo = $this->getFunctionMock('Flagship\Utils', 'curl_getinfo'); + $curlGetinfo->expects($this->any()) + ->willReturnCallback(function ($handle, $opt = null) { + if ($opt === CURLINFO_HTTP_CODE) return 200; + if ($opt === CURLINFO_HEADER_SIZE) return 19; + return null; + }); + + + $response = $this->client->get('https://example.com', ['param1' => 'value1', 'param2' => 'value2']); + + $options = $this->client->getOptions(); + $this->assertStringContainsString('param1=value1', $options[CURLOPT_URL]); + $this->assertStringContainsString('param2=value2', $options[CURLOPT_URL]); + } + + public function testGetThrowsExceptionOnError(): void + { + $this->setupClientWithMockedCurl(); + + $curlExec = $this->getFunctionMock('Flagship\Utils', 'curl_exec'); + $curlExec->expects($this->once()) + ->willReturn("HTTP/1.1 500 Internal Server Error\r\n\r\n{\"error\":\"Server error\"}"); + + $curlGetinfo = $this->getFunctionMock('Flagship\Utils', 'curl_getinfo'); + $curlGetinfo->expects($this->any()) + ->willReturnCallback(function ($handle, $opt = null) { + if ($opt === CURLINFO_HTTP_CODE) return 500; + if ($opt === CURLINFO_HEADER_SIZE) return 36; + return null; + }); + + + $this->expectException(Exception::class); + + $this->client->get('https://example.com/error'); + } + + // post() tests + public function testPostMakesSuccessfulRequest(): void + { + $this->setupClientWithMockedCurl(); + + $responseBody = json_encode(['status' => 'created']); + $rawResponse = "HTTP/1.1 201 Created\r\nContent-Type: application/json\r\n\r\n" . $responseBody; + + $curlExec = $this->getFunctionMock('Flagship\Utils', 'curl_exec'); + $curlExec->expects($this->once()) + ->willReturn($rawResponse); + + $curlGetinfo = $this->getFunctionMock('Flagship\Utils', 'curl_getinfo'); + $curlGetinfo->expects($this->any()) + ->willReturnCallback(function ($handle, $opt = null) { + if ($opt === CURLINFO_HTTP_CODE) return 201; + if ($opt === CURLINFO_HEADER_SIZE) return 47; + return null; + }); + + $response = $this->client->post('https://example.com/api', [], ['name' => 'test']); + + $this->assertInstanceOf(HttpResponse::class, $response); + $this->assertEquals(201, $response->getStatusCode()); + } + + public function testPostWithQueryAndData(): void + { + $this->setupClientWithMockedCurl(); + + $curlExec = $this->getFunctionMock('Flagship\Utils', 'curl_exec'); + $curlExec->expects($this->once()) + ->willReturn("HTTP/1.1 200 OK\r\n\r\n{}"); + + $curlGetinfo = $this->getFunctionMock('Flagship\Utils', 'curl_getinfo'); + $curlGetinfo->expects($this->any()) + ->willReturnCallback(function ($handle, $opt = null) { + if ($opt === CURLINFO_HTTP_CODE) return 200; + if ($opt === CURLINFO_HEADER_SIZE) return 19; + return null; + }); + + + $this->client->post( + 'https://example.com/api', + ['query' => 'param'], + ['data' => 'value'] ); - $this->assertEquals($urlExpected, $urlBuild); - $visitor = 'visitor=visitor'; - $urlExpected = $urlOriginal . '?' . $visitor; - $urlBuild = $buildMethod->invokeArgs($client, [$urlOriginal, $visitor]); - $this->assertEquals($urlExpected, $urlBuild); + + $options = $this->client->getOptions(); + $this->assertTrue($options[CURLOPT_POST]); + $this->assertEquals('POST', $options[CURLOPT_CUSTOMREQUEST]); } - public function testPost() + // Method chaining tests + public function testMethodChaining(): void { - $client = new HttpClient(); - $url = 'http://localhost'; - Curl::$response = '"Test-response"'; - Curl::$curlErrorCode = 0; - Curl::$curlHttpCodeInfo = 204; - Curl::$curlLastModifies = 1651479244; //2022-05-02 08:14:04 + $this->setupClientWithMockedCurl(); - $response = $client->post($url, [], []); + $result = $this->client + ->setTimeout(15) + ->setHeaders(['X-Test' => 'value']) + ->setHeaders(['Authorization' => 'Bearer token']); - $this->assertInstanceOf('Flagship\Model\HttpResponse', $response); - $this->assertSame(json_decode(Curl::$response), $response->getBody()); - $this->assertSame(Curl::$curlHttpCodeInfo, $response->getStatusCode()); - $this->assertSame("2022-05-02 08:14:04", $response->getHeaders()["last-modified"]); + $this->assertSame($this->client, $result); + + $options = $this->client->getOptions(); + $headers = $this->client->getHeaders(); + + $this->assertEquals(15, $options[CURLOPT_TIMEOUT]); + $this->assertArrayHasKey('X-Test', $headers); + $this->assertArrayHasKey('Authorization', $headers); } - public function testPostFailed() + // Helper methods + private function mockExtensionLoaded(): void { - $client = new HttpClient(); - $url = 'http://localhost'; - Curl::$response = '{"message": "Forbidden"}'; - Curl::$curlErrorCode = 0; - Curl::$curlHttpCodeInfo = 403; - $this->expectException('Exception'); - $response = $client->post($url, [], []); + $extensionLoaded = $this->getFunctionMock('Flagship\Utils', 'extension_loaded'); + $extensionLoaded->expects($this->any()) + ->with('curl') + ->willReturn(true); } - public function testGet() + + private function setupClientWithMockedCurl(): void { - $client = new HttpClient(); - $url = 'http://localhost'; - Curl::$response = '"Test-response"'; - Curl::$curlErrorCode = 0; - Curl::$curlHttpCodeInfo = 204; - - $response = $client->get($url, []); - $this->assertInstanceOf('Flagship\Model\HttpResponse', $response); - $this->assertSame(json_decode(Curl::$response), $response->getBody()); - $this->assertSame(Curl::$curlHttpCodeInfo, $response->getStatusCode()); + $this->mockExtensionLoaded(); + + $curlSetopt = $this->getFunctionMock('Flagship\Utils', 'curl_setopt'); + $curlSetopt->expects($this->any()) + ->willReturn(true); + + $this->client = new HttpClient(); } } diff --git a/tests/Visitor/CampaignsData.php b/tests/Visitor/CampaignsData.php index 58740c99..3149dd2d 100644 --- a/tests/Visitor/CampaignsData.php +++ b/tests/Visitor/CampaignsData.php @@ -9,80 +9,80 @@ trait CampaignsData public function campaignsModifications() { return [ - (new FlagDTO())->setKey('Number')->setValue(5)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('isBool')->setValue(false)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('background')->setValue('EE3300')->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('borderColor')->setValue('blue')->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('Null')->setValue(null)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('Empty')->setValue("")->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), - (new FlagDTO())->setKey('php')->setValue("value2")->setIsReference(false)->setVariationGroupId('c7q1lmuru9u05agq3apg')->setCampaignId('c7q1lmuru9u05agq3aog')->setVariationId('c7q1m8p172r04gs741og')->setSlug("campaign_2")->setCampaignType("ab"), - ]; + (new FlagDTO())->setKey('Number')->setValue(5)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('isBool')->setValue(false)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('background')->setValue('EE3300')->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('borderColor')->setValue('blue')->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('Null')->setValue(null)->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('Empty')->setValue("")->setIsReference(false)->setVariationGroupId('c8pimlr7n0ig3a0pt2jg')->setCampaignId('c8pimlr7n0ig3a0pt2ig')->setVariationId('c8pimlr7n0ig3a0pt2kg')->setSlug("campaign_1")->setCampaignType("ab"), + (new FlagDTO())->setKey('php')->setValue("value2")->setIsReference(false)->setVariationGroupId('c7q1lmuru9u05agq3apg')->setCampaignId('c7q1lmuru9u05agq3aog')->setVariationId('c7q1m8p172r04gs741og')->setSlug("campaign_2")->setCampaignType("ab"), + ]; } public function campaigns() { return [ - "visitorId" => "", - "campaigns" => [ - [ - "id" => "c8pimlr7n0ig3a0pt2ig", - "slug" => "campaign_1", - "type" => "ab", - "variationGroupId" => "c8pimlr7n0ig3a0pt2jg", - "variation" => [ - "id" => "c8pimlr7n0ig3a0pt2kg", - "modifications" => [ - "type" => "FLAG", - "value" => [ - "Number" => 5, - "isBool" => false, - "background" => "EE3300", - "borderColor" => "blue", - "Null" => null, - "Empty" => "", - ], - ], - "reference" => false, - ], - ], - [ - "id" => "c7q1lmuru9u05agq3aog", - "slug" => "campaign_2", - "type" => "ab", - "variationGroupId" => "c7q1lmuru9u05agq3apg", - "variation" => [ - "id" => "c7q1m8p172r04gs741og", - "modifications" => [ - "type" => "FLAG", - "value" => ["php" => "value2"], - ], - "reference" => false, - ], - ], - ], - ]; + "visitorId" => "", + "campaigns" => [ + [ + "id" => "c8pimlr7n0ig3a0pt2ig", + "slug" => "campaign_1", + "type" => "ab", + "variationGroupId" => "c8pimlr7n0ig3a0pt2jg", + "variation" => [ + "id" => "c8pimlr7n0ig3a0pt2kg", + "modifications" => [ + "type" => "FLAG", + "value" => [ + "Number" => 5, + "isBool" => false, + "background" => "EE3300", + "borderColor" => "blue", + "Null" => null, + "Empty" => "", + ], + ], + "reference" => false, + ], + ], + [ + "id" => "c7q1lmuru9u05agq3aog", + "slug" => "campaign_2", + "type" => "ab", + "variationGroupId" => "c7q1lmuru9u05agq3apg", + "variation" => [ + "id" => "c7q1m8p172r04gs741og", + "modifications" => [ + "type" => "FLAG", + "value" => ["php" => "value2"], + ], + "reference" => false, + ], + ], + ], + ]; } public function campaigns2() { return [ - "visitorId" => "", - "campaigns" => [ - [ - "id" => "c69sir3q6mc0ggqin8ag", - "slug" => "campaign_3", - "type" => "toggle", - "variationGroupId" => "c69sir3q6mc0ggqin8bg", - "variation" => [ - "id" => "c69sir3q6mc0ggqin8c0", - "modifications" => [ - "type" => "FLAG", - "value" => ["myAwesomeFeature" => 10], - ], - "reference" => false, - ], - ], - ], - ]; + "visitorId" => "", + "campaigns" => [ + [ + "id" => "c69sir3q6mc0ggqin8ag", + "slug" => "campaign_3", + "type" => "toggle", + "variationGroupId" => "c69sir3q6mc0ggqin8bg", + "variation" => [ + "id" => "c69sir3q6mc0ggqin8c0", + "modifications" => [ + "type" => "FLAG", + "value" => ["myAwesomeFeature" => 10], + ], + "reference" => false, + ], + ], + ], + ]; } } diff --git a/tests/Visitor/DefaultStrategyTest.php b/tests/Visitor/DefaultStrategyTest.php index 5af535e7..d1353433 100644 --- a/tests/Visitor/DefaultStrategyTest.php +++ b/tests/Visitor/DefaultStrategyTest.php @@ -4,14 +4,13 @@ namespace Flagship\Visitor; -require_once __dir__ . '/../Assets/Round.php'; - use DateTime; use Exception; use Flagship\Hit\Item; use Flagship\Hit\Page; use Flagship\Hit\Event; use Flagship\Hit\Screen; +use Flagship\BaseTestCase; use Flagship\Enum\HitType; use Flagship\Hit\Activate; use Flagship\Hit\UsageHit; @@ -22,7 +21,6 @@ use Flagship\Utils\Container; use Flagship\Utils\HttpClient; use Flagship\Utils\MurmurHash; -use PHPUnit\Framework\TestCase; use Flagship\Enum\EventCategory; use Flagship\Enum\FlagshipField; use Flagship\Enum\FSFetchReason; @@ -33,10 +31,12 @@ use Flagship\Utils\ConfigManager; use Flagship\Enum\FlagshipContext; use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; use Flagship\Config\BucketingConfig; use Flagship\Enum\VisitorCacheStatus; use Flagship\Config\DecisionApiConfig; use Flagship\Utils\ContainerInterface; +use Flagship\Utils\FlagshipLogManager; use Flagship\Enum\TroubleshootingLabel; use Flagship\Utils\HttpClientInterface; use Flagship\Api\TrackingManagerAbstract; @@ -44,7 +44,7 @@ use Flagship\Cache\IVisitorCacheImplementation; -class DefaultStrategyTest extends TestCase +class DefaultStrategyTest extends BaseTestCase { use CampaignsData; @@ -54,16 +54,18 @@ class DefaultStrategyTest extends TestCase public function modifications(): array { return [ - (new FlagDTO())->setKey('background')->setValue('EE3300')->setIsReference(false)->setVariationGroupId('c1e3t1nvfu1ncqfcdcp0')->setCampaignId('c1e3t1nvfu1ncqfcdco0')->setVariationId('c1e3t1nvfu1ncqfcdcq0'), - (new FlagDTO())->setKey('borderColor')->setValue('blue')->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), - (new FlagDTO())->setKey('Null')->setValue(null)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), - (new FlagDTO())->setKey('Empty')->setValue("")->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), - (new FlagDTO())->setKey('isBool')->setValue(false)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), - (new FlagDTO())->setKey('Number')->setValue(5)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), - ]; + (new FlagDTO())->setKey('background')->setValue('EE3300')->setIsReference(false)->setVariationGroupId('c1e3t1nvfu1ncqfcdcp0')->setCampaignId('c1e3t1nvfu1ncqfcdco0')->setVariationId('c1e3t1nvfu1ncqfcdcq0'), + (new FlagDTO())->setKey('borderColor')->setValue('blue')->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), + (new FlagDTO())->setKey('Null')->setValue(null)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), + (new FlagDTO())->setKey('Empty')->setValue("")->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), + (new FlagDTO())->setKey('isBool')->setValue(false)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), + (new FlagDTO())->setKey('Number')->setValue(5)->setIsReference(false)->setVariationGroupId('c1e3t1sddfu1ncqfcdcp0')->setCampaignId('c1slf3t1nvfu1ncqfcdcfd')->setVariationId('cleo3t1nvfu1ncqfcdcsdf'), + ]; } + + public function testUpdateContext() { /** @@ -84,20 +86,15 @@ public function testUpdateContext() $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; + - /** - * @var ApiManager|MockObject $decisionManager - */ $decisionManager = $this->getMockBuilder(ApiManager::class) ->disableOriginalConstructor() ->getMock(); - /** - * @var TrackingManagerAbstract|MockObject $trackingManager - */ $trackingManager = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', ['sendHit'], @@ -184,24 +181,21 @@ public function testUpdateContext() public function testUpdateContextCollection() { $configData = [ - 'envId' => 'env_value', - 'apiKey' => 'key_value', - ]; + 'envId' => 'env_value', + 'apiKey' => 'key_value', + ]; $config = new DecisionApiConfig($configData['envId'], $configData['apiKey']); $visitorId = "visitor_id"; + $visitorContext = [ 'name' => 'visitor_name', 'age' => 25 ]; - /** - * @var ApiManager|MockObject $decisionManager - */ + $decisionManager = $this->getMockBuilder(ApiManager::class) ->disableOriginalConstructor() ->getMock(); - /** - * @var TrackingManagerAbstract|MockObject $trackingManager - */ + $trackingManager = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', ['sendHit'], @@ -214,11 +208,13 @@ public function testUpdateContextCollection() $visitor = new VisitorDelegate(new Container(), $configManager, $visitorId, false, $visitorContext, true); $defaultStrategy = new DefaultStrategy($visitor); + $newVisitorContext = [ - 'vip' => true, - 'gender' => 'F', - ]; + 'vip' => true, + 'gender' => 'F', + ]; $defaultStrategy->updateContextCollection($newVisitorContext); + $this->assertCount(8, $visitor->getContext()); //Test without Key @@ -226,6 +222,7 @@ public function testUpdateContextCollection() $newVisitorContext = ['vip']; $defaultStrategy->updateContextCollection($newVisitorContext); + $this->assertCount(8, $visitor->getContext()); } @@ -233,9 +230,9 @@ public function testClearContext() { $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $config = new DecisionApiConfig('envId', 'apiKey'); /** * @var ApiManager|MockObject $decisionManager @@ -301,9 +298,9 @@ public function testAuthenticate() $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $config = new DecisionApiConfig('envId', 'apiKey'); $config->setLogManager($logManagerStub); @@ -502,8 +499,8 @@ public function testUnauthenticate() $this->logicalOr( FlagshipConstant::FLAGSHIP_VISITOR_NOT_AUTHENTIFICATE ), - [FlagshipConstant::TAG => $unauthenticateName] - ); + [FlagshipConstant::TAG => $unauthenticateName] + ); $defaultStrategy = new DefaultStrategy($visitor); $defaultStrategy->unauthenticate(); @@ -672,8 +669,8 @@ public function testFetchFlagsTroubleshootingData() false, true, [ - "setTroubleshootingData", - "addTroubleshootingHit", + "setTroubleshootingData", + "addTroubleshootingHit", ] ); @@ -681,38 +678,38 @@ public function testFetchFlagsTroubleshootingData() $httpResponseBody = $this->campaigns(); $troubleshootingData = [ - "startDate" => "2023-04-13T09:33:38.049Z", - "endDate" => "2023-04-13T10:03:38.049Z", - "timezone" => "Europe/Paris", - "traffic" => 40, - ]; + "startDate" => "2023-04-13T09:33:38.049Z", + "endDate" => "2023-04-13T10:03:38.049Z", + "timezone" => "Europe/Paris", + "traffic" => 40, + ]; $httpResponseBody["extras"] = [ - "accountSettings" => [ - "@type" => "type.googleapis.com/flagship.protobuf.AccountSettings", - "enabledXPC" => false, - "enabled1V1T" => false, - "troubleshooting" => $troubleshootingData, - ], - ]; + "accountSettings" => [ + "@type" => "type.googleapis.com/flagship.protobuf.AccountSettings", + "enabledXPC" => false, + "enabled1V1T" => false, + "troubleshooting" => $troubleshootingData, + ], + ]; $httpClientMock->expects($this->exactly(3))->method("post")->willReturn(new HttpResponse(200, $httpResponseBody)); $trackingManagerMock->expects($this->exactly(3))->method("setTroubleshootingData")->with($this->callback(function ($param) use ($troubleshootingData) { - $startDate = new DateTime($troubleshootingData['startDate']); - $endDate = new DateTime($troubleshootingData['endDate']); - return $param->getTraffic() === $troubleshootingData['traffic'] && - $param->getTimezone() === $troubleshootingData['timezone'] && - $param->getStartDate()->getTimestamp() === $startDate->getTimestamp() && - $param->getEndDate()->getTimestamp() === $endDate->getTimestamp(); + $startDate = new DateTime($troubleshootingData['startDate']); + $endDate = new DateTime($troubleshootingData['endDate']); + return $param->getTraffic() === $troubleshootingData['traffic'] && + $param->getTimezone() === $troubleshootingData['timezone'] && + $param->getStartDate()->getTimestamp() === $startDate->getTimestamp() && + $param->getEndDate()->getTimestamp() === $endDate->getTimestamp(); })); $matcher = $this->exactly(6); $trackingManagerMock->expects($matcher)->method("addTroubleshootingHit")->with( $this->logicalOr( $this->callback(function ($param) { - return $param->getLabel() === TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS || - $param->getLabel() === TroubleshootingLabel::VISITOR_SEND_HIT; + return $param->getLabel() === TroubleshootingLabel::VISITOR_FETCH_CAMPAIGNS || + $param->getLabel() === TroubleshootingLabel::VISITOR_SEND_HIT; }) ) ); @@ -774,8 +771,8 @@ public function testSendHit() $trackerManagerMock = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', [ - $config, - new HttpClient(), + $config, + new HttpClient(), ], '', true, @@ -868,8 +865,8 @@ public function testSendHitWithLog() $trackerManagerMock = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', [ - $config, - new HttpClient(), + $config, + new HttpClient(), ], '', true, @@ -917,23 +914,19 @@ public function testSendHitWithLog() public function testUserExposed() { - /** - * @var LoggerInterface|MockObject $logManagerStub - */ - $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', - [], - "", - true, - true, - true, - [ - 'error', - 'info', - ] - ); + $round = $this->getFunctionMock("Flagship\Traits", 'round'); + $round->expects($this->any())->willReturn(0); + + + $logManagerStub = $this->getMockBuilder( + FlagshipLogManager::class + )->disableOriginalConstructor()->onlyMethods( + ['info'] + )->getMock(); $config = new DecisionApiConfig('envId', 'apiKey'); + + $config->setLogManager($logManagerStub); /** @@ -942,12 +935,12 @@ public function testUserExposed() $trackerManagerStub = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', [ - $config, - new HttpClient(), + $config, + new HttpClient(), ], '', true, - true, + false, true, ['activateFlag'] ); @@ -962,7 +955,8 @@ public function testUserExposed() $key = "key"; $flagDTO = new FlagDTO(); - $flagDTO->setKey($key)->setCampaignId("campaignId")->setVariationGroupId("variationGroupId")->setVariationId("variationId")->setIsReference(false)->setCampaignType("campaignType")->setSlug("slug")->setCampaignName("campaignName")->setVariationGroupName("variationGroupName")->setVariationName("variationName")->setValue("value"); + $flagDTO->setKey($key)->setCampaignId("campaignId") + ->setVariationGroupId("variationGroupId")->setVariationId("variationId")->setIsReference(false)->setCampaignType("campaignType")->setSlug("slug")->setCampaignName("campaignName")->setVariationGroupName("variationGroupName")->setVariationName("variationName")->setValue("value"); $defaultValue = "default"; $flagMetadata = new FSFlagMetadata( @@ -977,18 +971,31 @@ public function testUserExposed() $flagDTO->getVariationName() ); - $activate = new Activate($flagDTO->getVariationGroupId(), $flagDTO->getVariationId()); - $activate->setFlagKey($flagDTO->getKey())->setFlagValue($flagDTO->getValue())->setFlagDefaultValue($defaultValue)->setFlagMetadata($flagMetadata)->setVisitorContext($visitor->getContext())->setVisitorId($visitor->getVisitorId())->setConfig($config); + $activate = new Activate( + $flagDTO->getVariationGroupId(), + $flagDTO->getVariationId(), + $key, + $flagMetadata + ); + + $activate->setFlagKey($flagDTO->getKey()) + ->setFlagValue($flagDTO->getValue()) + ->setFlagDefaultValue($defaultValue) + ->setVisitorContext($visitor->getContext()) + ->setVisitorId($visitor->getVisitorId()) + ->setConfig($config); $trackerManagerStub->expects($this->exactly(2)) ->method('activateFlag') - ->with($activate); + ->with($activate) + ; $defaultStrategy->visitorExposed($key, $defaultValue, $flagDTO, true); //Test defaultValue null $activate->setFlagDefaultValue(null); + $defaultStrategy->visitorExposed($key, null, $flagDTO, true); $functionName = FlagshipConstant::FLAG_USER_EXPOSED; @@ -1029,28 +1036,26 @@ public function testUserExposed() public function testGetFlagValue() { - $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', - [], - "", - true, - true, - true, - [ - 'error', - 'info', - ] - ); + $this->mockRoundFunction(); + $logManagerStub = $this->getMockBuilder( + FlagshipLogManager::class + ) + ->disableOriginalConstructor() + ->onlyMethods( + ['info', 'error'] + )->getMock(); $config = new DecisionApiConfig('envId', 'apiKey'); + + $config->setLogManager($logManagerStub); $trackerManagerStub = $this->getMockForAbstractClass( 'Flagship\Api\TrackingManagerAbstract', [ - $config, - new HttpClient(), + $config, + new HttpClient(), ], '', true, @@ -1084,10 +1089,17 @@ public function testGetFlagValue() $flagDTO->getVariationName() ); - $activate = new Activate($flagDTO->getVariationGroupId(), $flagDTO->getVariationId()); + $activate = new Activate( + $flagDTO->getVariationGroupId(), + $flagDTO->getVariationId(), + $key, + $flagMetadata + ); $activate->setFlagKey($flagDTO->getKey())->setFlagValue($flagDTO->getValue())->setFlagDefaultValue($defaultValue)->setFlagMetadata($flagMetadata)->setVisitorContext($visitor->getContext())->setVisitorId($visitor->getVisitorId())->setConfig($config); - $trackerManagerStub->expects($this->exactly(4))->method('activateFlag')->with($activate); + $trackerManagerStub->expects($this->exactly(4)) + ->method('activateFlag') + ->with($activate); $value = $defaultStrategy->getFlagValue($key, $defaultValue, $flagDTO); $this->assertEquals($value, $flagDTO->getValue()); @@ -1137,8 +1149,8 @@ public function testGetFlagMetadata() true, true, [ - 'error', - 'info', + 'error', + 'info', ] ); @@ -1150,8 +1162,8 @@ public function testGetFlagMetadata() false, true, [ - "setTroubleshootingData", - "addTroubleshootingHit", + "setTroubleshootingData", + "addTroubleshootingHit", ] ); @@ -1201,13 +1213,14 @@ public function testGetFlagMetadata() public function testLookupVisitor() { + $config = new DecisionApiConfig('envId', 'apiKey'); $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; /** * @var LoggerInterface|MockObject $logManagerStub @@ -1220,8 +1233,8 @@ public function testLookupVisitor() true, true, [ - 'error', - 'info', + 'error', + 'info', ] ); @@ -1266,54 +1279,54 @@ public function testLookupVisitor() $differentVisitorId = "different visitorID"; $visitorCache2 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [StrategyAbstract::VISITOR_ID => $differentVisitorId], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [StrategyAbstract::VISITOR_ID => $differentVisitorId], + ]; $visitorCache3 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [StrategyAbstract::VISITOR_ID => $visitorId], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [StrategyAbstract::VISITOR_ID => $visitorId], + ]; $visitorCache4 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::CAMPAIGNS => "not an array", - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::CAMPAIGNS => "not an array", + ], + ]; $visitorCache5 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::CAMPAIGNS => ["anythings"], - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::CAMPAIGNS => ["anythings"], + ], + ]; $visitorCache6 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::CAMPAIGNS => [ - [ - FlagshipField::FIELD_CAMPAIGN_ID => "c8pimlr7n0ig3a0pt2ig", - FlagshipField::FIELD_VARIATION_GROUP_ID => "c8pimlr7n0ig3a0pt2jg", - FlagshipField::FIELD_VARIATION_ID => "c8pimlr7n0ig3a0pt2kg", - FlagshipField::FIELD_IS_REFERENCE => false, - FlagshipField::FIELD_CAMPAIGN_TYPE => "ab", - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => [ - "Number" => 5, - "isBool" => false, - "background" => "EE3300", - "borderColor" => "blue", - "Null" => null, - "Empty" => "", - ], - ], - ], - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::CAMPAIGNS => [ + [ + FlagshipField::FIELD_CAMPAIGN_ID => "c8pimlr7n0ig3a0pt2ig", + FlagshipField::FIELD_VARIATION_GROUP_ID => "c8pimlr7n0ig3a0pt2jg", + FlagshipField::FIELD_VARIATION_ID => "c8pimlr7n0ig3a0pt2kg", + FlagshipField::FIELD_IS_REFERENCE => false, + FlagshipField::FIELD_CAMPAIGN_TYPE => "ab", + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => [ + "Number" => 5, + "isBool" => false, + "background" => "EE3300", + "borderColor" => "blue", + "Null" => null, + "Empty" => "", + ], + ], + ], + ], + ]; $visitorCache7 = [ StrategyAbstract::VERSION => 2, @@ -1354,21 +1367,21 @@ public function testLookupVisitor() [FlagshipConstant::TAG => $functionName] ); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return empty array $defaultStrategy->lookupVisitor(); $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::NONE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return array["version"=>1] only $defaultStrategy->lookupVisitor(); $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return cache with different visitor id @@ -1376,7 +1389,7 @@ public function testLookupVisitor() $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return cache without campaings @@ -1385,7 +1398,7 @@ public function testLookupVisitor() $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return cache with is_array(campaings) === false @@ -1393,7 +1406,7 @@ public function testLookupVisitor() $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return cache with invalid campaigns @@ -1401,7 +1414,7 @@ public function testLookupVisitor() $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); // test return cache with valid cache @@ -1409,14 +1422,14 @@ public function testLookupVisitor() $this->assertEquals($visitor->getVisitorCacheStatus(), VisitorCacheStatus::VISITOR_ID_CACHE); - $this->assertSame($visitorCache6, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache6), $visitor->visitorCache); // $defaultStrategy->lookupVisitor(); - $this->assertEquals(VisitorCacheStatus::VISITOR_ID_CACHE, $visitor->getVisitorCacheStatus(), ); + $this->assertEquals(VisitorCacheStatus::VISITOR_ID_CACHE, $visitor->getVisitorCacheStatus(),); - $this->assertSame($visitorCache6, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache6), $visitor->visitorCache); } public function testLookupVisitorXpc() @@ -1536,6 +1549,7 @@ public function testLookupVisitorXpc() * @var IVisitorCacheImplementation|MockObject $VisitorCacheImplementationMock */ $VisitorCacheImplementationMock->expects($this->exactly(7)) + ->method("lookupVisitor")->willReturnCallback(function ($id) use ($visitorCache, $visitorId, $anonymousId, $visitorCacheAnonymous) { if ($id === $visitorId) { return $visitorCache; @@ -1550,33 +1564,33 @@ public function testLookupVisitorXpc() $config->setVisitorCacheImplementation($VisitorCacheImplementationMock); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); $defaultStrategy->lookupVisitor(); - $this->assertSame($visitorCache, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache), $visitor->visitorCache); $this->assertSame(VisitorCacheStatus::VISITOR_ID_CACHE, $visitor->getVisitorCacheStatus()); $visitor->setAnonymousId($anonymousId); $defaultStrategy->lookupVisitor(); - $this->assertSame($visitorCache, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache), $visitor->visitorCache); $this->assertEquals(VisitorCacheStatus::VISITOR_ID_CACHE_WITH_ANONYMOUS_ID_CACHE, $visitor->getVisitorCacheStatus()); $visitor->setVisitorId("new_visitor_id"); $defaultStrategy->lookupVisitor(); $this->assertEquals(VisitorCacheStatus::ANONYMOUS_ID_CACHE, $visitor->getVisitorCacheStatus()); - $this->assertSame($visitorCacheAnonymous, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCacheAnonymous), $visitor->visitorCache); $visitor->setAnonymousId("another_anonymous_id"); $visitor->setVisitorId("another_visitor_id"); $defaultStrategy->lookupVisitor(); $this->assertEquals(VisitorCacheStatus::NONE, $visitor->getVisitorCacheStatus()); - $this->assertCount(0, $visitor->visitorCache); + $this->assertNull($visitor->visitorCache); } public function testCacheVisitor() @@ -1584,17 +1598,14 @@ public function testCacheVisitor() $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $config = new DecisionApiConfig('envId', 'apiKey'); - /** - * @var HttpClientInterface|MockObject $httpClientMock - */ $httpClientMock = $this->getMockForAbstractClass( - 'Flagship\Utils\HttpClientInterface', + HttpClientInterface::class, ['post'], '', false @@ -1603,33 +1614,22 @@ public function testCacheVisitor() $campaignsData = $this->campaigns(); $campaignsData2 = $this->campaigns2(); - $httpClientMock->expects($this->exactly(3))->method("post")->willReturnOnConsecutiveCalls( - new HttpResponse(200, $campaignsData), - new HttpResponse(200, $campaignsData2), - new HttpResponse(200, $campaignsData2) - ); + $httpClientMock->expects($this->exactly(3)) + ->method("post") + ->willReturnOnConsecutiveCalls( + new HttpResponse(200, $campaignsData), + new HttpResponse(200, $campaignsData2), + new HttpResponse(200, $campaignsData2) + ); $decisionManager = new ApiManager($httpClientMock, $config); - /** - * @var LoggerInterface|MockObject $logManagerStub - */ - $logManagerStub = $this->getMockForAbstractClass( - LoggerInterface::class, - [], - "", - true, - true, - true, - [ - 'error', - 'info', - ] - ); - /** - * @var TrackingManagerAbstract|MockObject $trackingManagerMock - */ + $logManagerStub = $this->mockLoggerManager([ + 'error', + 'info', + ]); + $trackingManagerMock = $this->getMockForAbstractClass( TrackingManagerAbstract::class, [], @@ -1638,16 +1638,13 @@ public function testCacheVisitor() false, true, [ - "setTroubleshootingData", - "addTroubleshootingHit", + "setTroubleshootingData", + "addTroubleshootingHit", ] ); $config->setLogManager($logManagerStub); - /** - * @var IVisitorCacheImplementation|MockObject $VisitorCacheImplementationMock - */ $VisitorCacheImplementationMock = $this->getMockForAbstractClass( IVisitorCacheImplementation::class, [], @@ -1656,16 +1653,14 @@ public function testCacheVisitor() true, true, [ - 'lookupVisitor', - 'cacheVisitor', + 'lookupVisitor', + 'cacheVisitor', ] ); $configManager = new ConfigManager($config, $decisionManager, $trackingManagerMock); - /** - * @var ContainerInterface|MockObject $containerMock - */ + $containerMock = $this->getMockForAbstractClass( ContainerInterface::class, ['get'], @@ -1680,6 +1675,7 @@ public function testCacheVisitor() }; $containerMock->method('get')->willReturnCallback($containerGetMethod); + $visitor = new VisitorDelegate( $containerMock, $configManager, @@ -1697,28 +1693,28 @@ public function testCacheVisitor() $assignmentsHistory[$campaign[FlagshipField::FIELD_VARIATION_GROUP_ID]] = $variation[FlagshipField::FIELD_ID]; $campaigns[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_SLUG => $campaign[FlagshipField::FIELD_SLUG] ?? null, - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; + FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], + FlagshipField::FIELD_SLUG => $campaign[FlagshipField::FIELD_SLUG] ?? null, + FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], + FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], + FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], + FlagshipField::FIELD_CAMPAIGN_TYPE => $campaign[FlagshipField::FIELD_CAMPAIGN_TYPE], + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => $modifications, + ]; } $visitorCache = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), - StrategyAbstract::CONSENT => $visitor->hasConsented(), - StrategyAbstract::CONTEXT => $visitor->getContext(), - StrategyAbstract::CAMPAIGNS => $campaigns, - StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), + StrategyAbstract::CONSENT => $visitor->hasConsented(), + StrategyAbstract::CONTEXT => $visitor->getContext(), + StrategyAbstract::CAMPAIGNS => $campaigns, + StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, + ], + ]; $assignmentsHistory2 = []; $campaigns2 = []; foreach ($campaignsData2[FlagshipField::FIELD_CAMPAIGNS] as $campaign) { @@ -1727,40 +1723,43 @@ public function testCacheVisitor() $assignmentsHistory2[$campaign[FlagshipField::FIELD_VARIATION_GROUP_ID]] = $variation[FlagshipField::FIELD_ID]; $campaigns2[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_SLUG => $campaign[FlagshipField::FIELD_SLUG] ?? null, - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; + FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], + FlagshipField::FIELD_SLUG => $campaign[FlagshipField::FIELD_SLUG] ?? null, + FlagshipField::FIELD_CAMPAIGN_TYPE => $campaign[FlagshipField::FIELD_CAMPAIGN_TYPE], + FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], + FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], + FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => $modifications, + ]; } $visitorCache2 = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), - StrategyAbstract::CONSENT => $visitor->hasConsented(), - StrategyAbstract::CONTEXT => $visitor->getContext(), - StrategyAbstract::CAMPAIGNS => $campaigns2, - StrategyAbstract::ASSIGNMENTS_HISTORY => array_merge($assignmentsHistory, $assignmentsHistory2), - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), + StrategyAbstract::CONSENT => $visitor->hasConsented(), + StrategyAbstract::CONTEXT => $visitor->getContext(), + StrategyAbstract::CAMPAIGNS => $campaigns2, + StrategyAbstract::ASSIGNMENTS_HISTORY => array_merge($assignmentsHistory, $assignmentsHistory2), + ], + ]; $exception = new Exception("Message error"); - $VisitorCacheImplementationMock->expects($this->exactly(3))->method("cacheVisitor")->with( - $this->logicalOr( - $visitorId - ), - $this->logicalOr( - $visitorCache, - $visitorCache2 - ) - )->willReturnOnConsecutiveCalls(null, null, $this->throwException($exception)); + $VisitorCacheImplementationMock + ->expects($this->exactly(3)) + ->method("cacheVisitor") + ->with( + $this->logicalOr( + $visitorId + ), + $this->logicalOr( + $visitorCache, + $visitorCache2 + ) + )->willReturnOnConsecutiveCalls(null, null, $this->throwException($exception)); $config->setVisitorCacheImplementation($VisitorCacheImplementationMock); @@ -1779,10 +1778,13 @@ public function testCacheVisitor() $visitor->fetchFlags(); - $logManagerStub->expects($this->exactly(1))->method('error')->with( - $exception->getMessage(), - [FlagshipConstant::TAG => $functionName] - ); + $logManagerStub->expects($this->exactly(1)) + ->method('error') + ->with( + $exception->getMessage(), + [FlagshipConstant::TAG => $functionName] + ) + ; $visitor->fetchFlags(); } @@ -1798,11 +1800,9 @@ public function testCacheVisitorXpc() $config = new DecisionApiConfig('envId', 'apiKey'); - /** - * @var HttpClientInterface|MockObject $httpClientMock - */ + $httpClientMock = $this->getMockForAbstractClass( - 'Flagship\Utils\HttpClientInterface', + HttpClientInterface::class, ['post'], '', false @@ -1811,11 +1811,9 @@ public function testCacheVisitorXpc() $decisionManager = new ApiManager($httpClientMock, $config); - /** - * @var LoggerInterface|MockObject $logManagerStub - */ + $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', + LoggerInterface::class, [], "", true, @@ -1824,9 +1822,6 @@ public function testCacheVisitorXpc() ['error', 'info'] ); - /** - * @var TrackingManagerAbstract|MockObject $trackingManagerMock - */ $trackingManagerMock = $this->getMockForAbstractClass( "Flagship\Api\TrackingManagerAbstract", [], @@ -1839,11 +1834,9 @@ public function testCacheVisitorXpc() $config->setLogManager($logManagerStub); - /** - * @var IVisitorCacheImplementation|MockObject $VisitorCacheImplementationMock - */ + $VisitorCacheImplementationMock = $this->getMockForAbstractClass( - "Flagship\Cache\IVisitorCacheImplementation", + IVisitorCacheImplementation::class, [], "", true, @@ -1852,11 +1845,13 @@ public function testCacheVisitorXpc() ['lookupVisitor', 'cacheVisitor'] ); - $configManager = new ConfigManager($config, $decisionManager, $trackingManagerMock); + $configManager = new ConfigManager( + $config, + $decisionManager, + $trackingManagerMock + ); + - /** - * @var ContainerInterface|MockObject $containerMock - */ $containerMock = $this->getMockForAbstractClass( 'Flagship\Utils\ContainerInterface', ['get'], @@ -1871,6 +1866,7 @@ public function testCacheVisitorXpc() }; $containerMock->method('get')->willReturnCallback($containerGetMethod); + $visitor = new VisitorDelegate( $containerMock, $configManager, @@ -1940,7 +1936,7 @@ public function testCacheVisitorXpc() $defaultStrategy->cacheVisitor(); - $this->assertSame($visitorCache, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache), $visitor->visitorCache); $visitor->setAnonymousId($anonymousId); @@ -1948,7 +1944,7 @@ public function testCacheVisitorXpc() $defaultStrategy->cacheVisitor(); - $this->assertSame($visitorCache2, $visitor->visitorCache); + $this->assertEquals(VisitorCacheDTO::fromArray($visitorCache2), $visitor->visitorCache); $visitor->setVisitorCacheStatus(VisitorCacheStatus::NONE); @@ -1967,9 +1963,9 @@ public function testFlushVisitor() { $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $config = new DecisionApiConfig('envId', 'apiKey'); $httpClientMock = $this->getMockForAbstractClass( @@ -1992,8 +1988,8 @@ public function testFlushVisitor() true, true, [ - 'error', - 'info', + 'error', + 'info', ] ); @@ -2051,11 +2047,12 @@ public function testFetchVisitorCampaigns() $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $config = new DecisionApiConfig('envId', 'apiKey'); + $httpClientMock = $this->getMockForAbstractClass( 'Flagship\Utils\HttpClientInterface', ['post'], @@ -2065,22 +2062,16 @@ public function testFetchVisitorCampaigns() $campaignsData = $this->campaigns(); - $httpClientMock->expects($this->exactly(2))->method("post")->willThrowException(new Exception()); + $httpClientMock->expects($this->exactly(2)) + ->method("post") + ->willThrowException(new Exception()); $decisionManager = new ApiManager($httpClientMock, $config); - $logManagerStub = $this->getMockForAbstractClass( - 'Psr\Log\LoggerInterface', - [], - "", - true, - true, - true, - [ - 'error', - 'info', - ] - ); + $logManagerStub = $this->mockLoggerManager([ + 'error', + 'info', + ]); $trackingManagerMock = $this->getMockForAbstractClass( "Flagship\Api\TrackingManagerAbstract", @@ -2090,8 +2081,8 @@ public function testFetchVisitorCampaigns() false, true, [ - "setTroubleshootingData", - "addTroubleshootingHit", + "setTroubleshootingData", + "addTroubleshootingHit", ] ); @@ -2132,28 +2123,30 @@ public function testFetchVisitorCampaigns() $assignmentsHistory[$campaign[FlagshipField::FIELD_ID]] = $variation[FlagshipField::FIELD_ID]; $campaigns[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; + FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], + FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], + FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], + FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], + FlagshipField::FIELD_CAMPAIGN_TYPE => $campaign[FlagshipField::FIELD_CAMPAIGN_TYPE], + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => $modifications, + ]; } $visitorCache = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), - StrategyAbstract::CONSENT => $visitor->hasConsented(), - StrategyAbstract::CONTEXT => $visitor->getContext(), - StrategyAbstract::CAMPAIGNS => $campaigns, - StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), + StrategyAbstract::CONSENT => $visitor->hasConsented(), + StrategyAbstract::CONTEXT => $visitor->getContext(), + StrategyAbstract::CAMPAIGNS => $campaigns, + StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, + ], + ]; + + $visitorCache = VisitorCacheDTO::fromArray($visitorCache); $visitor->visitorCache = $visitorCache; @@ -2161,7 +2154,7 @@ public function testFetchVisitorCampaigns() $this->assertCount(7, $visitor->getFlagsDTO()); - $visitor->visitorCache = []; + $visitor->visitorCache = null; $visitor->fetchFlags(); @@ -2170,6 +2163,7 @@ public function testFetchVisitorCampaigns() public function testSendAnalyticsHit() { + $this->mockRoundFunction(); $bucketingUrl = "https://terst.com"; $config = new BucketingConfig($bucketingUrl, 'envId', 'apiKey'); diff --git a/tests/Visitor/NoConsentStrategyTest.php b/tests/Visitor/NoConsentStrategyTest.php index ae237ace..3dba07b2 100644 --- a/tests/Visitor/NoConsentStrategyTest.php +++ b/tests/Visitor/NoConsentStrategyTest.php @@ -2,17 +2,18 @@ namespace Flagship\Visitor; -use Flagship\Config\DecisionApiConfig; -use Flagship\Decision\ApiManager; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FlagshipField; use Flagship\Hit\Page; -use Flagship\Hit\Troubleshooting; use Flagship\Model\FlagDTO; -use Flagship\Model\HttpResponse; -use Flagship\Utils\ConfigManager; use Flagship\Utils\Container; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipField; +use Flagship\Model\HttpResponse; +use Flagship\Decision\ApiManager; +use Flagship\Hit\Troubleshooting; +use Flagship\Utils\ConfigManager; +use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; +use Flagship\Config\DecisionApiConfig; class NoConsentStrategyTest extends TestCase { @@ -46,8 +47,8 @@ public function testMethods() false, true, [ - "setTroubleshootingData", - 'activateFlag', + "setTroubleshootingData", + 'activateFlag', ] ); @@ -96,24 +97,24 @@ public function testMethods() $noConsentStrategy->updateContext($key, $value); $this->assertSame([ - "sdk_osName" => PHP_OS, - FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, - FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::FS_USERS => $visitorId, - $key => $value, - ], $visitor->getContext()); + "sdk_osName" => PHP_OS, + FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, + FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::FS_USERS => $visitorId, + $key => $value, + ], $visitor->getContext()); //Test updateContextCollection $noConsentStrategy->updateContextCollection(['age' => 20]); $this->assertSame([ - "sdk_osName" => PHP_OS, - FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, - FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::FS_USERS => $visitorId, - $key => $value, - 'age' => 20, - ], $visitor->getContext()); + "sdk_osName" => PHP_OS, + FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, + FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::FS_USERS => $visitorId, + $key => $value, + 'age' => 20, + ], $visitor->getContext()); //Test clearContext $noConsentStrategy->clearContext(); @@ -144,27 +145,29 @@ public function testMethods() $assignmentsHistory[$campaign[FlagshipField::FIELD_ID]] = $variation[FlagshipField::FIELD_ID]; $campaigns[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; + FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], + FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], + FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], + FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], + FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => $modifications, + ]; } $visitorCache = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitorId, - StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), - StrategyAbstract::CONSENT => $visitor->hasConsented(), - StrategyAbstract::CONTEXT => $visitor->getContext(), - StrategyAbstract::CAMPAIGNS => $campaigns, - StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitorId, + StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), + StrategyAbstract::CONSENT => $visitor->hasConsented(), + StrategyAbstract::CONTEXT => $visitor->getContext(), + StrategyAbstract::CAMPAIGNS => $campaigns, + StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, + ], + ]; + + $visitorCache = VisitorCacheDTO::fromArray($visitorCache); //Test fetchVisitorCampaigns $visitor->visitorCache = $visitorCache; @@ -180,8 +183,8 @@ public function testMethods() true, true, [ - 'lookupVisitor', - 'cacheVisitor', + 'lookupVisitor', + 'cacheVisitor', ] ); diff --git a/tests/Visitor/PanicStrategyTest.php b/tests/Visitor/PanicStrategyTest.php index ee249633..99ccc438 100644 --- a/tests/Visitor/PanicStrategyTest.php +++ b/tests/Visitor/PanicStrategyTest.php @@ -2,16 +2,17 @@ namespace Flagship\Visitor; -use Flagship\Config\DecisionApiConfig; -use Flagship\Decision\ApiManager; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FlagshipField; -use Flagship\Enum\FSSdkStatus; use Flagship\Hit\Page; -use Flagship\Model\HttpResponse; -use Flagship\Utils\ConfigManager; use Flagship\Utils\Container; +use Flagship\Enum\FSSdkStatus; use PHPUnit\Framework\TestCase; +use Flagship\Enum\FlagshipField; +use Flagship\Model\HttpResponse; +use Flagship\Decision\ApiManager; +use Flagship\Utils\ConfigManager; +use Flagship\Enum\FlagshipConstant; +use Flagship\Model\VisitorCacheDTO; +use Flagship\Config\DecisionApiConfig; class PanicStrategyTest extends TestCase { @@ -142,27 +143,29 @@ public function testMethods() $assignmentsHistory[$campaign[FlagshipField::FIELD_ID]] = $variation[FlagshipField::FIELD_ID]; $campaigns[] = [ - FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], - FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], - FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], - FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], - FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], - StrategyAbstract::ACTIVATED => false, - StrategyAbstract::FLAGS => $modifications[FlagshipField::FIELD_VALUE], - ]; + FlagshipField::FIELD_CAMPAIGN_ID => $campaign[FlagshipField::FIELD_ID], + FlagshipField::FIELD_VARIATION_GROUP_ID => $campaign[FlagshipField::FIELD_VARIATION_GROUP_ID], + FlagshipField::FIELD_VARIATION_ID => $variation[FlagshipField::FIELD_ID], + FlagshipField::FIELD_IS_REFERENCE => $variation[FlagshipField::FIELD_REFERENCE], + FlagshipField::FIELD_CAMPAIGN_TYPE => $modifications[FlagshipField::FIELD_CAMPAIGN_TYPE], + StrategyAbstract::ACTIVATED => false, + StrategyAbstract::FLAGS => $modifications, + ]; } $visitorCache = [ - StrategyAbstract::VERSION => 1, - StrategyAbstract::DATA => [ - StrategyAbstract::VISITOR_ID => $visitor->getVisitorId(), - StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), - StrategyAbstract::CONSENT => $visitor->hasConsented(), - StrategyAbstract::CONTEXT => $visitor->getContext(), - StrategyAbstract::CAMPAIGNS => $campaigns, - StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, - ], - ]; + StrategyAbstract::VERSION => 1, + StrategyAbstract::DATA => [ + StrategyAbstract::VISITOR_ID => $visitor->getVisitorId(), + StrategyAbstract::ANONYMOUS_ID => $visitor->getAnonymousId(), + StrategyAbstract::CONSENT => $visitor->hasConsented(), + StrategyAbstract::CONTEXT => $visitor->getContext(), + StrategyAbstract::CAMPAIGNS => $campaigns, + StrategyAbstract::ASSIGNMENTS_HISTORY => $assignmentsHistory, + ], + ]; + + $visitorCache = VisitorCacheDTO::fromArray($visitorCache); //Test fetchVisitorCampaigns $visitor->visitorCache = $visitorCache; @@ -178,8 +181,8 @@ public function testMethods() true, true, [ - 'lookupVisitor', - 'cacheVisitor', + 'lookupVisitor', + 'cacheVisitor', ] ); diff --git a/tests/Visitor/VisitorDelegateTest.php b/tests/Visitor/VisitorDelegateTest.php index 16df8216..0e2bb8e5 100644 --- a/tests/Visitor/VisitorDelegateTest.php +++ b/tests/Visitor/VisitorDelegateTest.php @@ -2,49 +2,50 @@ namespace Flagship\Visitor; -use Flagship\Api\TrackingManagerAbstract; -use Flagship\Config\BucketingConfig; -use Flagship\Config\DecisionApiConfig; -use Flagship\Decision\ApiManager; +use Flagship\Hit\Page; +use Flagship\Hit\Event; +use ReflectionException; +use Flagship\Utils\Utils; +use Flagship\BaseTestCase; +use Flagship\Model\FlagDTO; +use Flagship\Utils\Container; +use Flagship\Enum\FSSdkStatus; use Flagship\Enum\EventCategory; -use Flagship\Enum\FlagshipConstant; -use Flagship\Enum\FlagshipContext; use Flagship\Enum\FSFetchReason; use Flagship\Enum\FSFetchStatus; -use Flagship\Enum\FSSdkStatus; -use Flagship\Flag\FSFlagMetadata; -use Flagship\Hit\Event; -use Flagship\Hit\Page; -use Flagship\Model\FetchFlagsStatus; -use Flagship\Model\FlagDTO; +use Flagship\Decision\ApiManager; use Flagship\Utils\ConfigManager; -use Flagship\Utils\Container; +use Flagship\Enum\FlagshipContext; +use Flagship\Enum\FlagshipConstant; +use Flagship\Config\BucketingConfig; +use Flagship\Model\FetchFlagsStatus; +use Flagship\Config\DecisionApiConfig; use Flagship\Utils\ContainerInterface; -use Flagship\Utils\Utils; -use PHPUnit\Framework\TestCase; -use ReflectionException; +use Flagship\Api\TrackingManagerAbstract; -class VisitorDelegateTest extends TestCase +class VisitorDelegateTest extends BaseTestCase { public function testVisitorDelegateConstruct() { + $this->mockRoundFunction(); + $configData = [ - 'envId' => 'env_value', - 'apiKey' => 'key_value', - ]; + 'envId' => 'env_value', + 'apiKey' => 'key_value', + ]; $config = new DecisionApiConfig($configData['envId'], $configData['apiKey']); $visitorId = "visitor_id"; $newVisitorId = 'new_visitor_id'; $ageKey = 'age'; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - "sdk_osName" => PHP_OS, - "sdk_deviceType" => "server", - FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, - FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::FS_USERS => $visitorId, - ]; + 'name' => 'visitor_name', + 'age' => 25, + "sdk_osName" => PHP_OS, + "sdk_deviceType" => "server", + FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, + FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::FS_USERS => $visitorId, + ]; $trackerManager = $this->getMockForAbstractClass( TrackingManagerAbstract::class, @@ -130,9 +131,9 @@ public function testVisitorDelegateConstruct() public function testSetAnonymous() { $configData = [ - 'envId' => 'env_value', - 'apiKey' => 'key_value', - ]; + 'envId' => 'env_value', + 'apiKey' => 'key_value', + ]; $config = new DecisionApiConfig($configData['envId'], $configData['apiKey']); $visitorId = "visitor_id"; @@ -175,18 +176,18 @@ public function testSetVisitorLog() ); $configData = [ - 'envId' => 'env_value', - 'apiKey' => 'key_value', - ]; + 'envId' => 'env_value', + 'apiKey' => 'key_value', + ]; $config = new DecisionApiConfig($configData['envId'], $configData['apiKey']); $config->setLogManager($logManagerStub); $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $trackerManager = $this->getMockForAbstractClass( TrackingManagerAbstract::class, @@ -227,16 +228,16 @@ public function testMethods() ['error'] ); $configData = [ - 'envId' => 'env_value', - 'apiKey' => 'key_value', - ]; + 'envId' => 'env_value', + 'apiKey' => 'key_value', + ]; $config = new DecisionApiConfig($configData['envId'], $configData['apiKey']); $visitorId = "visitor_id"; $visitorContext = [ - 'name' => 'visitor_name', - 'age' => 25, - ]; + 'name' => 'visitor_name', + 'age' => 25, + ]; $trackerManager = $this->getMockForAbstractClass( TrackingManagerAbstract::class, @@ -254,21 +255,21 @@ public function testMethods() )->onlyMethods(['get'])->disableOriginalConstructor()->getMock(); $defaultStrategy = $this->getMockBuilder('Flagship\Visitor\DefaultStrategy')->onlyMethods([ - 'initialContext', - 'updateContext', - 'updateContextCollection', - "cacheVisitor", - 'clearContext', - 'authenticate', - 'unauthenticate', - 'setConsent', - 'sendHit', - 'fetchFlags', - 'visitorExposed', - 'getFlagValue', - 'getFlagMetadata', - 'lookupVisitor', - ])->disableOriginalConstructor()->getMock(); + 'initialContext', + 'updateContext', + 'updateContextCollection', + "cacheVisitor", + 'clearContext', + 'authenticate', + 'unauthenticate', + 'setConsent', + 'sendHit', + 'fetchFlags', + 'visitorExposed', + 'getFlagValue', + 'getFlagMetadata', + 'lookupVisitor', + ])->disableOriginalConstructor()->getMock(); $containerMock->method('get')->willReturn($defaultStrategy); @@ -382,13 +383,13 @@ public function testJson() $config = new DecisionApiConfig(); $visitorId = "visitor_id"; $context = [ - "age" => 20, - "sdk_osName" => PHP_OS, - "sdk_deviceType" => "server", - FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, - FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, - FlagshipConstant::FS_USERS => $visitorId, - ]; + "age" => 20, + "sdk_osName" => PHP_OS, + "sdk_deviceType" => "server", + FlagshipConstant::FS_CLIENT => FlagshipConstant::SDK_LANGUAGE, + FlagshipConstant::FS_VERSION => FlagshipConstant::SDK_VERSION, + FlagshipConstant::FS_USERS => $visitorId, + ]; $trackerManager = $this->getMockForAbstractClass( TrackingManagerAbstract::class, [], @@ -403,10 +404,10 @@ public function testJson() $this->assertJsonStringEqualsJsonString( json_encode([ - 'visitorId' => $visitorId, - 'context' => $context, - 'hasConsent' => true, - ]), + 'visitorId' => $visitorId, + 'context' => $context, + 'hasConsent' => true, + ]), json_encode($visitorDelegate) ); }