From fe0bc77d991ae5d8ffcfd78f6f3d53f665951f11 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Tue, 13 Jul 2021 17:37:48 +0300 Subject: [PATCH 01/15] Flag-related tasks 1. Add numeric flag types RoxInt+RoxDouble or RoxNumber and rename Variant to RoxString 2. Flag evaluations consistency and extreme cases handle 3. Validate Dynamic and Static API --- composer.lock | 97 ++--- src/Rox/Core/Client/DynamicApi.php | 63 ++- src/Rox/Core/Client/DynamicApiInterface.php | 24 +- src/Rox/Core/Client/InternalFlags.php | 13 +- .../FlagAddedCallbackArgs.php | 10 +- .../Core/Entities/BoolFlagValueConverter.php | 44 +++ .../Core/Entities/BooleanFlagInterface.php | 27 ++ src/Rox/Core/Entities/DoubleFlagInterface.php | 14 + .../Entities/DoubleFlagValueConverter.php | 48 +++ .../Entities/EntitiesProviderInterface.php | 24 +- src/Rox/Core/Entities/Flag.php | 65 ---- src/Rox/Core/Entities/FlagSetter.php | 6 +- src/Rox/Core/Entities/FlagValueConverter.php | 29 ++ src/Rox/Core/Entities/FlagValueConverters.php | 93 +++++ src/Rox/Core/Entities/IntFlagInterface.php | 14 + .../Core/Entities/IntFlagValueConverter.php | 46 +++ src/Rox/Core/Entities/RoxStringBase.php | 331 ++++++++++++++++ src/Rox/Core/Entities/StringFlagInterface.php | 14 + .../Entities/StringFlagValueConverter.php | 38 ++ src/Rox/Core/Entities/Variant.php | 218 ----------- .../Core/Impression/Models/ReportingValue.php | 17 +- src/Rox/Core/Register/Registerer.php | 4 +- src/Rox/Core/Repositories/FlagRepository.php | 8 +- .../Repositories/FlagRepositoryInterface.php | 6 +- src/Rox/Core/Roxx/EvaluationContext.php | 27 ++ src/Rox/Core/Roxx/EvaluationResult.php | 25 +- src/Rox/Core/Roxx/ExperimentsExtensions.php | 8 +- src/Rox/Core/Roxx/Parser.php | 9 +- src/Rox/Core/Roxx/ParserInterface.php | 3 +- src/Rox/Core/XPack/Network/StateSender.php | 4 +- src/Rox/Server/Flags/RoxDouble.php | 37 ++ src/Rox/Server/Flags/RoxFlag.php | 62 ++- src/Rox/Server/Flags/RoxInt.php | 37 ++ src/Rox/Server/Flags/RoxString.php | 37 ++ src/Rox/Server/Flags/RoxVariant.php | 10 - .../Server/Flags/ServerEntitiesProvider.php | 20 +- tests/Rox/Core/Client/DynamicApiTests.php | 99 ----- tests/Rox/Core/Client/InternalFlagsTests.php | 9 +- tests/Rox/Core/Entities/FlagSetterTests.php | 15 +- tests/Rox/Core/Entities/FlagTests.php | 46 --- tests/Rox/Core/Entities/VariantTests.php | 116 ------ tests/Rox/Core/Register/TestContainer.php | 14 +- .../Core/Repositories/FlagRepositoryTests.php | 6 +- .../Core/Roxx/ExperimentsExtensionsTests.php | 46 +-- .../Core/XPack/Network/StateSenderTests.php | 40 +- tests/Rox/E2E/Container.php | 22 +- tests/Rox/E2E/ContainerTwo.php | 6 +- tests/Rox/Server/Flags/DynamicApiTests.php | 359 ++++++++++++++++++ tests/Rox/Server/Flags/RoxDoubleTests.php | 119 ++++++ tests/Rox/Server/Flags/RoxFlagTests.php | 123 ++++++ tests/Rox/Server/Flags/RoxFlagsTestCase.php | 175 +++++++++ tests/Rox/Server/Flags/RoxIntTests.php | 121 ++++++ tests/Rox/Server/Flags/RoxStringTests.php | 203 ++++++++++ 53 files changed, 2301 insertions(+), 750 deletions(-) create mode 100644 src/Rox/Core/Entities/BoolFlagValueConverter.php create mode 100644 src/Rox/Core/Entities/BooleanFlagInterface.php create mode 100644 src/Rox/Core/Entities/DoubleFlagInterface.php create mode 100644 src/Rox/Core/Entities/DoubleFlagValueConverter.php delete mode 100644 src/Rox/Core/Entities/Flag.php create mode 100644 src/Rox/Core/Entities/FlagValueConverter.php create mode 100644 src/Rox/Core/Entities/FlagValueConverters.php create mode 100644 src/Rox/Core/Entities/IntFlagInterface.php create mode 100644 src/Rox/Core/Entities/IntFlagValueConverter.php create mode 100644 src/Rox/Core/Entities/RoxStringBase.php create mode 100644 src/Rox/Core/Entities/StringFlagInterface.php create mode 100644 src/Rox/Core/Entities/StringFlagValueConverter.php delete mode 100644 src/Rox/Core/Entities/Variant.php create mode 100644 src/Rox/Core/Roxx/EvaluationContext.php create mode 100644 src/Rox/Server/Flags/RoxDouble.php create mode 100644 src/Rox/Server/Flags/RoxInt.php create mode 100644 src/Rox/Server/Flags/RoxString.php delete mode 100644 src/Rox/Server/Flags/RoxVariant.php delete mode 100644 tests/Rox/Core/Client/DynamicApiTests.php delete mode 100644 tests/Rox/Core/Entities/FlagTests.php delete mode 100644 tests/Rox/Core/Entities/VariantTests.php create mode 100644 tests/Rox/Server/Flags/DynamicApiTests.php create mode 100644 tests/Rox/Server/Flags/RoxDoubleTests.php create mode 100644 tests/Rox/Server/Flags/RoxFlagTests.php create mode 100644 tests/Rox/Server/Flags/RoxFlagsTestCase.php create mode 100644 tests/Rox/Server/Flags/RoxIntTests.php create mode 100644 tests/Rox/Server/Flags/RoxStringTests.php diff --git a/composer.lock b/composer.lock index 84ab6de..7c39e83 100644 --- a/composer.lock +++ b/composer.lock @@ -153,16 +153,16 @@ }, { "name": "guzzlehttp/promises", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "60d379c243457e073cff02bc323a2a86cb355631" + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", - "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", "shasum": "" }, "require": { @@ -202,22 +202,22 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.0" + "source": "https://github.com/guzzle/promises/tree/1.4.1" }, - "time": "2020-09-30T07:37:28+00:00" + "time": "2021-03-07T09:25:29+00:00" }, { "name": "guzzlehttp/psr7", - "version": "1.7.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3" + "reference": "dc960a912984efb74d0a90222870c72c87f10c91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3", - "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91", "shasum": "" }, "require": { @@ -277,32 +277,33 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.7.0" + "source": "https://github.com/guzzle/psr7/tree/1.8.2" }, - "time": "2020-09-30T07:37:11+00:00" + "time": "2021-04-26T09:17:50+00:00" }, { "name": "kevinrob/guzzle-cache-middleware", - "version": "v3.3.1", + "version": "v3.4.1", "source": { "type": "git", "url": "https://github.com/Kevinrob/guzzle-cache-middleware.git", - "reference": "f978b8da7484a16e26589a5518d6bacc6ccdee99" + "reference": "122e309f64934511146a88d5645599f561b6fae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Kevinrob/guzzle-cache-middleware/zipball/f978b8da7484a16e26589a5518d6bacc6ccdee99", - "reference": "f978b8da7484a16e26589a5518d6bacc6ccdee99", + "url": "https://api.github.com/repos/Kevinrob/guzzle-cache-middleware/zipball/122e309f64934511146a88d5645599f561b6fae3", + "reference": "122e309f64934511146a88d5645599f561b6fae3", "shasum": "" }, "require": { + "guzzlehttp/guzzle": "^6.0 || ^7.0", + "guzzlehttp/psr7": "^1.7.0", "php": ">=5.5.0" }, "require-dev": { "cache/array-adapter": "^0.4 || ^0.5 || ^1.0", "cache/simple-cache-bridge": "^0.1 || ^1.0", "doctrine/cache": "^1.0", - "guzzlehttp/guzzle": "^6.0", "illuminate/cache": "^5.0", "league/flysystem": "^1.0", "phpunit/phpunit": "^4.8.36 || ^5.0", @@ -359,22 +360,22 @@ ], "support": { "issues": "https://github.com/Kevinrob/guzzle-cache-middleware/issues", - "source": "https://github.com/Kevinrob/guzzle-cache-middleware/tree/v3.3.1" + "source": "https://github.com/Kevinrob/guzzle-cache-middleware/tree/v3.4.1" }, - "time": "2020-02-14T11:17:02+00:00" + "time": "2021-07-11T09:00:28+00:00" }, { "name": "monolog/monolog", - "version": "1.26.0", + "version": "1.26.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33" + "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/2209ddd84e7ef1256b7af205d0717fb62cfc9c33", - "reference": "2209ddd84e7ef1256b7af205d0717fb62cfc9c33", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", + "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", "shasum": "" }, "require": { @@ -435,7 +436,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.26.0" + "source": "https://github.com/Seldaek/monolog/tree/1.26.1" }, "funding": [ { @@ -447,20 +448,20 @@ "type": "tidelift" } ], - "time": "2020-12-14T12:56:38+00:00" + "time": "2021-05-28T08:32:12+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.19", + "version": "v2.0.20", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241" + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/446fc9faa5c2a9ddf65eb7121c0af7e857295241", - "reference": "446fc9faa5c2a9ddf65eb7121c0af7e857295241", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a", + "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a", "shasum": "" }, "require": { @@ -501,7 +502,7 @@ "issues": "https://github.com/paragonie/random_compat/issues", "source": "https://github.com/paragonie/random_compat" }, - "time": "2020-10-15T10:06:57+00:00" + "time": "2021-04-17T09:33:01+00:00" }, { "name": "psr/http-message", @@ -558,16 +559,16 @@ }, { "name": "psr/log", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { @@ -591,7 +592,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -602,9 +603,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ralouphie/getallheaders", @@ -1263,16 +1264,16 @@ }, { "name": "mockery/mockery", - "version": "1.3.3", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d" + "reference": "31467aeb3ca3188158613322d66df81cedd86626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/60fa2f67f6e4d3634bb4a45ff3171fa52215800d", - "reference": "60fa2f67f6e4d3634bb4a45ff3171fa52215800d", + "url": "https://api.github.com/repos/mockery/mockery/zipball/31467aeb3ca3188158613322d66df81cedd86626", + "reference": "31467aeb3ca3188158613322d66df81cedd86626", "shasum": "" }, "require": { @@ -1326,9 +1327,9 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.3.3" + "source": "https://github.com/mockery/mockery/tree/1.3.4" }, - "time": "2020-08-11T18:10:21+00:00" + "time": "2021-02-24T09:51:00+00:00" }, { "name": "myclabs/deep-copy", @@ -2662,12 +2663,12 @@ "version": "1.9.1", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", + "url": "https://github.com/webmozarts/assert.git", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "shasum": "" }, @@ -2705,8 +2706,8 @@ "validate" ], "support": { - "issues": "https://github.com/webmozart/assert/issues", - "source": "https://github.com/webmozart/assert/tree/master" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" }, "time": "2020-07-08T17:02:28+00:00" } @@ -2725,5 +2726,5 @@ "platform-overrides": { "php": "5.6" }, - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.1.0" } diff --git a/src/Rox/Core/Client/DynamicApi.php b/src/Rox/Core/Client/DynamicApi.php index becd6e8..f6ea672 100644 --- a/src/Rox/Core/Client/DynamicApi.php +++ b/src/Rox/Core/Client/DynamicApi.php @@ -2,9 +2,10 @@ namespace Rox\Core\Client; +use InvalidArgumentException; use Rox\Core\Context\ContextInterface; use Rox\Core\Entities\EntitiesProviderInterface; -use Rox\Core\Entities\Flag; +use Rox\Core\Entities\FlagValueConverters; use Rox\Core\Repositories\FlagRepositoryInterface; class DynamicApi implements DynamicApiInterface @@ -33,44 +34,68 @@ public function __construct( } /** - * @param string $name - * @param string $defaultValue - * @param ContextInterface|null $context - * @return bool + * @inheritDoc */ function isEnabled($name, $defaultValue, ContextInterface $context = null) { + $this->checkName($name); $variant = $this->_flagRepository->getFlag($name); if ($variant == null) { $variant = $this->_entitiesProvider->createFlag($defaultValue); $this->_flagRepository->addFlag($variant, $name); } + return $variant->getBooleanValue($context, FlagValueConverters::getInstance() + ->getBool()->convertToString($defaultValue)); + } - $flag = $variant; - if (!$flag instanceof Flag) { - return $defaultValue; + /** + * @inheritDoc + */ + function getValue($name, $defaultValue, $variations = [], ContextInterface $context = null) + { + $this->checkName($name); + $variant = $this->_flagRepository->getFlag($name); + if ($variant == null) { + $variant = $this->_entitiesProvider->createString($defaultValue, $variations); + $this->_flagRepository->addFlag($variant, $name); } + return $variant->getStringValue($context, $defaultValue); + } - $isEnabled = $flag->isEnabled($context, true); - return $isEnabled !== null ? $isEnabled : $defaultValue; + /** + * @inheritDoc + */ + function getInt($name, $defaultValue, $variations = [], ContextInterface $context = null) + { + $this->checkName($name); + $variant = $this->_flagRepository->getFlag($name); + if ($variant == null) { + $variant = $this->_entitiesProvider->createInt($defaultValue, $variations); + $this->_flagRepository->addFlag($variant, $name); + } + return $variant->getIntValue($context, FlagValueConverters::getInstance() + ->getInt()->convertToString($defaultValue)); } /** - * @param string $name - * @param string $defaultValue - * @param array $options - * @param ContextInterface|null $context - * @return string + * @inheritDoc */ - function getValue($name, $defaultValue, $options = [], ContextInterface $context = null) + function getDouble($name, $defaultValue, $variations = [], ContextInterface $context = null) { + $this->checkName($name); $variant = $this->_flagRepository->getFlag($name); if ($variant == null) { - $variant = $this->_entitiesProvider->createVariant($defaultValue, $options); + $variant = $this->_entitiesProvider->createDouble($defaultValue, $variations); $this->_flagRepository->addFlag($variant, $name); } + return $variant->getDoubleValue($context, FlagValueConverters::getInstance()->getDouble() + ->convertToString($defaultValue)); + } - $value = $variant->getValue($context, true); - return $value !== null ? $value : $defaultValue; + private function checkName($name) + { + if (!$name) { + throw new InvalidArgumentException("DynamicApi - name canot be null"); + } } } diff --git a/src/Rox/Core/Client/DynamicApiInterface.php b/src/Rox/Core/Client/DynamicApiInterface.php index 245385a..6c2328e 100644 --- a/src/Rox/Core/Client/DynamicApiInterface.php +++ b/src/Rox/Core/Client/DynamicApiInterface.php @@ -8,7 +8,7 @@ interface DynamicApiInterface { /** * @param string $name - * @param string $defaultValue + * @param bool $defaultValue * @param ContextInterface|null $context * @return bool */ @@ -17,9 +17,27 @@ function isEnabled($name, $defaultValue, ContextInterface $context = null); /** * @param string $name * @param string $defaultValue - * @param array $options + * @param array $variations * @param ContextInterface|null $context * @return string */ - function getValue($name, $defaultValue, $options = [], ContextInterface $context = null); + function getValue($name, $defaultValue, $variations = [], ContextInterface $context = null); + + /** + * @param string $name + * @param int $defaultValue + * @param array $variations + * @param ContextInterface|null $context + * @return int + */ + function getInt($name, $defaultValue, $variations = [], ContextInterface $context = null); + + /** + * @param string $name + * @param double $defaultValue + * @param array $variations + * @param ContextInterface|null $context + * @return double + */ + function getDouble($name, $defaultValue, $variations = [], ContextInterface $context = null); } diff --git a/src/Rox/Core/Client/InternalFlags.php b/src/Rox/Core/Client/InternalFlags.php index 329cc88..e8cffae 100644 --- a/src/Rox/Core/Client/InternalFlags.php +++ b/src/Rox/Core/Client/InternalFlags.php @@ -2,7 +2,7 @@ namespace Rox\Core\Client; -use Rox\Core\Entities\Flag; +use Rox\Core\Entities\BoolFlagValueConverter; use Rox\Core\Repositories\ExperimentRepositoryInterface; use Rox\Core\Roxx\ParserInterface; @@ -41,8 +41,10 @@ function isEnabled($flagName) if ($internalExperiment == null) { return false; } - $value = $this->_parser->evaluateExpression($internalExperiment->getCondition(), null)->stringValue(); - return $value === Flag::FLAG_TRUE_VALUE; + $value = $this->_parser + ->evaluateExpression($internalExperiment->getCondition(), null) + ->stringValue(); + return $value === BoolFlagValueConverter::FLAG_TRUE_VALUE; } /** @@ -55,7 +57,8 @@ function getIntValue($flagName) if ($internalExperiment == null) { return null; } - $value = $this->_parser->evaluateExpression($internalExperiment->getCondition(), null)->integerValue(); - return $value; + return $this->_parser + ->evaluateExpression($internalExperiment->getCondition(), null) + ->integerValue(); } } diff --git a/src/Rox/Core/CustomProperties/FlagAddedCallbackArgs.php b/src/Rox/Core/CustomProperties/FlagAddedCallbackArgs.php index 1c5ea47..e7638bd 100644 --- a/src/Rox/Core/CustomProperties/FlagAddedCallbackArgs.php +++ b/src/Rox/Core/CustomProperties/FlagAddedCallbackArgs.php @@ -2,26 +2,26 @@ namespace Rox\Core\CustomProperties; -use Rox\Core\Entities\Variant; +use Rox\Core\Entities\RoxStringBase; class FlagAddedCallbackArgs { /** - * @var Variant $_variant + * @var RoxStringBase $_variant */ private $_variant; /** * FlagAddedCallbackArgs constructor. - * @param Variant $variant + * @param RoxStringBase $variant */ - public function __construct(Variant $variant) + public function __construct(RoxStringBase $variant) { $this->_variant = $variant; } /** - * @return Variant + * @return RoxStringBase */ public function getVariant() { diff --git a/src/Rox/Core/Entities/BoolFlagValueConverter.php b/src/Rox/Core/Entities/BoolFlagValueConverter.php new file mode 100644 index 0000000..8196972 --- /dev/null +++ b/src/Rox/Core/Entities/BoolFlagValueConverter.php @@ -0,0 +1,44 @@ +warning("Experiment type mismatch (double), returning default value"); + } + } + return floatval($alternativeValue); + } +} diff --git a/src/Rox/Core/Entities/EntitiesProviderInterface.php b/src/Rox/Core/Entities/EntitiesProviderInterface.php index 92e1d13..4e562ad 100644 --- a/src/Rox/Core/Entities/EntitiesProviderInterface.php +++ b/src/Rox/Core/Entities/EntitiesProviderInterface.php @@ -5,15 +5,29 @@ interface EntitiesProviderInterface { /** - * @param string $defaultValue - * @return Flag + * @param bool $defaultValue + * @return RoxStringBase */ function createFlag($defaultValue); /** * @param string $defaultValue - * @param array $options - * @return Variant + * @param array $variations + * @return RoxStringBase + */ + function createString($defaultValue, array $variations); + + /** + * @param int $defaultValue + * @param array $variations + * @return RoxStringBase + */ + function createInt($defaultValue, array $variations); + + /** + * @param double $defaultValue + * @param array $variations + * @return RoxStringBase */ - function createVariant($defaultValue, array $options); + function createDouble($defaultValue, array $variations); } diff --git a/src/Rox/Core/Entities/Flag.php b/src/Rox/Core/Entities/Flag.php deleted file mode 100644 index e64c39b..0000000 --- a/src/Rox/Core/Entities/Flag.php +++ /dev/null @@ -1,65 +0,0 @@ -getValue($context, $nullInsteadOfDefault); - return $nullInsteadOfDefault && ($value === null) ? null : $this->isEnabledFromString($value); - } - - /** - * @param ContextInterface|null $context - * @param callable $action - */ - public function enabled($context, callable $action) - { - if ($this->isEnabled($context)) { - $action(); - } - } - - /** - * @param ContextInterface|null $context - * @param callable $action - */ - public function disabled($context, callable $action) - { - if (!$this->isEnabled($context)) { - $action(); - } - } - - /** - * @param string $value - * @return bool - */ - public function isEnabledFromString($value) - { - return $value === self::FLAG_TRUE_VALUE; - } -} diff --git a/src/Rox/Core/Entities/FlagSetter.php b/src/Rox/Core/Entities/FlagSetter.php index 4248de3..1d9d085 100644 --- a/src/Rox/Core/Entities/FlagSetter.php +++ b/src/Rox/Core/Entities/FlagSetter.php @@ -42,7 +42,7 @@ public function __construct( FlagRepositoryInterface $flagRepository, ParserInterface $parser, ExperimentRepositoryInterface $experimentRepository, - $impressionInvoker = null) + ImpressionInvokerInterface $impressionInvoker = null) { $this->_flagRepository = $flagRepository; $this->_parser = $parser; @@ -68,14 +68,14 @@ public function setExperiments() } } - foreach (array_values($this->_flagRepository->getAllFlags()) as /*Variant*/ $variant) { + foreach (array_values($this->_flagRepository->getAllFlags()) as /*RoxStringBase*/ $variant) { if (!in_array($variant->getName(), $flagsWithCondition)) { $this->_setFlagData($variant); } } } - private function _setFlagData(Variant $variant, ExperimentModel $experiment = null) + private function _setFlagData(RoxStringBase $variant, ExperimentModel $experiment = null) { $variant->setForEvaluation($this->_parser, $experiment, $this->_impressionInvoker); } diff --git a/src/Rox/Core/Entities/FlagValueConverter.php b/src/Rox/Core/Entities/FlagValueConverter.php new file mode 100644 index 0000000..90c8318 --- /dev/null +++ b/src/Rox/Core/Entities/FlagValueConverter.php @@ -0,0 +1,29 @@ +_int; + } + + /** + * @return FlagValueConverter + */ + public function getDouble() + { + return $this->_double; + } + + /** + * @return FlagValueConverter + */ + public function getBool() + { + return $this->_bool; + } + + /** + * @return FlagValueConverter + */ + public function getString() + { + return $this->_string; + } + + private function __construct() + { + $this->_bool = new BoolFlagValueConverter(); + $this->_string = new StringFlagValueConverter(); + $this->_int = new IntFlagValueConverter(); + $this->_double = new DoubleFlagValueConverter(); + } + + private function __clone() + { + } + + public function __wakeup() + { + throw new RuntimeException("Cannot deserialize singleton"); + } +} diff --git a/src/Rox/Core/Entities/IntFlagInterface.php b/src/Rox/Core/Entities/IntFlagInterface.php new file mode 100644 index 0000000..47a3dec --- /dev/null +++ b/src/Rox/Core/Entities/IntFlagInterface.php @@ -0,0 +1,14 @@ +warning("Experiment type mismatch (int), returning default value"); + } + } + return intval($alternativeValue); + } + +} \ No newline at end of file diff --git a/src/Rox/Core/Entities/RoxStringBase.php b/src/Rox/Core/Entities/RoxStringBase.php new file mode 100644 index 0000000..ce0d899 --- /dev/null +++ b/src/Rox/Core/Entities/RoxStringBase.php @@ -0,0 +1,331 @@ +getArrayCopy(); + $allVariations[] = $defaultValue; + $this->_variations = $allVariations; + } else { + $this->_variations = (new ArrayObject($variations))->getArrayCopy(); + } + $this->_defaultValue = $defaultValue; + $this->_log = LoggerFactory::getDefaultFactory()->createLogger(RoxStringBase::class); + } + + /** + * @param mixed $value + * @retrun mixed Value. + * @throws InvalidArgumentException + */ + protected function checkValueType($value) + { + if (!$this->getConverter()->isValid($value)) { + throw new InvalidArgumentException("Invalid value type: ${value}"); + } + return $value; + } + + /** + * @param array $variations + * @return array Variations. + * @throws InvalidArgumentException + */ + protected function checkVariationsType(array $variations) + { + if ($invalidVariations = array_filter($variations, function ($value) { + return !$this->getConverter()->isValid($value); + })) { + $invalidValue = array_shift($invalidVariations); + throw new InvalidArgumentException("Invalid variation type: ${invalidValue}"); + } + return $variations; + } + + /** + * @return FlagValueConverter + */ + protected abstract function getConverter(); + + /** + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * @return string + */ + public function getDefaultValue() + { + return $this->_defaultValue; + } + + /** + * @return string[] + */ + public function getVariations() + { + return $this->_variations; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->_name = $name; + } + + /** + * @return string + */ + public function getCondition() + { + return $this->_condition; + } + + /** + * @param string $condition + */ + public function setCondition($condition) + { + $this->_condition = $condition; + } + + /** + * @return ParserInterface + */ + public function getParser() + { + return $this->_parser; + } + + /** + * @param ParserInterface $parser + */ + public function setParser($parser) + { + $this->_parser = $parser; + } + + /** + * @return ContextInterface + */ + public function getGlobalContext() + { + return $this->_globalContext; + } + + /** + * @return ImpressionInvokerInterface + */ + public function getImpressionInvoker() + { + return $this->_impressionInvoker; + } + + /** + * @param ImpressionInvokerInterface $impressionInvoker + */ + public function setImpressionInvoker($impressionInvoker) + { + $this->_impressionInvoker = $impressionInvoker; + } + + /** + * @return ExperimentModel + */ + public function getExperiment() + { + return $this->_experiment; + } + + /** + * @param ParserInterface|null $parser + * @param ExperimentModel|null $experiment + * @param ImpressionInvokerInterface|null $impressionInvoker + */ + public function setForEvaluation($parser, $experiment, $impressionInvoker) + { + if ($experiment != null) { + $this->_experiment = $experiment; + $this->_condition = $experiment->getCondition(); + } else { + $this->_experiment = null; + $this->_condition = ""; + } + + $this->_parser = $parser; + $this->_impressionInvoker = $impressionInvoker; + } + + public function setContext(ContextInterface $globalContext) + { + $this->_globalContext = $globalContext; + } + + /** + * @param ContextInterface|null $context + * @param string|null $alternativeDefaultValue + * @param EvaluationContext|null $evaluationContext + * @return string + */ + public function getStringValue($context = null, $alternativeDefaultValue = null, $evaluationContext = null) + { + return $this->_getFlagValue( + FlagValueConverters::getInstance()->getString(), + $context, $alternativeDefaultValue, $evaluationContext); + } + + /** + * @param ContextInterface|null $context + * @param string|null $alternativeDefaultValue + * @param EvaluationContext|null $evaluationContext + * @return int + */ + public function getIntValue($context = null, $alternativeDefaultValue = null, $evaluationContext = null) + { + return $this->_getFlagValue( + FlagValueConverters::getInstance()->getInt(), + $context, $alternativeDefaultValue, $evaluationContext); + } + + /** + * @param ContextInterface|null $context + * @param string|null $alternativeDefaultValue + * @param EvaluationContext|null $evaluationContext + * @return double + */ + public function getDoubleValue($context = null, $alternativeDefaultValue = null, $evaluationContext = null) + { + return $this->_getFlagValue( + FlagValueConverters::getInstance()->getDouble(), + $context, $alternativeDefaultValue, $evaluationContext); + } + + /** + * @param ContextInterface|null $context + * @param string|null $alternativeDefaultValue + * @param EvaluationContext|null $evaluationContext + * @return bool + */ + public function getBooleanValue($context = null, $alternativeDefaultValue = null, $evaluationContext = null) + { + return $this->_getFlagValue( + FlagValueConverters::getInstance()->getBool(), + $context, $alternativeDefaultValue, $evaluationContext); + } + + /** + * @param FlagValueConverter $converter + * @param ContextInterface $context + * @param string|null $alternativeDefaultValue + * @param EvaluationContext|null $evaluationContext + * @return mixed + */ + private function _getFlagValue(FlagValueConverter $converter, $context = null, $alternativeDefaultValue = null, $evaluationContext = null) + { + $evaluation = $this->_getExperimentValue($context, $evaluationContext); + $experimentStringValue = $evaluation->stringValue(); + $finalValue = $converter->normalizeValue($experimentStringValue, + $alternativeDefaultValue ?: $this->_defaultValue, $this->_log); + if (!$evaluationContext || $evaluationContext->isShouldRaiseImpressionHandler()) { + if ($this->_impressionInvoker != null) { + $this->_impressionInvoker->invoke( + new ReportingValue($this->_name, $converter->convertToString($finalValue), !!$this->_experiment), + $this->_experiment, $evaluation->getUsedContext()); + } + } + return $finalValue; + } + + /** + * @param ContextInterface|null $context + * @param EvaluationContext|null $evaluationContext + * @return EvaluationResult + */ + private function _getExperimentValue($context, $evaluationContext = null) + { + $mergedContext = new MergedContext($this->_globalContext, $context); + if ($this->_parser != null && $this->_condition) { + return $this->_parser->evaluateExpression($this->_condition, $mergedContext, $evaluationContext); + } + return new EvaluationResult(null, $mergedContext); + } +} diff --git a/src/Rox/Core/Entities/StringFlagInterface.php b/src/Rox/Core/Entities/StringFlagInterface.php new file mode 100644 index 0000000..dea1b6c --- /dev/null +++ b/src/Rox/Core/Entities/StringFlagInterface.php @@ -0,0 +1,14 @@ +getArrayCopy(); - $allOptions[] = $defaultValue; - $this->_options = $allOptions; - } else { - $this->_options = (new ArrayObject($options))->getArrayCopy(); - } - $this->_defaultValue = $defaultValue; - } - - /** - * @return string - */ - public function getName() - { - return $this->_name; - } - - /** - * @return string - */ - public function getDefaultValue() - { - return $this->_defaultValue; - } - - /** - * @return string[] - */ - public function getOptions() - { - return $this->_options; - } - - /** - * @param string $name - */ - public function setName($name) - { - $this->_name = $name; - } - - /** - * @return string - */ - public function getCondition() - { - return $this->_condition; - } - - /** - * @param string $condition - */ - public function setCondition($condition) - { - $this->_condition = $condition; - } - - /** - * @return ParserInterface - */ - public function getParser() - { - return $this->_parser; - } - - /** - * @param ParserInterface $parser - */ - public function setParser($parser) - { - $this->_parser = $parser; - } - - /** - * @return ContextInterface - */ - public function getGlobalContext() - { - return $this->_globalContext; - } - - /** - * @return ImpressionInvokerInterface - */ - public function getImpressionInvoker() - { - return $this->_impressionInvoker; - } - - /** - * @param ImpressionInvokerInterface $impressionInvoker - */ - public function setImpressionInvoker($impressionInvoker) - { - $this->_impressionInvoker = $impressionInvoker; - } - - /** - * @return ExperimentModel - */ - public function getExperiment() - { - return $this->_experiment; - } - - /** - * @param ParserInterface|null $parser - * @param ExperimentModel|null $experiment - * @param ImpressionInvokerInterface|null $impressionInvoker - */ - public function setForEvaluation($parser, $experiment, $impressionInvoker) - { - if ($experiment != null) { - $this->_experiment = $experiment; - $this->_condition = $experiment->getCondition(); - } else { - $this->_experiment = null; - $this->_condition = ""; - } - - $this->_parser = $parser; - $this->_impressionInvoker = $impressionInvoker; - } - - public function setContext(ContextInterface $globalContext) - { - $this->_globalContext = $globalContext; - } - - /** - * @param ContextInterface|null $context - * @param bool $nullInsteadOfDefault - * @return mixed - */ - public function getValue($context = null, $nullInsteadOfDefault = false) - { - $returnValue = $nullInsteadOfDefault ? null : $this->_defaultValue; - $mergedContext = new MergedContext($this->_globalContext, $context); - - if ($this->_parser != null && $this->_condition) { - $evaluationResult = $this->_parser->evaluateExpression($this->_condition, $mergedContext); - if ($evaluationResult != null && $evaluationResult->stringValue()) { - $value = $evaluationResult->stringValue(); - if ($value) { - $returnValue = $value; - } - } - } - - if ($this->_impressionInvoker != null) { - $this->_impressionInvoker->invoke(new ReportingValue($this->_name, $returnValue), $this->_experiment, $mergedContext); - } - - return $returnValue; - } -} diff --git a/src/Rox/Core/Impression/Models/ReportingValue.php b/src/Rox/Core/Impression/Models/ReportingValue.php index 6e89c94..d5df882 100644 --- a/src/Rox/Core/Impression/Models/ReportingValue.php +++ b/src/Rox/Core/Impression/Models/ReportingValue.php @@ -14,15 +14,22 @@ class ReportingValue */ private $_value; + /** + * @var bool $_targeting + */ + private $_targeting; + /** * ReportingValue constructor. * @param string $name * @param string $value + * @param bool $targeting */ - public function __construct($name, $value) + public function __construct($name, $value, $targeting = false) { $this->_name = $name; $this->_value = $value; + $this->_targeting = $targeting; } /** @@ -41,6 +48,14 @@ public function getValue() return $this->_value; } + /** + * @return bool + */ + public function isTargeting() + { + return $this->_targeting; + } + /** * @return string */ diff --git a/src/Rox/Core/Register/Registerer.php b/src/Rox/Core/Register/Registerer.php index 2758a22..0ab965e 100644 --- a/src/Rox/Core/Register/Registerer.php +++ b/src/Rox/Core/Register/Registerer.php @@ -6,7 +6,7 @@ use Psr\Log\LoggerInterface; use ReflectionClass; use ReflectionException; -use Rox\Core\Entities\Variant; +use Rox\Core\Entities\RoxStringBase; use Rox\Core\Logging\LoggerFactory; use Rox\Core\Repositories\FlagRepositoryInterface; @@ -70,7 +70,7 @@ public function registerInstance($container, $ns) } foreach ($properties as $name => $value) { - if ($value instanceof Variant) { + if ($value instanceof RoxStringBase) { if ($ns) { $name = "${ns}.${name}"; } diff --git a/src/Rox/Core/Repositories/FlagRepository.php b/src/Rox/Core/Repositories/FlagRepository.php index 5dc4506..f2c97b9 100644 --- a/src/Rox/Core/Repositories/FlagRepository.php +++ b/src/Rox/Core/Repositories/FlagRepository.php @@ -3,7 +3,7 @@ namespace Rox\Core\Repositories; use Rox\Core\CustomProperties\FlagAddedCallbackArgs; -use Rox\Core\Entities\Variant; +use Rox\Core\Entities\RoxStringBase; class FlagRepository implements FlagRepositoryInterface { @@ -18,7 +18,7 @@ class FlagRepository implements FlagRepositoryInterface private $_callbacks = []; /** - * @param Variant $variant + * @param RoxStringBase $variant * @param string $name * @return void */ @@ -34,7 +34,7 @@ function addFlag($variant, $name) /** * @param string $name - * @return Variant|null + * @return RoxStringBase|null */ function getFlag($name) { @@ -63,7 +63,7 @@ function addFlagAddedCallback($callback) } /** - * @param Variant $variant + * @param RoxStringBase $variant */ private function _fireFlagAdded($variant) { diff --git a/src/Rox/Core/Repositories/FlagRepositoryInterface.php b/src/Rox/Core/Repositories/FlagRepositoryInterface.php index 8a1e9db..aa82c1a 100644 --- a/src/Rox/Core/Repositories/FlagRepositoryInterface.php +++ b/src/Rox/Core/Repositories/FlagRepositoryInterface.php @@ -2,12 +2,12 @@ namespace Rox\Core\Repositories; -use Rox\Core\Entities\Variant; +use Rox\Core\Entities\RoxStringBase; interface FlagRepositoryInterface { /** - * @param Variant $variant + * @param RoxStringBase $variant * @param string $name * @return void */ @@ -15,7 +15,7 @@ function addFlag($variant, $name); /** * @param string $name - * @return Variant|null + * @return RoxStringBase|null */ function getFlag($name); diff --git a/src/Rox/Core/Roxx/EvaluationContext.php b/src/Rox/Core/Roxx/EvaluationContext.php new file mode 100644 index 0000000..df91570 --- /dev/null +++ b/src/Rox/Core/Roxx/EvaluationContext.php @@ -0,0 +1,27 @@ +_shouldRaiseImpressionHandler; + } + + /** + * @param bool $shouldRaiseImpressionHandler + */ + public function setShouldRaiseImpressionHandler($shouldRaiseImpressionHandler) + { + $this->_shouldRaiseImpressionHandler = $shouldRaiseImpressionHandler; + } +} \ No newline at end of file diff --git a/src/Rox/Core/Roxx/EvaluationResult.php b/src/Rox/Core/Roxx/EvaluationResult.php index cd450d7..8a5ef3c 100644 --- a/src/Rox/Core/Roxx/EvaluationResult.php +++ b/src/Rox/Core/Roxx/EvaluationResult.php @@ -2,19 +2,30 @@ namespace Rox\Core\Roxx; -use Rox\Core\Entities\Flag; +use Rox\Core\Entities\BoolFlagValueConverter; class EvaluationResult { private $_value; + private $_usedContext; /** * EvaluationResult constructor. * @param mixed $value + * @param mixed $context */ - public function __construct($value) + public function __construct($value, $context = null) { $this->_value = $value; + $this->_usedContext = $context; + } + + /** + * @return mixed + */ + public function getUsedContext() + { + return $this->_usedContext; } /** @@ -62,11 +73,17 @@ public function doubleValue() public function stringValue() { if (is_string($this->_value)) { - return (string)$this->_value; + return $this->_value; + } + + if (is_numeric($this->_value)) { + return strval($this->_value); } if (is_bool($this->_value)) { - return $this->_value ? Flag::FLAG_TRUE_VALUE : Flag::FLAG_FALSE_VALUE; + return $this->_value + ? BoolFlagValueConverter::FLAG_TRUE_VALUE + : BoolFlagValueConverter::FLAG_FALSE_VALUE; } return null; diff --git a/src/Rox/Core/Roxx/ExperimentsExtensions.php b/src/Rox/Core/Roxx/ExperimentsExtensions.php index 4a3f260..be95241 100644 --- a/src/Rox/Core/Roxx/ExperimentsExtensions.php +++ b/src/Rox/Core/Roxx/ExperimentsExtensions.php @@ -3,7 +3,7 @@ namespace Rox\Core\Roxx; use Rox\Core\Context\ContextInterface; -use Rox\Core\Entities\Flag; +use Rox\Core\Entities\BoolFlagValueConverter; use Rox\Core\Repositories\ExperimentRepositoryInterface; use Rox\Core\Repositories\FlagRepositoryInterface; use Rox\Core\Repositories\TargetGroupRepositoryInterface; @@ -78,13 +78,13 @@ public function extend() $stack->push($isInPercentage); }); - $this->_parser->addOperator("flagValue", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { + $this->_parser->addOperator("flagValue", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context, EvaluationContext $evaluationContext = null) { $featureFlagIdentifier = (string)$stack->pop(); - $result = Flag::FLAG_FALSE_VALUE; + $result = BoolFlagValueConverter::FLAG_FALSE_VALUE; $variant = $this->_flagsRepository->getFlag($featureFlagIdentifier); if ($variant != null) { - $result = $variant->getValue($context); + $result = $variant->getStringValue($context, null, $evaluationContext); } else { $flagsExperiment = $this->_experimentRepository->getExperimentByFlag($featureFlagIdentifier); if ($flagsExperiment != null && $flagsExperiment->getCondition()) { diff --git a/src/Rox/Core/Roxx/Parser.php b/src/Rox/Core/Roxx/Parser.php index f770c8a..4d92081 100644 --- a/src/Rox/Core/Roxx/Parser.php +++ b/src/Rox/Core/Roxx/Parser.php @@ -195,9 +195,10 @@ function (ParserInterface $parser, StackInterface $stack, ContextInterface $cont /** * @param string $expression * @param ContextInterface $context + * @param EvaluationContext $evaluationContext * @return EvaluationResult */ - public function evaluateExpression($expression, $context = null) + public function evaluateExpression($expression, $context = null, $evaluationContext = null) { if ($context == null) { $context = (new ContextBuilder())->build(); // Don't pass nulls anywhere, it's a bad practice. @@ -218,10 +219,10 @@ public function evaluateExpression($expression, $context = null) } else if ($node->getType() == Node::TYPE_RATOR) { $key = (string)$node->getValue(); if (array_key_exists($key, $this->_operatorsMap)) { - $this->_operatorsMap[$key]($this, $stack, $context); + $this->_operatorsMap[$key]($this, $stack, $context, $evaluationContext); } } else { - return new EvaluationResult($result); + return new EvaluationResult($result, $context); } } @@ -234,6 +235,6 @@ public function evaluateExpression($expression, $context = null) ]); } - return new EvaluationResult($result); + return new EvaluationResult($result, $context); } } diff --git a/src/Rox/Core/Roxx/ParserInterface.php b/src/Rox/Core/Roxx/ParserInterface.php index 6b4ce6a..5d9698c 100644 --- a/src/Rox/Core/Roxx/ParserInterface.php +++ b/src/Rox/Core/Roxx/ParserInterface.php @@ -9,9 +9,10 @@ interface ParserInterface /** * @param string $expression * @param ContextInterface $context + * @param EvaluationContext $evaluationContext * @return EvaluationResult */ - function evaluateExpression($expression, $context); + function evaluateExpression($expression, $context, $evaluationContext = null); /** * @param string $name diff --git a/src/Rox/Core/XPack/Network/StateSender.php b/src/Rox/Core/XPack/Network/StateSender.php index 8a941ed..c418ec8 100644 --- a/src/Rox/Core/XPack/Network/StateSender.php +++ b/src/Rox/Core/XPack/Network/StateSender.php @@ -124,12 +124,12 @@ private function _serializeFeatureFlags() $keys = array_keys($allFlags); sort($keys); return array_map(function ($key) use (&$allFlags) { - $var = /*Variant*/ + $var = /*RoxStringBase*/ $allFlags[$key]; return [ 'name' => $var->getName(), 'defaultValue' => $var->getDefaultValue(), - 'options' => $var->getOptions(), + 'options' => $var->getVariations(), ]; }, $keys); } diff --git a/src/Rox/Server/Flags/RoxDouble.php b/src/Rox/Server/Flags/RoxDouble.php new file mode 100644 index 0000000..c2543af --- /dev/null +++ b/src/Rox/Server/Flags/RoxDouble.php @@ -0,0 +1,37 @@ +checkValueType($defaultValue), + $this->checkVariationsType($variations)); + } + + /** + * @inheritDoc + */ + protected function getConverter() + { + return FlagValueConverters::getInstance()->getDouble(); + } + + /** + * @inheritDoc + */ + function getValue($context = null) + { + return $this->getDoubleValue($context); + } +} diff --git a/src/Rox/Server/Flags/RoxFlag.php b/src/Rox/Server/Flags/RoxFlag.php index 4f29e9f..7a52f85 100644 --- a/src/Rox/Server/Flags/RoxFlag.php +++ b/src/Rox/Server/Flags/RoxFlag.php @@ -2,9 +2,65 @@ namespace Rox\Server\Flags; -use Rox\Core\Entities\Flag; +use Rox\Core\Context\ContextInterface; +use Rox\Core\Entities\BooleanFlagInterface; +use Rox\Core\Entities\BoolFlagValueConverter; +use Rox\Core\Entities\FlagValueConverters; +use Rox\Core\Entities\RoxStringBase; -class RoxFlag extends Flag +class RoxFlag extends RoxStringBase implements BooleanFlagInterface { + /** + * Flag constructor. + * @param bool $defaultValue + */ + public function __construct($defaultValue = false) + { + parent::__construct( + FlagValueConverters::getInstance() + ->getBool() + ->convertToString( + $this->checkValueType($defaultValue)), + [BoolFlagValueConverter::FLAG_FALSE_VALUE, + BoolFlagValueConverter::FLAG_TRUE_VALUE]); + } -} \ No newline at end of file + /** + * @inheritDoc + */ + protected function getConverter() + { + return FlagValueConverters::getInstance()->getBool(); + } + + /** + * @param ContextInterface|null $context + * @return bool + */ + public function isEnabled($context = null) + { + return $this->getBooleanValue($context); + } + + /** + * @param ContextInterface|null $context + * @param callable $action + */ + public function enabled($context, callable $action) + { + if ($this->isEnabled($context)) { + $action(); + } + } + + /** + * @param ContextInterface|null $context + * @param callable $action + */ + public function disabled($context, callable $action) + { + if (!$this->isEnabled($context)) { + $action(); + } + } +} diff --git a/src/Rox/Server/Flags/RoxInt.php b/src/Rox/Server/Flags/RoxInt.php new file mode 100644 index 0000000..31113b5 --- /dev/null +++ b/src/Rox/Server/Flags/RoxInt.php @@ -0,0 +1,37 @@ +checkValueType($defaultValue), + $this->checkVariationsType($variations)); + } + + /** + * @inheritDoc + */ + protected function getConverter() + { + return FlagValueConverters::getInstance()->getInt(); + } + + /** + * @inheritDoc + */ + function getValue($context = null) + { + return $this->getIntValue($context); + } +} diff --git a/src/Rox/Server/Flags/RoxString.php b/src/Rox/Server/Flags/RoxString.php new file mode 100644 index 0000000..09eb82a --- /dev/null +++ b/src/Rox/Server/Flags/RoxString.php @@ -0,0 +1,37 @@ +checkValueType($defaultValue), + $this->checkVariationsType($variations)); + } + + /** + * @inheritDoc + */ + protected function getConverter() + { + return FlagValueConverters::getInstance()->getString(); + } + + /** + * @inheritDoc + */ + function getValue($context = null) + { + return $this->getStringValue($context); + } +} diff --git a/src/Rox/Server/Flags/RoxVariant.php b/src/Rox/Server/Flags/RoxVariant.php deleted file mode 100644 index 946efd6..0000000 --- a/src/Rox/Server/Flags/RoxVariant.php +++ /dev/null @@ -1,10 +0,0 @@ -_ep = \Mockery::mock(EntitiesProviderInterface::class) - ->shouldReceive('createFlag') - ->andReturnUsing(function ($defaultValue) { - return new Flag($defaultValue); - }) - ->byDefault() - ->getMock() - ->shouldReceive('createVariant') - ->andReturnUsing(function ($defaultValue, array $options = []) { - return new Variant($defaultValue, $options); - }) - ->byDefault() - ->getMock(); - } - - public function testIsEnabled() - { - $parser = new Parser(); - $flagRepo = new FlagRepository(); - $expRepo = new ExperimentRepository(); - $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, null); - $dynamicApi = new DynamicApi($flagRepo, $this->_ep); - - $this->assertTrue($dynamicApi->isEnabled("default.newFlag", true)); - $this->assertTrue($flagRepo->getFlag("default.newFlag")->isEnabled(null)); - $this->assertFalse($dynamicApi->isEnabled("default.newFlag", false)); - $this->assertEquals(1, count($flagRepo->getAllFlags())); - - $expRepo->setExperiments( - [ - new ExperimentModel("1", "default.newFlag", "and(true, true)", false, ["default.newFlag"], [], "stam") - ]); - $flagSetter->setExperiments(); - - $this->assertTrue($dynamicApi->isEnabled("default.newFlag", false)); - } - - public function testIsEnabledAfterSetup() - { - $parser = new Parser(); - $flagRepo = new FlagRepository(); - $expRepo = new ExperimentRepository(); - $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, null); - $dynamicApi = new DynamicApi($flagRepo, $this->_ep); - - $expRepo->setExperiments([ - new ExperimentModel("1", "default.newFlag", "and(true, true)", false, ["default.newFlag"], [], "stam") - ]); - $flagSetter->setExperiments(); - - $this->assertTrue($dynamicApi->isEnabled("default.newFlag", false)); - } - - public function testGetValue() - { - $parser = new Parser(); - $flagRepo = new FlagRepository(); - $expRepo = new ExperimentRepository(); - $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, null); - $dynamicApi = new DynamicApi($flagRepo, $this->_ep); - - $this->assertEquals("A", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", "C"])); - $this->assertEquals("A", $flagRepo->getFlag("default.newVariant")->getValue()); - $this->assertEquals("B", $dynamicApi->getValue("default.newVariant", "B", ["A", "B", "C"])); - $this->assertEquals(1, count($flagRepo->getAllFlags())); - - $expRepo->setExperiments([ - new ExperimentModel("1", "default.newVariant", "ifThen(true, \"B\", \"A\")", false, ["default.newVariant"], [], "stam") - ]); - $flagSetter->setExperiments(); - - $this->assertEquals("B", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", "C"])); - } -} diff --git a/tests/Rox/Core/Client/InternalFlagsTests.php b/tests/Rox/Core/Client/InternalFlagsTests.php index b6a5c66..b718914 100644 --- a/tests/Rox/Core/Client/InternalFlagsTests.php +++ b/tests/Rox/Core/Client/InternalFlagsTests.php @@ -1,10 +1,9 @@ shouldReceive('evaluateExpression') - ->andReturn(new EvaluationResult(Flag::FLAG_FALSE_VALUE)) + ->andReturn(new EvaluationResult(BoolFlagValueConverter::FLAG_FALSE_VALUE)) ->getMock(); $expRepo = \Mockery::mock(ExperimentRepositoryInterface::class) @@ -61,7 +60,7 @@ public function testWillReturnTrueWhenExpressionIsTrue() { $parser = \Mockery::mock(ParserInterface::class) ->shouldReceive('evaluateExpression') - ->andReturn(new EvaluationResult(Flag::FLAG_TRUE_VALUE)) + ->andReturn(new EvaluationResult(BoolFlagValueConverter::FLAG_TRUE_VALUE)) ->getMock(); $expRepo = \Mockery::mock(ExperimentRepositoryInterface::class) diff --git a/tests/Rox/Core/Entities/FlagSetterTests.php b/tests/Rox/Core/Entities/FlagSetterTests.php index 91aa007..3bd04ce 100644 --- a/tests/Rox/Core/Entities/FlagSetterTests.php +++ b/tests/Rox/Core/Entities/FlagSetterTests.php @@ -10,6 +10,7 @@ use Rox\Core\Repositories\FlagRepository; use Rox\Core\Roxx\ParserInterface; use Rox\RoxTestCase; +use Rox\Server\Flags\RoxFlag; class FlagSetterTests extends RoxTestCase { @@ -22,7 +23,7 @@ public function testWillSetFlagData() $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); - $flagRepo->addFlag(new Flag(), "f1"); + $flagRepo->addFlag(new RoxFlag(), "f1"); $expRepo->setExperiments( [ new ExperimentModel("33", "1", "1", false, ["f1"], [], "stam") @@ -45,8 +46,8 @@ public function testWillNotSetForOtherFlag() $internalFlags = \Mockery::mock(InternalFlagsInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); - $flagRepo->addFlag(new Flag(), "f1"); - $flagRepo->addFlag(new Flag(), "f2"); + $flagRepo->addFlag(new RoxFlag(), "f1"); + $flagRepo->addFlag(new RoxFlag(), "f2"); $expRepo->setExperiments( [ new ExperimentModel("1", "1", "1", false, ["f1"], [], "stam") @@ -71,7 +72,7 @@ public function testWillSetExperimentForFlagAndWillRemoveIt() $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, $impressionInvoker); - $flagRepo->addFlag(new Flag(), "f2"); + $flagRepo->addFlag(new RoxFlag(), "f2"); $flagSetter->setExperiments(); @@ -104,7 +105,7 @@ public function testWillSetFlagWithoutExperimentAndThenAddExperiment() $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, $impressionInvoker); - $flagRepo->addFlag(new Flag(), "f2"); + $flagRepo->addFlag(new RoxFlag(), "f2"); $expRepo->setExperiments( [ new ExperimentModel("id1", "1", "con1", false, ["f2"], [], "stam") @@ -143,8 +144,8 @@ public function testWillSetDataForAddedFlag() $flagSetter = new FlagSetter($flagRepo, $parser, $expRepo, $impressionInvoker); $flagSetter->setExperiments(); - $flagRepo->addFlag(new Flag(), "f1"); - $flagRepo->addFlag(new Flag(), "f2"); + $flagRepo->addFlag(new RoxFlag(), "f1"); + $flagRepo->addFlag(new RoxFlag(), "f2"); $this->assertEquals($flagRepo->getFlag("f1")->getCondition(), "1"); $this->assertEquals($flagRepo->getFlag("f2")->getCondition(), ''); diff --git a/tests/Rox/Core/Entities/FlagTests.php b/tests/Rox/Core/Entities/FlagTests.php deleted file mode 100644 index 926c182..0000000 --- a/tests/Rox/Core/Entities/FlagTests.php +++ /dev/null @@ -1,46 +0,0 @@ -assertEquals($flag->isEnabled(null), false); - } - - public function testFlagWithDefaultValue() - { - $flag = new Flag(true); - - $this->assertEquals($flag->isEnabled(null), true); - } - - public function testWillInvokeEnabledAction() - { - $flag = new Flag(true); - - $isCalled = [false]; - $flag->enabled(null, function () use (&$isCalled) { - $isCalled[0] = true; - }); - - $this->assertEquals($isCalled[0], true); - } - - public function testWillInvokeDisabledAction() - { - $flag = new Flag(); - - $isCalled = [false]; - $flag->disabled(null, function () use (&$isCalled) { - $isCalled[0] = true; - }); - - $this->assertEquals($isCalled[0], true); - } -} diff --git a/tests/Rox/Core/Entities/VariantTests.php b/tests/Rox/Core/Entities/VariantTests.php deleted file mode 100644 index 901b276..0000000 --- a/tests/Rox/Core/Entities/VariantTests.php +++ /dev/null @@ -1,116 +0,0 @@ -assertEquals(count($variant->getOptions()), 3); - } - - public function testWillAddDefaultToOptionsIfNotExists() - { - $variant = new Variant('1', ['2', '3']); - - $this->assertEquals(count($variant->getOptions()), 3); - $this->assertContains('1', $variant->getOptions()); - } - - public function testWillSetName() - { - $variant = new Variant('1', ['2', '3']); - - $this->assertEquals($variant->getName(), null); - - $variant->setName('bop'); - - $this->assertEquals($variant->getName(), 'bop'); - } - - public function testWillReturnDefaultValueWhenNoParserOrCondition() - { - $variant = new Variant('1', ['2', '3']); - - $this->assertEquals($variant->getValue(null), '1'); - - $parser = \Mockery::mock(ParserInterface::class); - $variant->setForEvaluation($parser, null, null); - - $this->assertEquals($variant->getValue(null), '1'); - - $variant->setForEvaluation(null, new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), null); - - $this->assertEquals($variant->getValue(null), '1'); - } - - public function testWillExpressionValueWhenResultNotInOptions() - { - $parser = \Mockery::mock(ParserInterface::class) - ->shouldReceive('evaluateExpression') - ->andReturn(new EvaluationResult('xxx')) - ->getMock(); - - $variant = new Variant('1', ['2', '3']); - - $variant->setForEvaluation($parser, - new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), - null); - - $this->assertEquals($variant->getValue(null), 'xxx'); - } - - public function testWillReturnValueWhenOnEvaluation() - { - $parser = \Mockery::mock(ParserInterface::class) - ->shouldReceive('evaluateExpression') - ->andReturn(new EvaluationResult('2')) - ->getMock(); - - $variant = new Variant('1', ['2', '3']); - - $variant->setForEvaluation($parser, - new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), - null); - - $this->assertEquals('2', $variant->getValue(null)); - } - - public function testWillRaiseImpression() - { - $parser = \Mockery::mock(ParserInterface::class) - ->shouldReceive('evaluateExpression') - ->andReturn(new EvaluationResult('2')) - ->getMock(); - - $variant = new Variant('1', ['2', '3']); - - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) - ->shouldReceive('isEnabled') - ->andReturn(false) - ->getMock(); - - $impInvoker = new XImpressionInvoker($internalFlags, null, null); - $variant->setForEvaluation($parser, - new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), - $impInvoker); - - $isImpressionRaised = [false]; - $impInvoker->register(function (ImpressionArgs $e) use (&$isImpressionRaised) { - $isImpressionRaised[0] = true; - }); - - $this->assertEquals('2', $variant->getValue()); - $this->assertTrue($isImpressionRaised[0]); - } -} diff --git a/tests/Rox/Core/Register/TestContainer.php b/tests/Rox/Core/Register/TestContainer.php index 1d0549e..9b1352c 100644 --- a/tests/Rox/Core/Register/TestContainer.php +++ b/tests/Rox/Core/Register/TestContainer.php @@ -2,8 +2,8 @@ namespace Rox\Core\Register; -use Rox\Core\Entities\Flag; -use Rox\Core\Entities\Variant; +use Rox\Server\Flags\RoxFlag; +use Rox\Server\Flags\RoxString; use stdClass; class TestContainer @@ -19,11 +19,11 @@ class TestContainer */ public function __construct() { - $this->variant1 = new Variant("1", ["1", "2", "3"]); - $this->variant2 = new Variant("6", ["4", "5", "6"]); - $this->flag1 = new Flag(); - $this->flag2 = new Flag(); - $this->flag3 = new Flag(); + $this->variant1 = new RoxString("1", ["1", "2", "3"]); + $this->variant2 = new RoxString("6", ["4", "5", "6"]); + $this->flag1 = new RoxFlag(); + $this->flag2 = new RoxFlag(); + $this->flag3 = new RoxFlag(); $this->somethingElse = new stdClass(); } } diff --git a/tests/Rox/Core/Repositories/FlagRepositoryTests.php b/tests/Rox/Core/Repositories/FlagRepositoryTests.php index a97beda..d8663e0 100644 --- a/tests/Rox/Core/Repositories/FlagRepositoryTests.php +++ b/tests/Rox/Core/Repositories/FlagRepositoryTests.php @@ -3,8 +3,8 @@ namespace Rox\Core\Repositories; use Rox\Core\CustomProperties\FlagAddedCallbackArgs; -use Rox\Core\Entities\Flag; use Rox\RoxTestCase; +use Rox\Server\Flags\RoxFlag; class FlagRepositoryTests extends RoxTestCase { @@ -18,7 +18,7 @@ public function testWillReturnNullWhenFlagNotFound() public function testWillAddFlagAndSetName() { $repo = new FlagRepository(); - $flag = new Flag(); + $flag = new RoxFlag(); $repo->addFlag($flag, "harti"); @@ -28,7 +28,7 @@ public function testWillAddFlagAndSetName() public function testWillRaiseFlagAddedEvent() { $repo = new FlagRepository(); - $flag = new Flag(); + $flag = new RoxFlag(); $variantFromEvent = [null]; $repo->addFlagAddedCallback(function (FlagAddedCallbackArgs $args) use (&$variantFromEvent) { diff --git a/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php b/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php index 80c95d8..5b4cec0 100644 --- a/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php +++ b/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php @@ -10,9 +10,7 @@ use Rox\Core\CustomProperties\CustomPropertyRepository; use Rox\Core\CustomProperties\CustomPropertyType; use Rox\Core\CustomProperties\DynamicProperties; -use Rox\Core\Entities\Flag; use Rox\Core\Entities\FlagSetter; -use Rox\Core\Entities\Variant; use Rox\Core\Impression\ImpressionArgs; use Rox\Core\Impression\XImpressionInvoker; use Rox\Core\Repositories\ExperimentRepository; @@ -21,6 +19,8 @@ use Rox\Core\Repositories\FlagRepositoryInterface; use Rox\Core\Repositories\TargetGroupRepository; use Rox\RoxTestCase; +use Rox\Server\Flags\RoxFlag; +use Rox\Server\Flags\RoxString; class ExperimentsExtensionsTests extends RoxTestCase { @@ -121,7 +121,7 @@ public function testFlagValueFlagEvaluationDefault() new ExperimentsExtensions($parser, $targetGroupsRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $v = new Variant("op1", ["op2"]); + $v = new RoxString("op1", ["op2"]); $flagRepository->addFlag($v, "f1"); $this->assertEquals("op1", $parser->evaluateExpression("flagValue(\"f1\")")->stringValue()); } @@ -137,10 +137,10 @@ public function testFlagDependencyValue() new ExperimentsExtensions($parser, $targetGroupsRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $f = new Flag(); + $f = new RoxFlag(); $flagRepository->addFlag($f, "f1"); - $v = new Variant("blue", ["red", "green"]); + $v = new RoxString("blue", ["red", "green"]); $flagRepository->addFlag($v, "v1"); $v->setCondition("ifThen(eq(\"true\", flagValue(\"f1\")), \"red\", \"green\")"); $v->setParser($parser); @@ -161,12 +161,12 @@ public function testFlagDependencyImpressionHandler() new ExperimentsExtensions($parser, $targetGroupsRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $f = new Flag(); + $f = new RoxFlag(); $flagRepository->addFlag($f, "f1"); $f->setImpressionInvoker($ii); $impressionList = []; - $v = new Variant("blue", ["red", "green"]); + $v = new RoxString("blue", ["red", "green"]); $flagRepository->addFlag($v, "v1"); $v->setCondition("ifThen(eq(\"true\", flagValue(\"f1\")), \"red\", \"green\")"); $v->setParser($parser); @@ -178,7 +178,7 @@ public function testFlagDependencyImpressionHandler() $this->assertEquals("green", $v->getValue()); - $this->assertEquals(2, count($impressionList)); + $this->assertCount(2, $impressionList); $this->assertEquals("f1", $impressionList[0]->getReportingValue()->getName()); $this->assertEquals("false", $impressionList[0]->getReportingValue()->getValue()); @@ -198,12 +198,12 @@ public function testFlagDependency2LevelsBottomNotExists() new ExperimentsExtensions($parser, $targetGroupsRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $f = new Flag(); + $f = new RoxFlag(); $flagRepository->addFlag($f, "f1"); $f->setParser($parser); $f->setCondition("flagValue(\"someFlag\")"); - $v = new Variant("blue", ["red", "green"]); + $v = new RoxString("blue", ["red", "green"]); $flagRepository->addFlag($v, "v1"); $v->setCondition("ifThen(eq(\"true\", flagValue(\"f1\")), \"red\", \"green\")"); $v->setParser($parser); @@ -230,7 +230,7 @@ public function testFlagDependencyUnexistingFlagButExistingExperiment() new ExperimentsExtensions($parser, $targetGroupRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $colorVar = new Variant("red", ["red", "green", "blue"]); + $colorVar = new RoxString("red", ["red", "green", "blue"]); $colorVar->setParser($parser); $flagRepository->addFlag($colorVar, "colorVar"); @@ -259,7 +259,7 @@ public function testFlagDependencyUnexistingFlagAndExperimentUndefined() new ExperimentsExtensions($parser, $targetGroupRepository, $flagRepository, $experimentRepository); $experimentsExtensions->extend(); - $colorVar = new Variant("red", ["red", "green", "blue"]); + $colorVar = new RoxString("red", ["red", "green", "blue"]); $colorVar->setParser($parser); $flagRepository->addFlag($colorVar, "colorVar"); @@ -284,19 +284,19 @@ public function testFlagDependencyWithContext() return (bool)$context->get("isPropOn"); })); - $flag1 = new Flag(); + $flag1 = new RoxFlag(); $flag1->setCondition("property(\"prop\")"); $flag1->setParser($parser); $flagRepository->addFlag($flag1, "flag1"); - $flag2 = new Flag(); + $flag2 = new RoxFlag(); $flag2->setCondition("flagValue(\"flag1\")"); $flag2->setParser($parser); $flagRepository->addFlag($flag2, "flag2"); - $flagValue = $flag2->getValue((new ContextBuilder())->build(["isPropOn" => true])); + $flagValue = $flag2->isEnabled((new ContextBuilder())->build(["isPropOn" => true])); - $this->assertEquals("true", $flagValue); + $this->assertTrue($flagValue); } public function testFlagDependencyWithContextUsedOnExperimentWithNoFlag() @@ -314,7 +314,7 @@ public function testFlagDependencyWithContextUsedOnExperimentWithNoFlag() return (bool)$context->get("isPropOn"); })); - $flag3 = new Flag(); + $flag3 = new RoxFlag(); $flag3->setCondition("flagValue(\"flag2\")"); $flag3->setParser($parser); $flagRepository->addFlag($flag3, "flag3"); @@ -323,9 +323,9 @@ public function testFlagDependencyWithContextUsedOnExperimentWithNoFlag() $experimentRepository->setExperiments($experimentModels); - $flagValue = $flag3->getValue((new ContextBuilder())->build(["isPropOn" => true])); + $flagValue = $flag3->isEnabled((new ContextBuilder())->build(["isPropOn" => true])); - $this->assertEquals("true", $flagValue); + $this->assertTrue($flagValue); } public function testFlagDependencyWithContext2LevelMidLevelNoFlagEvalExperiment() @@ -343,12 +343,12 @@ public function testFlagDependencyWithContext2LevelMidLevelNoFlagEvalExperiment( return (bool)$context->get("isPropOn"); })); - $flag1 = new Flag(); + $flag1 = new RoxFlag(); $flag1->setCondition("property(\"prop\")"); $flag1->setParser($parser); $flagRepository->addFlag($flag1, "flag1"); - $flag3 = new Flag(); + $flag3 = new RoxFlag(); $flag3->setCondition("flagValue(\"flag2\")"); $flag3->setParser($parser); $flagRepository->addFlag($flag3, "flag3"); @@ -357,8 +357,8 @@ public function testFlagDependencyWithContext2LevelMidLevelNoFlagEvalExperiment( $experimentRepository->setExperiments($experimentModels); - $flagValue = $flag3->getValue((new ContextBuilder())->build(["isPropOn" => true])); + $flagValue = $flag3->isEnabled((new ContextBuilder())->build(["isPropOn" => true])); - $this->assertEquals("true", $flagValue); + $this->assertTrue($flagValue); } } diff --git a/tests/Rox/Core/XPack/Network/StateSenderTests.php b/tests/Rox/Core/XPack/Network/StateSenderTests.php index 888f759..df1ef3e 100644 --- a/tests/Rox/Core/XPack/Network/StateSenderTests.php +++ b/tests/Rox/Core/XPack/Network/StateSenderTests.php @@ -1,6 +1,6 @@ once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag1"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag1"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); - $this->assertEquals(parse_url($reqData[0]->getUrl())['path'], "/{$this->_appKey}/09FBB5C9B28B300E8FF14BE4EBB5A829"); + $this->assertEquals("/{$this->_appKey}/09FBB5C9B28B300E8FF14BE4EBB5A829", parse_url($reqData[0]->getUrl())['path']); $this->_validateNoErrors(); } @@ -115,7 +117,7 @@ public function testWillNotCallCDNTwice() ->once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag1"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag1"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -141,7 +143,7 @@ public function testWillCallOnlyCDNStateMD5ChangesForFlag() ->twice() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag1"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag1"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -149,7 +151,7 @@ public function testWillCallOnlyCDNStateMD5ChangesForFlag() $this->assertEquals(parse_url($reqData[0]->getUrl())['path'], "/{$this->_appKey}/09FBB5C9B28B300E8FF14BE4EBB5A829"); - $this->_flagRepo->addFlag(new Flag(), "flag2"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag2"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -194,8 +196,8 @@ public function testWillCallOnlyCDNStateMD5FlagOrderNotImportant() ->times(3) ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag1"); - $this->_flagRepo->addFlag(new Flag(), "flag2"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag1"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag2"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -207,8 +209,8 @@ public function testWillCallOnlyCDNStateMD5FlagOrderNotImportant() $stateSender->send(); $fr2 = new FlagRepository(); $stateSender2 = new StateSender($request, $this->_dp, $fr2, $this->_cpRepo); - $fr2->addFlag(new Flag(), "flag2"); - $fr2->addFlag(new Flag(), "flag1"); + $fr2->addFlag(new RoxFlag(), "flag2"); + $fr2->addFlag(new RoxFlag(), "flag1"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender2->send(); @@ -267,7 +269,7 @@ public function testWillReturnNullWhenCDNFailsWithException() ->never() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -293,7 +295,7 @@ public function testWillReturnNullWhenCDNSucceedWithEmptyResponse() ->never() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -317,7 +319,7 @@ public function testWillReturnNullWhenCDNSucceedWithNotJsonResponse() ->never() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -347,7 +349,7 @@ public function testWillReturnNullWhenCDNFails404APIWithException() ->once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $this->_cpRepo->addCustomProperty(new CustomProperty("id", CustomPropertyType::getString(), "1111")); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); @@ -382,7 +384,7 @@ public function testWillReturnAPIDataWhenCDNSucceedWithResult404APIOK() ->once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $this->_cpRepo->addCustomProperty(new CustomProperty("id", CustomPropertyType::getString(), "1111")); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); @@ -415,7 +417,7 @@ public function testWillReturnAPIDataWhenCDNFails404APIOK() ->once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $this->_cpRepo->addCustomProperty(new CustomProperty("id", CustomPropertyType::getString(), "1111")); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); @@ -458,7 +460,7 @@ public function testWillReturnNullDataWhenBothNotFound() ->once() ->getMock(); - $this->_flagRepo->addFlag(new Flag(), "flag"); + $this->_flagRepo->addFlag(new RoxFlag(), "flag"); $stateSender = new StateSender($request, $this->_dp, $this->_flagRepo, $this->_cpRepo); $stateSender->send(); diff --git a/tests/Rox/E2E/Container.php b/tests/Rox/E2E/Container.php index b8b39bd..f02f870 100644 --- a/tests/Rox/E2E/Container.php +++ b/tests/Rox/E2E/Container.php @@ -3,7 +3,7 @@ namespace Rox\E2E; use Rox\Server\Flags\RoxFlag; -use Rox\Server\Flags\RoxVariant; +use Rox\Server\Flags\RoxString; class Container { @@ -53,17 +53,17 @@ class Container public $flagTargetGroupsNone; /** - * @var RoxVariant $variantWithContext + * @var RoxString $variantWithContext */ public $variantWithContext; /** - * @var RoxVariant $variant + * @var RoxString $variant */ public $variant; /** - * @var RoxVariant $variantOverwritten + * @var RoxString $variantOverwritten */ public $variantOverwritten; @@ -73,7 +73,7 @@ class Container public $flagForDependency; /** - * @var RoxVariant $flagColorsForDependency + * @var RoxString $flagColorsForDependency */ public $flagColorsForDependency; @@ -83,7 +83,7 @@ class Container public $flagDependent; /** - * @var RoxVariant $flagColorDependentWithContext + * @var RoxString $flagColorDependentWithContext */ public $flagColorDependentWithContext; @@ -104,15 +104,15 @@ public function __construct() $this->flagTargetGroupsAny = new RoxFlag(); $this->flagTargetGroupsNone = new RoxFlag(); - $this->variantWithContext = new RoxVariant("red", ["red", "blue", "green"]); + $this->variantWithContext = new RoxString("red", ["red", "blue", "green"]); - $this->variant = new RoxVariant("red", ["red", "blue", "green"]); - $this->variantOverwritten = new RoxVariant("red", ["red", "blue", "green"]); + $this->variant = new RoxString("red", ["red", "blue", "green"]); + $this->variantOverwritten = new RoxString("red", ["red", "blue", "green"]); $this->flagForDependency = new RoxFlag(false); - $this->flagColorsForDependency = new RoxVariant("White", ["White", "Blue", "Green", "Yellow"]); + $this->flagColorsForDependency = new RoxString("White", ["White", "Blue", "Green", "Yellow"]); $this->flagDependent = new RoxFlag(false); - $this->flagColorDependentWithContext = new RoxVariant("White", ["White", "Blue", "Green", "Yellow"]); + $this->flagColorDependentWithContext = new RoxString("White", ["White", "Blue", "Green", "Yellow"]); } public static function getInstance() diff --git a/tests/Rox/E2E/ContainerTwo.php b/tests/Rox/E2E/ContainerTwo.php index 94e2153..cba30d3 100644 --- a/tests/Rox/E2E/ContainerTwo.php +++ b/tests/Rox/E2E/ContainerTwo.php @@ -3,7 +3,7 @@ namespace Rox\E2E; use Rox\Server\Flags\RoxFlag; -use Rox\Server\Flags\RoxVariant; +use Rox\Server\Flags\RoxString; class ContainerTwo { @@ -18,7 +18,7 @@ class ContainerTwo public $flag2; /** - * @var RoxVariant $variant + * @var RoxString $variant */ public $variant2; @@ -28,7 +28,7 @@ class ContainerTwo public function __construct() { $this->flag2 = new RoxFlag(true); - $this->variant2 = new RoxVariant("red", ["red", "blue", "green"]); + $this->variant2 = new RoxString("red", ["red", "blue", "green"]); } public static function getInstance() diff --git a/tests/Rox/Server/Flags/DynamicApiTests.php b/tests/Rox/Server/Flags/DynamicApiTests.php new file mode 100644 index 0000000..f75d95c --- /dev/null +++ b/tests/Rox/Server/Flags/DynamicApiTests.php @@ -0,0 +1,359 @@ +_ep = new ServerEntitiesProvider(); + } + + public function testIsEnabledNullName() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->isEnabled(null, false); + } + + public function testIsEnabled() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertTrue($dynamicApi->isEnabled("default.newFlag", true)); + $this->checkLastImpression("default.newFlag", "true"); + + $this->assertInstanceOf(BooleanFlagInterface::class, $flagRepo->getFlag("default.newFlag")); + $this->assertTrue($flagRepo->getFlag("default.newFlag")->getBooleanValue()); + $this->checkLastImpression("default.newFlag", "true"); + + $this->assertFalse($dynamicApi->isEnabled("default.newFlag", false)); + $this->assertCount(1, $flagRepo->getAllFlags()); + $this->checkLastImpression("default.newFlag", "false"); + + $this->setExperiments(["default.newFlag" => "and(true, true)"]); + $this->assertTrue($dynamicApi->isEnabled("default.newFlag", false)); + $this->checkLastImpression("default.newFlag", "true", true); + } + + public function testIsEnabledAfterSetup() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->setExperiments(["default.newFlag" => "and(true, true)"]); + $this->assertTrue($dynamicApi->isEnabled("default.newFlag", false)); + $this->checkLastImpression("default.newFlag", "true", true); + } + + public function testIsEnabledDifferentTypeCall() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + + $this->assertFalse($dynamicApi->isEnabled("default.newFlag", false)); + $this->checkLastImpression("default.newFlag", "false"); + + $this->setExperiments(["default.newFlag" => "ifThen(true, \"true\", \"true\")"]); + + $this->assertEquals(3.4, $dynamicApi->getDouble("default.newFlag", 3.4)); + $this->checkLastImpression("default.newFlag", "3.4", true); + + $this->assertEquals("true", $dynamicApi->getValue("default.newFlag", "1")); + $this->checkLastImpression("default.newFlag", "true", true); + + $this->assertEquals(2, $dynamicApi->getInt("default.newFlag", 2)); + $this->checkLastImpression("default.newFlag", "2", true); + } + + public function testIsEnabledWrongExperimentType() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->setExperiments(["default.newFlag" => "\"otherValue\""]); + $this->assertFalse($dynamicApi->isEnabled("default.newFlag", false)); + $this->checkLastImpression("default.newFlag", "false", true); + } + + public function testGetValueNullName() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getValue(null, "A", ["A", "B", "C"]); + } + + public function testGetValueVariationNull() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getValue("default.newVariant", "A", ["A", "B", null]); + } + + public function testGetValueVariationWrongTypeInt() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getValue("default.newVariant", "A", ["A", "B", 1]); + } + + public function testGetValueVariationWrongTypeBool() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getValue("default.newVariant", "A", ["A", "B", true]); + } + + public function testGetValueVariationWrongTypeDouble() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getValue("default.newVariant", "A", ["A", "B", 1.2]); + } + + public function testGetValueVariationNullWhenVariantExists() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->assertEquals("A", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", "C"])); + $this->assertEquals("A", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", null])); + } + + public function testGetValue() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals("A", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", "C"])); + $this->checkLastImpression("default.newVariant", "A"); + + $this->assertInstanceOf(StringFlagInterface::class, $flagRepo->getFlag("default.newVariant")); + $this->assertEquals("A", $flagRepo->getFlag("default.newVariant")->getStringValue()); + $this->checkLastImpression("default.newVariant", "A"); + + $this->assertEquals("B", $dynamicApi->getValue("default.newVariant", "B", ["A", "B", "C"])); + $this->assertCount(1, $flagRepo->getAllFlags()); + $this->checkLastImpression("default.newVariant", "B"); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"B\", \"A\")"]); + $this->assertEquals("B", $dynamicApi->getValue("default.newVariant", "A", ["A", "B", "C"])); + $this->checkLastImpression("default.newVariant", "B", true); + } + + public function testGetValueDifferentTypeCall() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals("value", $dynamicApi->getValue("default.newVariant", "value")); + $this->checkLastImpression("default.newVariant", "value"); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"val1\", \"val2\")"]); + $this->assertEquals(3.4, $dynamicApi->getDouble("default.newVariant", 3.4)); + $this->checkLastImpression("default.newVariant", "3.4", true); + + $this->assertFalse($dynamicApi->isEnabled("default.newVariant", false)); + $this->checkLastImpression("default.newVariant", "false", true); + + $this->assertEquals(2, $dynamicApi->getInt("default.newVariant", 2)); + $this->checkLastImpression("default.newVariant", "2", true); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"true\", \"true\")"]); + $this->assertTrue($dynamicApi->isEnabled("default.newVariant", false)); + $this->checkLastImpression("default.newVariant", "true", true); + } + + public function testGetIntNullName() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getInt(null, 1, [2, 3]); + } + + public function testGetIntVariationNull() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getInt("default.newVariant", 1, [2, null]); + } + + public function testGetIntVariationNullWhenVariantExists() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1, [2, 3])); + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1, [2, null])); + } + + public function testGetIntVariationWrongTypeString() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getInt("default.newVariant", 1, [2, "3"]); + } + + public function testGetIntVariationWrongTypeBool() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getInt("default.newVariant", 1, [2, true]); + } + + public function testGetIntVariationWrongTypeDouble() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getInt("default.newVariant", 1, [2, 3.4]); + } + + public function testGetInt() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1)); + $this->checkLastImpression("default.newVariant", "1"); + + $this->assertInstanceOf(IntFlagInterface::class, $flagRepo->getFlag("default.newVariant")); + $this->assertEquals(1, $flagRepo->getFlag("default.newVariant")->getIntValue()); + $this->checkLastImpression("default.newVariant", "1"); + + $this->assertEquals(2, $dynamicApi->getInt("default.newVariant", 2)); + $this->checkLastImpression("default.newVariant", "2"); + $this->assertCount(1, $flagRepo->getAllFlags()); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"3\", \"4\")"]); + $this->assertEquals(3, $dynamicApi->getInt("default.newVariant", 1)); + $this->checkLastImpression("default.newVariant", "3", true); + } + + public function testGetIntDifferentTypeCall() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1)); + $this->checkLastImpression("default.newVariant", "1"); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"2\", \"3\")"]); + $this->assertEquals(2, $dynamicApi->getDouble("default.newVariant", 3.4)); + $this->checkLastImpression("default.newVariant", "2", true); + + $this->assertEquals("2", $dynamicApi->getValue("default.newVariant", "1")); + $this->checkLastImpression("default.newVariant", "2", true); + + $this->assertFalse($dynamicApi->isEnabled("default.newVariant", true)); + $this->checkLastImpression("default.newVariant", "false", true); + } + + public function testGetIntWrongExperimentType() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1)); + $this->assertInstanceOf(IntFlagInterface::class, $flagRepo->getFlag("default.newVariant")); + $this->assertEquals(2, $dynamicApi->getInt("default.newVariant", 2)); + $this->assertCount(1, $flagRepo->getAllFlags()); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"3.5\", \"4.1\")"]); + $this->assertEquals(2, $dynamicApi->getInt("default.newVariant", 2)); + } + + public function testGetDoubleNullName() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getDouble(null, 1.1, [2.2, 3.3]); + } + + public function testGetDoubleVariationNull() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, null]); + } + + public function testGetDoubleVariationNullWhenVariantExists() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->assertEquals(1.1, $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, 3.3])); + $this->assertEquals(1.1, $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, null])); + } + + public function testGetDoubleVariationWrongTypeString() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, "3.3"]); + } + + public function testGetDoubleVariationWrongTypeBool() + { + $this->expectException(InvalidArgumentException::class); + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, true]); + } + + public function testGetDoubleVariationTypeInt() + { + $dynamicApi = new DynamicApi($this->getFlagRepository(), $this->_ep); + $this->assertEquals(1.1, $dynamicApi->getDouble("default.newVariant", 1.1, [2.2, 3])); // int is ok + } + + public function testGetDouble() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(1.1, $dynamicApi->getDouble("default.newVariant", 1.1)); + $this->checkLastImpression("default.newVariant", "1.1"); + + $this->assertInstanceOf(DoubleFlagInterface::class, $flagRepo->getFlag("default.newVariant")); + + $this->assertEquals(2.2, $dynamicApi->getDouble("default.newVariant", 2.2)); + $this->checkLastImpression("default.newVariant", "2.2"); + $this->assertCount(1, $flagRepo->getAllFlags()); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"3.3\", \"4.4\")"]); + $this->assertEquals(3.3, $dynamicApi->getDouble("default.newVariant", 2.2)); + $this->checkLastImpression("default.newVariant", "3.3", true); + } + + public function testGetDoubleDifferentTypeCall() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(2.1, $dynamicApi->getDouble("default.newVariant", 2.1)); + $this->checkLastImpression("default.newVariant", "2.1"); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"3.3\", \"4.4\")"]); + $this->assertEquals(1, $dynamicApi->getInt("default.newVariant", 1)); + $this->assertEquals("3.3", $dynamicApi->getValue("default.newVariant", "1")); + $this->assertFalse($dynamicApi->isEnabled("default.newVariant", true)); + } + + public function testGetDoubleWrongExperimentType() + { + $flagRepo = $this->getFlagRepository(); + $dynamicApi = new DynamicApi($flagRepo, $this->_ep); + + $this->assertEquals(1.1, $dynamicApi->getDouble("default.newVariant", 1.1)); + $this->assertInstanceOf(DoubleFlagInterface::class, $flagRepo->getFlag("default.newVariant")); + $this->assertEquals(2.2, $dynamicApi->getDouble("default.newVariant", 2.2)); + $this->assertCount(1, $flagRepo->getAllFlags()); + + $this->setExperiments(["default.newVariant" => "ifThen(true, \"aaa\", \"bbb\")"]); + $this->assertEquals(1, $dynamicApi->getDouble("default.newVariant", 1)); + } +} diff --git a/tests/Rox/Server/Flags/RoxDoubleTests.php b/tests/Rox/Server/Flags/RoxDoubleTests.php new file mode 100644 index 0000000..dc37513 --- /dev/null +++ b/tests/Rox/Server/Flags/RoxDoubleTests.php @@ -0,0 +1,119 @@ +expectException(InvalidArgumentException::class); + new RoxDouble($invalidValue); + } + + /** + * @dataProvider invalidValues + * @param mixed $invalidValue + */ + public function testInvalidVariationValueThrowsException($invalidValue) + { + $this->expectException(InvalidArgumentException::class); + new RoxDouble(1.1, [$invalidValue]); + } + + /** + * @return array[] + */ + public function invalidValues() + { + return [ + [null], + [false], + [true], + ["1"] + ]; + } + + public function testWillAddDefaultToOptionsWhenNoOptions() + { + $variant = new RoxDouble(1.1); + $this->assertCount(1, $variant->getVariations()); + $this->assertContains('1.1', $variant->getVariations()); + } + + public function testWillNotAddDefaultToOptionsIfExists() + { + $variant = new RoxDouble(1.1, [1.1, 2.2, 3.3]); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1.1', $variant->getVariations()); + } + + public function testWillAddDefaultToOptionsIfNotExists() + { + $variant = new RoxDouble(1.1, [2.2, 3.3]); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1.1', $variant->getVariations()); + } + + public function testWillReturnDefault() + { + $variant = new RoxDouble(1.1); + $this->assertEquals(1.1, $variant->getValue()); + } + + public function testWillReturnDefaultWhenNoExperimentAfterSetup() + { + $variant = new RoxDouble(1.1); + $variant->setName('test'); + $variant->setForEvaluation($this->getParser(), null, $this->getImpressionInvoker()); + $this->assertEquals(1.1, $variant->getValue()); + $this->checkLastImpression('test', '1.1'); + } + + public function testWillReturnDefaultWhenExperimentReturnsUndefined() + { + $variant = new RoxDouble(1.1); + $this->setupFlag($variant, 'test', 'undefined'); + $this->assertEquals(1.1, $variant->getValue()); + $this->checkLastImpression('test', '1.1', true); + } + + public function testWillReturnDefaultWhenExperimentReturnsNull() + { + $variant = new RoxDouble(1.1); + $this->setupFlag($variant, 'test', null); + $this->assertEquals(1.1, $variant->getValue()); + $this->checkLastImpression('test', '1.1', true); + } + + public function testWillReturnExperimentExpressionValue() + { + $variant = new RoxDouble(1.1); + $this->setupFlag($variant, 'test', '1.123'); + $this->assertEquals(1.123, $variant->getValue()); + $this->checkLastImpression('test', '1.123', true); + } + + public function testWillReturnDefaultWhenExperimentWrongType() + { + $variant = new RoxDouble(1.1); + $this->setupFlag($variant, 'test', '"2ss"'); + $this->assertEquals(1.1, $variant->getValue()); + $this->checkLastImpression('test', '1.1', true); + } + + public function testWillUseContext() + { + $context = (new ContextBuilder())->build(['key' => 55]); + $variant = new RoxDouble(1.1); + $this->setupFlag($variant, 'test', '2.2'); + $this->assertEquals(2.2, $variant->getValue($context)); + $this->checkLastImpression('test', '2.2', true, 'key', 55); + } +} \ No newline at end of file diff --git a/tests/Rox/Server/Flags/RoxFlagTests.php b/tests/Rox/Server/Flags/RoxFlagTests.php new file mode 100644 index 0000000..4c189e5 --- /dev/null +++ b/tests/Rox/Server/Flags/RoxFlagTests.php @@ -0,0 +1,123 @@ +expectException(InvalidArgumentException::class); + new RoxFlag($defaultValue); + } + + /** + * @return array[] + */ + public function invalidDefaultValues() + { + return [ + [null], + ["false"], + [1], + [1.2] + ]; + } + + public function testFlagWithoutDefaultValue() + { + $flag = new RoxFlag(); + + $this->assertEquals(false, $flag->isEnabled(null)); + } + + public function testFlagWithDefaultValue() + { + $flag = new RoxFlag(true); + + $this->assertEquals(true, $flag->isEnabled(null)); + } + + public function testFlagWithDefaultValueAfterSetup() + { + $flag = new RoxFlag(false); + $flag->setName("test"); + $flag->setForEvaluation( + $this->getParser(), + null, + $this->getImpressionInvoker()); + $this->assertFalse($flag->isEnabled()); + $this->checkLastImpression("test", "false"); + } + + public function testFlagWithExperimentExpressionValue() + { + $flag = new RoxFlag(false); + $this->setupFlag($flag, "test", "and(true, true)"); + $this->assertTrue($flag->isEnabled()); + $this->checkLastImpression("test", "true", true); + } + + public function testFlagWithExperimentReturnsUndefined() + { + $flag = new RoxFlag(true); + $this->setupFlag($flag, "test", "undefined"); + $this->assertTrue($flag->isEnabled()); + $this->checkLastImpression("test", "true", true); + } + + public function testFlagWithExperimentEvaluationReturnsNull() + { + $flag = new RoxFlag(true); + $this->setupFlag($flag, "test", null); + $this->assertTrue($flag->isEnabled()); + $this->checkLastImpression("test", "true", true); + } + + public function testFlagWithExperimentWrongType() + { + $flag = new RoxFlag(true); + $this->setupFlag($flag, "test", "0"); + $this->assertTrue($flag->isEnabled()); + $this->checkLastImpression("test", "true", true); + } + + public function testWillUseContext() + { + $context = (new ContextBuilder())->build(['key' => 55]); + $flag = new RoxFlag(true); + $this->setupFlag($flag, "test", "true"); + $this->assertTrue($flag->isEnabled($context)); + $this->checkLastImpression("test", "true", true, "key", 55); + } + + public function testWillInvokeEnabledAction() + { + $flag = new RoxFlag(true); + + $isCalled = [false]; + $flag->enabled(null, function () use (&$isCalled) { + $isCalled[0] = true; + }); + + $this->assertEquals(true, $isCalled[0]); + } + + public function testWillInvokeDisabledAction() + { + $flag = new RoxFlag(); + + $isCalled = [false]; + $flag->disabled(null, function () use (&$isCalled) { + $isCalled[0] = true; + }); + + $this->assertEquals(true, $isCalled[0]); + } +} diff --git a/tests/Rox/Server/Flags/RoxFlagsTestCase.php b/tests/Rox/Server/Flags/RoxFlagsTestCase.php new file mode 100644 index 0000000..6e43e37 --- /dev/null +++ b/tests/Rox/Server/Flags/RoxFlagsTestCase.php @@ -0,0 +1,175 @@ +shouldReceive('isEnabled') + ->andReturn(false) + ->byDefault() + ->getMock(); + + $this->_impressionInvoker = new XImpressionInvoker($internalFlags, null, null); + $this->_impressionInvoker->register(function (ImpressionArgs $e) { + $this->_lastImpression = $e; + }); + + $this->_parser = new Parser(); + $this->_flagRepo = new FlagRepository(); + $this->_expRepo = new ExperimentRepository(); + $this->_flagSetter = new FlagSetter($this->_flagRepo, $this->_parser, $this->_expRepo, $this->_impressionInvoker); + } + + /** + * @return ParserInterface + */ + protected function getParser() + { + return $this->_parser; + } + + /** + * @return FlagRepositoryInterface + */ + protected function getFlagRepository() + { + return $this->_flagRepo; + } + + /** + * @return ExperimentRepositoryInterface + */ + protected function getExperimentRepository() + { + return $this->_expRepo; + } + + /** + * @return ImpressionInvoker + */ + protected function getImpressionInvoker() + { + return $this->_impressionInvoker; + } + + /** + * @return FlagSetter + */ + protected function getFlagSetter() + { + return $this->_flagSetter; + } + + /** + * @param array $exp Experiments flagName => flagExpression + */ + protected function setExperiments(array $exp) + { + $expCounter = [0]; + $flagNames = array_keys($exp); + $this->_expRepo->setExperiments(array_map(function ($name) use ($exp, &$expCounter) { + $id = strval($expCounter[0]++); + return $this->createExperiment($id, $name, $exp[$name]); + }, $flagNames)); + $this->_flagSetter->setExperiments(); + } + + /** + * @param string $id + * @param string $name + * @param string $expression + * @return ExperimentModel + */ + protected function createExperiment($id, $name, $expression) + { + return new ExperimentModel($id, $name, $expression, false, [$name], [], "stam"); + } + + /** + * @param RoxStringBase $flag + * @param string $name + * @param string|null $expression + */ + protected function setupFlag(RoxStringBase $flag, $name, $expression = null) + { + $flag->setName($name); + $flag->setForEvaluation( + $this->getParser(), + $this->createExperiment("1", $name, $expression), + $this->getImpressionInvoker()); + } + + /** + * @param string $name + * @param string $value + * @param bool $targeting + * @param string $contextKey Optional context key/value pair to check. + * @param mixed $contextValue Optional context key/value pair to check. + */ + protected function checkLastImpression($name, $value, $targeting = false, $contextKey = null, $contextValue = null) + { + $impressionArgs = $this->_lastImpression; + $this->assertNotNull($impressionArgs); + $this->assertEquals($name, $impressionArgs->getReportingValue()->getName()); + $this->assertEquals($value, $impressionArgs->getReportingValue()->getValue()); + $this->assertEquals($targeting, $impressionArgs->getReportingValue()->isTargeting()); + if ($contextKey) { + $context = $impressionArgs->getContext(); + $this->assertNotNull($context); + $this->assertEquals($contextValue, $context->get($contextKey)); + } + $this->_lastImpression = null; + } +} \ No newline at end of file diff --git a/tests/Rox/Server/Flags/RoxIntTests.php b/tests/Rox/Server/Flags/RoxIntTests.php new file mode 100644 index 0000000..8fa1b26 --- /dev/null +++ b/tests/Rox/Server/Flags/RoxIntTests.php @@ -0,0 +1,121 @@ +expectException(InvalidArgumentException::class); + new RoxInt($invalidValue); + } + + /** + * @dataProvider invalidValues + * @param mixed $invalidValue + */ + public function testInvalidVariationValueThrowsException($invalidValue) + { + $this->expectException(InvalidArgumentException::class); + new RoxInt(1, [$invalidValue]); + } + + /** + * @return array[] + */ + public function invalidValues() + { + return [ + [null], + [false], + [true], + ["1"], + [1.2] + ]; + } + + public function testWillAddDefaultToOptionsWhenNoOptions() + { + $variant = new RoxInt(1); + $this->assertCount(1, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillNotAddDefaultToOptionsIfExists() + { + $variant = new RoxInt(1, [1, 2, 3]); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillAddDefaultToOptionsIfNotExists() + { + $variant = new RoxInt(1, [2, 3]); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillReturnDefaultWhenNoExperiment() + { + $variant = new RoxInt(3); + $this->assertEquals(3, $variant->getValue()); + } + + public function testWillReturnDefaultWhenNoExperimentAfterSetup() + { + $variant = new RoxInt(3); + $variant->setName('test'); + $variant->setForEvaluation($this->getParser(), null, $this->getImpressionInvoker()); + $this->assertEquals(3, $variant->getValue()); + $this->checkLastImpression('test', '3'); + } + + public function testWillReturnDefaultWhenExperimentReturnsUndefined() + { + $variant = new RoxInt(3); + $this->setupFlag($variant, 'test', 'undefined'); + $this->assertEquals(3, $variant->getValue()); + $this->checkLastImpression('test', '3', true); + } + + public function testWillReturnExperimentExpressionValue() + { + $variant = new RoxInt(1); + $this->setupFlag($variant, 'test', '2'); + $this->assertEquals(2, $variant->getValue()); + $this->checkLastImpression('test', '2', true); + } + + public function testWillReturnDefaultWhenWrongExperimentType() + { + $variant = new RoxInt(2); + $this->setupFlag($variant, 'test', '1.44'); + $this->assertEquals(2, $variant->getValue()); + $this->checkLastImpression('test', '2', true); + } + + public function testWillReturnDefaultEvaluationReturnsNull() + { + $variant = new RoxInt(2); + $this->setupFlag($variant, 'test', null); + $this->assertEquals(2, $variant->getValue()); + $this->checkLastImpression('test', '2', true); + } + + public function testWillUseContext() + { + $context = (new ContextBuilder())->build(['key' => 55]); + $variant = new RoxInt(1, [2, 3]); + $this->setupFlag($variant, 'test', '2'); + $this->assertEquals(2, $variant->getValue($context)); + $this->checkLastImpression('test', '2', true, 'key', 55); + } +} \ No newline at end of file diff --git a/tests/Rox/Server/Flags/RoxStringTests.php b/tests/Rox/Server/Flags/RoxStringTests.php new file mode 100644 index 0000000..8d64b0a --- /dev/null +++ b/tests/Rox/Server/Flags/RoxStringTests.php @@ -0,0 +1,203 @@ +expectException(InvalidArgumentException::class); + new RoxString($invalidValue); + } + + /** + * @dataProvider invalidValues + * @param mixed $invalidValue + */ + public function testInvalidVariationValueThrowsException($invalidValue) + { + $this->expectException(InvalidArgumentException::class); + new RoxString('1', [$invalidValue]); + } + + /** + * @return array[] + */ + public function invalidValues() + { + return [ + [null], + [false], + [true], + [1], + [1.2] + ]; + } + + public function testWillAddDefaultToOptionsWhenNoOptions() + { + $variant = new RoxString('1'); + $this->assertCount(1, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillNotAddDefaultToOptionsIfExists() + { + $variant = new RoxString('1', ['1', '2', '3']); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillAddDefaultToOptionsIfNotExists() + { + $variant = new RoxString('1', ['2', '3']); + $this->assertCount(3, $variant->getVariations()); + $this->assertContains('1', $variant->getVariations()); + } + + public function testWillSetName() + { + $variant = new RoxString('1', ['2', '3']); + + $this->assertEquals(null, $variant->getName()); + $variant->setName('bop'); + $this->assertEquals('bop', $variant->getName()); + } + + public function testWillReturnDefaultValueWhenNoExperiment() + { + $variant = new RoxString('1'); + $this->assertEquals('1', $variant->getValue()); + } + + public function testWillDefaultAfterSetup() + { + $variant = new RoxString('1'); + $variant->setName('test'); + $variant->setForEvaluation($this->getParser(), null, $this->getImpressionInvoker()); + $this->assertEquals('1', $variant->getValue()); + $this->checkLastImpression('test', '1'); + } + + public function testFlagWithExperiment() + { + $variant = new RoxString('val'); + $this->setupFlag($variant, 'test', "\"dif\""); + $this->assertEquals('dif', $variant->getValue()); + $this->checkLastImpression('test', 'dif', true); + } + + public function testFlagWithExperimentReturnsUndefined() + { + $variant = new RoxString('val'); + $this->setupFlag($variant, 'test', "undefined"); + $this->assertEquals('val', $variant->getValue()); + $this->checkLastImpression('test', 'val', true); + } + + public function testWillEvaluationReturnsNull() + { + $variant = new RoxString('val'); + $this->setupFlag($variant, "test", null); + $this->assertEquals('val', $variant->getValue()); + $this->checkLastImpression('test', 'val', true); + } + + public function testWillUseContext() + { + $context = (new ContextBuilder())->build(['key' => 55]); + $variant = new RoxString('val'); + $this->setupFlag($variant, "test", "\"dif\""); + $this->assertEquals('dif', $variant->getValue($context)); + $this->checkLastImpression("test", "dif", true, "key", 55); + } + + public function testWillReturnDefaultValueWhenNoParserOrCondition() + { + $variant = new RoxString('1', ['2', '3']); + + $this->assertEquals('1', $variant->getValue(null)); + + $parser = \Mockery::mock(ParserInterface::class); + $variant->setForEvaluation($parser, null, null); + + $this->assertEquals('1', $variant->getValue(null)); + + $variant->setForEvaluation(null, new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), null); + + $this->assertEquals('1', $variant->getValue(null)); + } + + public function testWillExpressionValueWhenResultNotInOptions() + { + $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('evaluateExpression') + ->andReturn(new EvaluationResult('xxx')) + ->getMock(); + + $variant = new RoxString('1', ['2', '3']); + + $variant->setForEvaluation($parser, + new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), + null); + + $this->assertEquals('xxx', $variant->getValue(null)); + } + + public function testWillReturnValueWhenOnEvaluation() + { + $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('evaluateExpression') + ->andReturn(new EvaluationResult('2')) + ->getMock(); + + $variant = new RoxString('1', ['2', '3']); + + $variant->setForEvaluation($parser, + new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), + null); + + $this->assertEquals('2', $variant->getValue(null)); + } + + public function testWillRaiseImpression() + { + $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('evaluateExpression') + ->andReturn(new EvaluationResult('2')) + ->getMock(); + + $variant = new RoxString('1', ['2', '3']); + + $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + ->shouldReceive('isEnabled') + ->andReturn(false) + ->getMock(); + + $impInvoker = new XImpressionInvoker($internalFlags, null, null); + $variant->setForEvaluation($parser, + new ExperimentModel('id', 'name', '123', false, ['1'], [], 'stam'), + $impInvoker); + + $isImpressionRaised = [false]; + $impInvoker->register(function (ImpressionArgs $e) use (&$isImpressionRaised) { + $isImpressionRaised[0] = true; + }); + + $this->assertEquals('2', $variant->getValue()); + $this->assertTrue($isImpressionRaised[0]); + } +} From a80fc297e5c91c76add0955c49f655200eadc052 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Fri, 24 Sep 2021 21:12:38 +0300 Subject: [PATCH 02/15] Remove Experiments from SDK public API + Analytics should send the flag name, and not the experimentId --- src/Rox/Core/Impression/ImpressionArgs.php | 19 +-- src/Rox/Core/Impression/ImpressionInvoker.php | 5 +- src/Rox/Core/Impression/Models/Experiment.php | 78 ------------ .../Core/Impression/Models/ReportingValue.php | 2 +- src/Rox/Core/XPack/Analytics/Event.php | 49 -------- .../XPack/Impression/XImpressionInvoker.php | 23 ++-- .../Impressions/ImpressionInvokerTests.php | 112 ++++++++---------- tests/Rox/E2E/RoxE2ETests.php | 4 +- 8 files changed, 65 insertions(+), 227 deletions(-) delete mode 100644 src/Rox/Core/Impression/Models/Experiment.php diff --git a/src/Rox/Core/Impression/ImpressionArgs.php b/src/Rox/Core/Impression/ImpressionArgs.php index ef5a20d..7d85bba 100644 --- a/src/Rox/Core/Impression/ImpressionArgs.php +++ b/src/Rox/Core/Impression/ImpressionArgs.php @@ -3,7 +3,6 @@ namespace Rox\Core\Impression; use Rox\Core\Context\ContextInterface; -use Rox\Core\Impression\Models\Experiment; use Rox\Core\Impression\Models\ReportingValue; class ImpressionArgs @@ -13,11 +12,6 @@ class ImpressionArgs */ private $_reportingValue; - /** - * @var Experiment $_experiment - */ - private $_experiment; - /** * @var ContextInterface $_context */ @@ -26,16 +20,13 @@ class ImpressionArgs /** * ImpressionArgs constructor. * @param ReportingValue $_reportingValue - * @param Experiment|null $_experiment * @param ContextInterface|null $_context */ public function __construct( ReportingValue $_reportingValue, - $_experiment, - $_context) + $_context) { $this->_reportingValue = $_reportingValue; - $this->_experiment = $_experiment; $this->_context = $_context; } @@ -47,14 +38,6 @@ public function getReportingValue() return $this->_reportingValue; } - /** - * @return Experiment - */ - public function getExperiment() - { - return $this->_experiment; - } - /** * @return ContextInterface */ diff --git a/src/Rox/Core/Impression/ImpressionInvoker.php b/src/Rox/Core/Impression/ImpressionInvoker.php index 16be38b..1c5e517 100644 --- a/src/Rox/Core/Impression/ImpressionInvoker.php +++ b/src/Rox/Core/Impression/ImpressionInvoker.php @@ -4,7 +4,6 @@ use Rox\Core\Configuration\Models\ExperimentModel; use Rox\Core\Context\ContextInterface; -use Rox\Core\Impression\Models\Experiment; use Rox\Core\Impression\Models\ReportingValue; class ImpressionInvoker implements ImpressionInvokerInterface @@ -42,9 +41,7 @@ function invoke( */ private function _fireImpression(ReportingValue $value, $experiment, $context) { - $args = new ImpressionArgs($value, - $experiment != null ? new Experiment($experiment) : null, - $context); + $args = new ImpressionArgs($value, $context); foreach ($this->_handlers as $handler) { $handler($args); } diff --git a/src/Rox/Core/Impression/Models/Experiment.php b/src/Rox/Core/Impression/Models/Experiment.php deleted file mode 100644 index 15189f5..0000000 --- a/src/Rox/Core/Impression/Models/Experiment.php +++ /dev/null @@ -1,78 +0,0 @@ -_name = $experiment->getName(); - $this->_identifier = $experiment->getId(); - $this->_archived = $experiment->isArchived(); - $this->_labels = (new ArrayObject($experiment->getLabels()))->getArrayCopy(); - } - - /** - * @return string - */ - public function getName() - { - return $this->_name; - } - - /** - * @return string - */ - public function getIdentifier() - { - return $this->_identifier; - } - - /** - * @return bool - */ - public function isArchived() - { - return $this->_archived; - } - - /** - * @return string[] - */ - public function getLabels() - { - return $this->_labels; - } - - public function __toString() - { - return $this->_name; - } -} diff --git a/src/Rox/Core/Impression/Models/ReportingValue.php b/src/Rox/Core/Impression/Models/ReportingValue.php index d5df882..907a075 100644 --- a/src/Rox/Core/Impression/Models/ReportingValue.php +++ b/src/Rox/Core/Impression/Models/ReportingValue.php @@ -61,6 +61,6 @@ public function isTargeting() */ public function __toString() { - return "{$this->_name}, {$this->_value}"; + return "{$this->_name}, {$this->_value}, {$this->_targeting}"; } } diff --git a/src/Rox/Core/XPack/Analytics/Event.php b/src/Rox/Core/XPack/Analytics/Event.php index 699f6e0..df9cb2d 100644 --- a/src/Rox/Core/XPack/Analytics/Event.php +++ b/src/Rox/Core/XPack/Analytics/Event.php @@ -22,16 +22,6 @@ class Event implements JsonSerializable */ private $_distinctId; - /** - * @var string $_experimentId - */ - private $_experimentId; - - /** - * @var string $_experimentVersion - */ - private $_experimentVersion; - /** * @var string $_type */ @@ -44,7 +34,6 @@ class Event implements JsonSerializable public function __construct() { - $this->_experimentVersion = '0'; $this->_type = 'IMPRESSION'; $time = TimeUtils::currentTimeMillis(); $ms = isset($_ENV['rox.analytics.ms']) ? $_ENV['rox.analytics.ms'] : null; @@ -108,42 +97,6 @@ public function setDistinctId($distinctId) return $this; } - /** - * @return string - */ - public function getExperimentId() - { - return $this->_experimentId; - } - - /** - * @param string $experimentId - * @return Event - */ - public function setExperimentId($experimentId) - { - $this->_experimentId = $experimentId; - return $this; - } - - /** - * @return string - */ - public function getExperimentVersion() - { - return $this->_experimentVersion; - } - - /** - * @param string $experimentVersion - * @return Event - */ - public function setExperimentVersion($experimentVersion) - { - $this->_experimentVersion = $experimentVersion; - return $this; - } - /** * @return string */ @@ -189,8 +142,6 @@ public function jsonSerialize() 'flag' => $this->_flag, 'value' => $this->_value, 'distinctId' => $this->_distinctId, - 'experimentId' => $this->_experimentId, - 'experimentVersion' => $this->_experimentVersion, 'type' => $this->_type, 'time' => $this->_time ]; diff --git a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php index fa800db..4fb3b37 100644 --- a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php +++ b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php @@ -9,7 +9,6 @@ use Rox\Core\Consts\PropertyType; use Rox\Core\Context\ContextInterface; use Rox\Core\CustomProperties\CustomPropertyType; -use Rox\Core\Impression\Models\Experiment; use Rox\Core\Impression\Models\ReportingValue; use Rox\Core\Logging\LoggerFactory; use Rox\Core\Repositories\CustomPropertyRepositoryInterface; @@ -50,9 +49,9 @@ class XImpressionInvoker implements ImpressionInvokerInterface * @param ClientInterface|null $analyticsClient */ public function __construct( - InternalFlagsInterface $internalFlags, + InternalFlagsInterface $internalFlags, CustomPropertyRepositoryInterface $customPropertyRepository = null, - ClientInterface $analyticsClient = null) + ClientInterface $analyticsClient = null) { $this->_log = LoggerFactory::getInstance()->createLogger(self::class); $this->_customPropertyRepository = $customPropertyRepository; @@ -76,15 +75,15 @@ function register(callable $handler) * @param ContextInterface|null $context */ function invoke( - ReportingValue $value, - ExperimentModel $experiment = null, + ReportingValue $value, + ExperimentModel $experiment = null, ContextInterface $context = null) { try { $internalExperiment = $this->_internalFlags->isEnabled('rox.internal.analytics'); - if ($internalExperiment && $experiment != null && $this->_analyticsClient != null) { - $prop = $this->_customPropertyRepository->getCustomProperty($experiment->getStickinessProperty()); - if ($prop === null) { + if ($internalExperiment && $this->_analyticsClient) { + $prop = $experiment ? $this->_customPropertyRepository->getCustomProperty($experiment->getStickinessProperty()) : null; + if (!$prop) { $prop = $this->_customPropertyRepository->getCustomProperty('rox.' . PropertyType::getDistinctId()->getName()); } $distinctId = '(null_distinct_id'; @@ -98,8 +97,7 @@ function invoke( $this->_analyticsClient->track((new Event()) ->setFlag($value->getName()) ->setValue($value->getValue()) - ->setDistinctId($distinctId) - ->setExperimentId($experiment->getId())); + ->setDistinctId($distinctId)); } } catch (Exception $e) { @@ -108,10 +106,7 @@ function invoke( ]); } - $this->_fireImpression(new ImpressionArgs($value, - $experiment != null - ? new Experiment($experiment) - : null, $context)); + $this->_fireImpression(new ImpressionArgs($value, $context)); } /** diff --git a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php index a64b8d9..fc0cd30 100644 --- a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php +++ b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php @@ -10,7 +10,6 @@ use Rox\Core\CustomProperties\CustomProperty; use Rox\Core\CustomProperties\CustomPropertyType; use Rox\Core\Impression\ImpressionArgs; -use Rox\Core\Impression\Models\Experiment; use Rox\Core\Impression\Models\ReportingValue; use Rox\Core\Impression\XImpressionInvoker; use Rox\Core\Repositories\CustomPropertyRepositoryInterface; @@ -44,26 +43,17 @@ public function testWillTestImpressionInvokerInvokeAndParameters() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, ['label1'], 'stam'); - $experiment = new Experiment($originalExperiment); $isImpressionRaised = [false]; $impressionInvoker->register(function (ImpressionArgs $e) use ( $context, - $experiment, $reportingValue, $impressionInvoker, &$isImpressionRaised ) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); - $this->assertEquals($e->getExperiment()->getName(), $experiment->getName()); - $this->assertEquals($e->getExperiment()->isArchived(), $experiment->isArchived()); - $this->assertEquals(count($e->getExperiment()->getLabels()), count($experiment->getLabels())); - $this->assertEquals(count($e->getExperiment()->getLabels()), 1); - $this->assertEquals($e->getExperiment()->getLabels()[0], 'label1'); $this->assertEquals($e->getContext(), $context); - $isImpressionRaised[0] = true; }); @@ -72,18 +62,6 @@ public function testWillTestImpressionInvokerInvokeAndParameters() $this->assertTrue($isImpressionRaised[0]); } - public function testExperimentConstructor() - { - $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, ['name1'], 'stam'); - - $experiment = new Experiment($originalExperiment); - - $this->assertEquals($originalExperiment->getName(), $experiment->getName()); - $this->assertEquals($originalExperiment->getId(), $experiment->getIdentifier()); - $this->assertEquals($originalExperiment->isArchived(), $experiment->isArchived()); - $this->assertEquals($originalExperiment->getLabels()[0], 'name1'); - } - public function testReportingValueConstructor() { $reportingValue = new ReportingValue('pi', 'ka'); @@ -96,38 +74,70 @@ public function testImpressionArgsConstructor() { $context = (new ContextBuilder())->build(['obj1' => 1]); - $reportingValue = new ReportingValue('name', 'value'); - - $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); - $experiment = new Experiment($originalExperiment); - - $impressionArgs = new ImpressionArgs($reportingValue, $experiment, $context); + $reportingValue = new ReportingValue('name', 'value', true); + $impressionArgs = new ImpressionArgs($reportingValue, $context); $this->assertEquals($reportingValue, $impressionArgs->getReportingValue()); - $this->assertEquals($experiment, $impressionArgs->getExperiment()); $this->assertEquals($context, $impressionArgs->getContext()); } - public function testWillNotInvokeAnalyticsWhenFlagIsOff() + public function testWillInvokeAnalyticsWhenNoExperiment() { $internalFlags = \Mockery::mock(InternalFlagsInterface::class); - $analytics = \Mockery::mock(ClientInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); $context = (new ContextBuilder())->build(['obj1' => 1]); - $reportingValue = new ReportingValue('name', 'value'); - - $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); - $experiment = new Experiment($originalExperiment); + $reportingValue = new ReportingValue('name', 'value', false); $isImpressionRaised = [false]; - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker, &$isImpressionRaised) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker, &$isImpressionRaised) { + $this->assertEquals($e->getReportingValue(), $reportingValue); + $this->assertEquals($e->getContext(), $context); + $isImpressionRaised[0] = true; + }); + + $impressionInvoker->invoke($reportingValue, null, $context); + $this->assertTrue($isImpressionRaised[0]); + } + + public function testWillInvokeAnalyticsOnExperiment() + { + $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); + + $context = (new ContextBuilder())->build(['obj1' => 1]); + + $reportingValue = new ReportingValue('name', 'value', false); + $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, ['label1'], 'stam'); + + $isImpressionRaised = [false]; + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker, &$isImpressionRaised) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); + $isImpressionRaised[0] = true; + }); + + $impressionInvoker->invoke($reportingValue, $originalExperiment, $context); + + $this->assertTrue($isImpressionRaised[0]); + } + public function testWillNotInvokeAnalyticsWhenFlagIsOff() + { + $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); + + $context = (new ContextBuilder())->build(['obj1' => 1]); + + $reportingValue = new ReportingValue('name', 'value', true); + $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); + + $isImpressionRaised = [false]; + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker, &$isImpressionRaised) { + $this->assertEquals($e->getReportingValue(), $reportingValue); + $this->assertEquals($e->getContext(), $context); $isImpressionRaised[0] = true; }); @@ -172,12 +182,10 @@ public function testWillNotInvokeAnalyticsWhenIsRoxy() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); - $experiment = new Experiment($originalExperiment); - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); }); @@ -224,20 +232,16 @@ public function testWillInvokeAnalytics() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); - $experiment = new Experiment($originalExperiment); - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); }); $impressionInvoker->invoke($reportingValue, $originalExperiment, $context); $this->assertEquals($outEvent[0]->getDistinctId(), 'stam'); - $this->assertEquals($outEvent[0]->getExperimentId(), 'id'); - $this->assertEquals($outEvent[0]->getExperimentVersion(), '0'); $this->assertEquals($outEvent[0]->getFlag(), 'name'); $this->assertEquals($outEvent[0]->getValue(), 'value'); $this->assertEquals($outEvent[0]->getType(), 'IMPRESSION'); @@ -287,12 +291,10 @@ public function testWillInvokeAnalyticsWithStickinessProp() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stickProp'); - $experiment = new Experiment($originalExperiment); - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); }); @@ -300,8 +302,6 @@ public function testWillInvokeAnalyticsWithStickinessProp() $this->assertNotNull($outEvent[0]); $this->assertEquals($outEvent[0]->getDistinctId(), 'stamStick'); - $this->assertEquals($outEvent[0]->getExperimentId(), 'id'); - $this->assertEquals($outEvent[0]->getExperimentVersion(), '0'); $this->assertEquals($outEvent[0]->getFlag(), 'name'); $this->assertEquals($outEvent[0]->getValue(), 'value'); $this->assertEquals($outEvent[0]->getType(), 'IMPRESSION'); @@ -351,12 +351,10 @@ public function testWillInvokeAnalyticsWithDefaultPropWhenNoStickinessProp() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stickPropy'); - $experiment = new Experiment($originalExperiment); - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); }); @@ -364,8 +362,6 @@ public function testWillInvokeAnalyticsWithDefaultPropWhenNoStickinessProp() $this->assertNotNull($outEvent[0]); $this->assertEquals($outEvent[0]->getDistinctId(), 'stamDist'); - $this->assertEquals($outEvent[0]->getExperimentId(), 'id'); - $this->assertEquals($outEvent[0]->getExperimentVersion(), '0'); $this->assertEquals($outEvent[0]->getFlag(), 'name'); $this->assertEquals($outEvent[0]->getValue(), 'value'); $this->assertEquals($outEvent[0]->getType(), 'IMPRESSION'); @@ -407,12 +403,10 @@ public function testWillInvokeAnalyticsWithBadDistinctId() $reportingValue = new ReportingValue('name', 'value'); $originalExperiment = new ExperimentModel('id', 'name', 'cond', true, null, [], 'stam'); - $experiment = new Experiment($originalExperiment); - $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $experiment, $reportingValue, $impressionInvoker) { + $impressionInvoker->register(function (ImpressionArgs $e) use ($context, $reportingValue, $impressionInvoker) { $this->assertEquals($e->getReportingValue(), $reportingValue); - $this->assertEquals($e->getExperiment()->getIdentifier(), $experiment->getIdentifier()); $this->assertEquals($e->getContext(), $context); }); @@ -420,8 +414,6 @@ public function testWillInvokeAnalyticsWithBadDistinctId() $this->assertNotNull($outEvent[0]); $this->assertEquals($outEvent[0]->getDistinctId(), '(null_distinct_id'); - $this->assertEquals($outEvent[0]->getExperimentId(), 'id'); - $this->assertEquals($outEvent[0]->getExperimentVersion(), '0'); $this->assertEquals($outEvent[0]->getFlag(), 'name'); $this->assertEquals($outEvent[0]->getValue(), 'value'); $this->assertEquals($outEvent[0]->getType(), 'IMPRESSION'); diff --git a/tests/Rox/E2E/RoxE2ETests.php b/tests/Rox/E2E/RoxE2ETests.php index 2a65cd4..c3a924a 100644 --- a/tests/Rox/E2E/RoxE2ETests.php +++ b/tests/Rox/E2E/RoxE2ETests.php @@ -167,9 +167,7 @@ public function testImpressionHandler() $this->assertEquals("flagForImpressionWithExperimentAndContext", TestVars::$impressionReturnedArgs->getReportingValue()->getName()); $this->assertNotNull(TestVars::$impressionReturnedArgs); - $this->assertNotNull(TestVars::$impressionReturnedArgs->getExperiment()); - $this->assertEquals("5df8d8b930fcc301c34ad331", TestVars::$impressionReturnedArgs->getExperiment()->getIdentifier()); - $this->assertEquals("flag for impression with experiment and context", TestVars::$impressionReturnedArgs->getExperiment()->getName()); + $this->assertTrue(TestVars::$impressionReturnedArgs->getReportingValue()->isTargeting()); $this->assertEquals("val", TestVars::$impressionReturnedArgs->getContext()->get("var")); } From f939037da9fa967cf98f68bab635f8e871307af5 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Sat, 25 Sep 2021 10:12:19 +0300 Subject: [PATCH 03/15] Add Roxx expression operators --- composer.lock | 48 +++++++++------ src/Rox/Core/Consts/Build.php | 2 +- .../Entities/DoubleFlagValueConverter.php | 12 ++-- src/Rox/Core/Roxx/Parser.php | 41 +++++++++++-- src/Rox/Core/Roxx/ValueCompareExtensions.php | 61 ++++++++++--------- src/Rox/Core/Utils/DotNetCompat.php | 9 --- src/Rox/Core/Utils/NumericUtils.php | 54 ++++++++++++++++ tests/Rox/Core/Roxx/ParserTests.php | 49 ++++++++++++++- 8 files changed, 204 insertions(+), 72 deletions(-) create mode 100644 src/Rox/Core/Utils/NumericUtils.php diff --git a/composer.lock b/composer.lock index 7c39e83..ed142da 100644 --- a/composer.lock +++ b/composer.lock @@ -653,22 +653,22 @@ }, { "name": "ramsey/uuid", - "version": "3.9.3", + "version": "3.9.5", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" + "reference": "e62af90c1cf1552167e69e68c4c96b6a07a919c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/e62af90c1cf1552167e69e68c4c96b6a07a919c7", + "reference": "e62af90c1cf1552167e69e68c4c96b6a07a919c7", "shasum": "" }, "require": { "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | 9.99.99", - "php": "^5.4 | ^7 | ^8", + "paragonie/random_compat": "^1 | ^2 | ^9.99.99", + "php": "^5.4 | ^7 | ~8.0.0 | ~8.1.0", "symfony/polyfill-ctype": "^1.8" }, "replace": { @@ -677,14 +677,16 @@ "require-dev": { "codeception/aspect-mock": "^1 | ^2", "doctrine/annotations": "^1.2", - "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", - "jakub-onderka/php-parallel-lint": "^1", + "goaop/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", "mockery/mockery": "^0.9.11 | ^1", "moontoast/math": "^1.1", + "nikic/php-parser": "<=4.5.0", "paragonie/random-lib": "^2", - "php-mock/php-mock-phpunit": "^0.3 | ^1.1", - "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", - "squizlabs/php_codesniffer": "^3.5" + "php-mock/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpunit/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", + "squizlabs/php_codesniffer": "^3.5", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "ext-ctype": "Provides support for PHP Ctype functions", @@ -742,7 +744,17 @@ "source": "https://github.com/ramsey/uuid", "wiki": "https://github.com/ramsey/uuid/wiki" }, - "time": "2020-02-21T04:36:14+00:00" + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2021-09-24T18:27:08+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1264,16 +1276,16 @@ }, { "name": "mockery/mockery", - "version": "1.3.4", + "version": "1.3.5", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "31467aeb3ca3188158613322d66df81cedd86626" + "reference": "472fa8ca4e55483d55ee1e73c963718c4393791d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/31467aeb3ca3188158613322d66df81cedd86626", - "reference": "31467aeb3ca3188158613322d66df81cedd86626", + "url": "https://api.github.com/repos/mockery/mockery/zipball/472fa8ca4e55483d55ee1e73c963718c4393791d", + "reference": "472fa8ca4e55483d55ee1e73c963718c4393791d", "shasum": "" }, "require": { @@ -1327,9 +1339,9 @@ ], "support": { "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.3.4" + "source": "https://github.com/mockery/mockery/tree/1.3.5" }, - "time": "2021-02-24T09:51:00+00:00" + "time": "2021-09-13T15:33:03+00:00" }, { "name": "myclabs/deep-copy", diff --git a/src/Rox/Core/Consts/Build.php b/src/Rox/Core/Consts/Build.php index 8c0a3dd..576af44 100644 --- a/src/Rox/Core/Consts/Build.php +++ b/src/Rox/Core/Consts/Build.php @@ -5,5 +5,5 @@ class Build { const PLATFORM = 'PHP'; - const API_VERSION = '1.8.0'; + const API_VERSION = '1.9.0'; } diff --git a/src/Rox/Core/Entities/DoubleFlagValueConverter.php b/src/Rox/Core/Entities/DoubleFlagValueConverter.php index 8371ad6..5eff386 100644 --- a/src/Rox/Core/Entities/DoubleFlagValueConverter.php +++ b/src/Rox/Core/Entities/DoubleFlagValueConverter.php @@ -5,6 +5,7 @@ use Psr\Log\LoggerInterface; +use Rox\Core\Utils\NumericUtils; final class DoubleFlagValueConverter implements FlagValueConverter { @@ -34,15 +35,10 @@ function convertToString($value) */ function normalizeValue($stringValue, $alternativeValue, LoggerInterface $log = null) { - if ($stringValue) { - if (preg_match('/^\d+(\.\d*)?$/', $stringValue) && - ((($doubleValue = floatval($stringValue)) !== 0.0) || - preg_match('/^0+(\.0*)?$/', $stringValue))) { - return $doubleValue; - } else if ($log) { - $log->warning("Experiment type mismatch (double), returning default value"); - } + if ($stringValue && NumericUtils::parseNumber($stringValue, $doubleValue)) { + return $doubleValue; } + $log->warning("Experiment type mismatch (double), returning default value"); return floatval($alternativeValue); } } diff --git a/src/Rox/Core/Roxx/Parser.php b/src/Rox/Core/Roxx/Parser.php index 4d92081..ff3187f 100644 --- a/src/Rox/Core/Roxx/Parser.php +++ b/src/Rox/Core/Roxx/Parser.php @@ -7,7 +7,7 @@ use Rox\Core\Context\ContextBuilder; use Rox\Core\Context\ContextInterface; use Rox\Core\Logging\LoggerFactory; -use Rox\Core\Utils\DotNetCompat; +use Rox\Core\Utils\NumericUtils; use Rox\Core\Utils\TimeUtils; class Parser implements ParserInterface @@ -105,6 +105,22 @@ function (ParserInterface $parser, StackInterface $stack, ContextInterface $cont $stack->push($op1 !== $op2); }); + $this->addOperator("numne", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { + $op1 = $stack->pop(); + $op2 = $stack->pop(); + + $decimal1 = 0; + $decimal2 = 0; + $result = false; + + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = !NumericUtils::numbersEqual($decimal1, $decimal2); + } + + $stack->push($result); + }); + $this->addOperator("eq", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { $op1 = $stack->pop(); $op2 = $stack->pop(); @@ -117,15 +133,28 @@ function (ParserInterface $parser, StackInterface $stack, ContextInterface $cont $op2 = false; } - if (DotNetCompat::isNumericStrict($op1)) { - $op1 = (float)$op1; // cast int to float for comparison + if (NumericUtils::isNumericStrict($op1) && NumericUtils::isNumericStrict($op2)) { + $stack->push(NumericUtils::numbersEqual($op1, $op2)); + return; } - if (DotNetCompat::isNumericStrict($op2)) { - $op2 = (float)$op2; // cast int to float for comparison + $stack->push($op1 === $op2); + }); + + $this->addOperator("numeq", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { + $op1 = $stack->pop(); + $op2 = $stack->pop(); + + $decimal1 = 0; + $decimal2 = 0; + $result = false; + + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = NumericUtils::numbersEqual($decimal1, $decimal2); } - $stack->push($op1 === $op2); + $stack->push($result); }); $this->addOperator("not", diff --git a/src/Rox/Core/Roxx/ValueCompareExtensions.php b/src/Rox/Core/Roxx/ValueCompareExtensions.php index 526eb31..4876d73 100644 --- a/src/Rox/Core/Roxx/ValueCompareExtensions.php +++ b/src/Rox/Core/Roxx/ValueCompareExtensions.php @@ -4,6 +4,7 @@ use Rox\Core\Context\ContextInterface; use Rox\Core\Utils\DotNetCompat; +use Rox\Core\Utils\NumericUtils; class ValueCompareExtensions { @@ -23,60 +24,64 @@ public function extend() $op1 = $stack->pop(); $op2 = $stack->pop(); - if (!(DotNetCompat::isNumericStrict($op1)) || !(DotNetCompat::isNumericStrict($op2))) { - $stack->push(false); - return; - } + $decimal1 = 0; + $decimal2 = 0; + $result = false; - $decimal1 = (float)$op1; - $decimal2 = (float)$op2; + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = $decimal1 < $decimal2; + } - $stack->push($decimal1 < $decimal2); + $stack->push($result); }); $this->_parser->addOperator("lte", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { $op1 = $stack->pop(); $op2 = $stack->pop(); - if (!(DotNetCompat::isNumericStrict($op1)) || !(DotNetCompat::isNumericStrict($op2))) { - $stack->push(false); - return; - } + $decimal1 = 0; + $decimal2 = 0; + $result = false; - $decimal1 = (float)$op1; - $decimal2 = (float)$op2; + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = $decimal1 < $decimal2 || NumericUtils::numbersEqual($decimal1, $decimal2); + } - $stack->push($decimal1 <= $decimal2); + $stack->push($result); }); $this->_parser->addOperator("gt", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { $op1 = $stack->pop(); $op2 = $stack->pop(); - if (!(DotNetCompat::isNumericStrict($op1)) || !(DotNetCompat::isNumericStrict($op2))) { - $stack->push(false); - return; - } + $decimal1 = 0; + $decimal2 = 0; + $result = false; - $decimal1 = (float)$op1; - $decimal2 = (float)$op2; + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = $decimal1 > $decimal2; + } - $stack->push($decimal1 > $decimal2); + $stack->push($result); }); $this->_parser->addOperator("gte", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { $op1 = $stack->pop(); $op2 = $stack->pop(); - if (!(DotNetCompat::isNumericStrict($op1)) || !(DotNetCompat::isNumericStrict($op2))) { - $stack->push(false); - return; - } + $decimal1 = 0; + $decimal2 = 0; + $result = false; - $decimal1 = (float)$op1; - $decimal2 = (float)$op2; + if (NumericUtils::parseNumber($op1, $decimal1) && + NumericUtils::parseNumber($op2, $decimal2)) { + $result = $decimal1 > $decimal2 || NumericUtils::numbersEqual($decimal1, $decimal2); + } - $stack->push($decimal1 >= $decimal2); + $stack->push($result); }); $this->_parser->addOperator("semverNe", function (ParserInterface $parser, StackInterface $stack, ContextInterface $context) { diff --git a/src/Rox/Core/Utils/DotNetCompat.php b/src/Rox/Core/Utils/DotNetCompat.php index 1be99ec..1554fb7 100644 --- a/src/Rox/Core/Utils/DotNetCompat.php +++ b/src/Rox/Core/Utils/DotNetCompat.php @@ -21,13 +21,4 @@ static function toJson($value) $json = preg_replace("/\[\s+\]/m", '[]', $json); // PHP 5.X issue, it outputs empty JSON array as [ ... newlines ... ] return preg_replace('/^( +?)\\1(?=[^ ])/m', '$1', $json); // intend by 2 instead of 4, just as in .NET } - - /** - * @param mixed $val - * @return bool - */ - public static function isNumericStrict($val) - { - return is_int($val) || is_float($val); - } } diff --git a/src/Rox/Core/Utils/NumericUtils.php b/src/Rox/Core/Utils/NumericUtils.php new file mode 100644 index 0000000..dec3721 --- /dev/null +++ b/src/Rox/Core/Utils/NumericUtils.php @@ -0,0 +1,54 @@ +assertSame($parser->evaluateExpression("not(and(false, or(false, true)))")->boolValue(), true); } + public function testNumeqExpressionsEvaluation() + { + $parser = new Parser(); + + $this->assertSame($parser->evaluateExpression("numeq(\"la la\", \"la la\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numeq(\"la la\", \"la,la\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numeq(\"lala\", \"lala\")")->boolValue(), false); + + $this->assertSame($parser->evaluateExpression("numeq(\"10\", \"10\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numeq(\"10\", 10)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numeq(10, \"10\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numeq(10, 10)")->boolValue(), true); + + $this->assertSame($parser->evaluateExpression("numeq(\"10\", \"11\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numeq(\"10\", 11)")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numeq(10, \"11\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numeq(10, 11)")->boolValue(), false); + + $this->assertSame($parser->evaluateExpression("numne(\"la la\", \"la la\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numne(\"la la\", \"la,la\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numne(\"lala\", \"lala\")")->boolValue(), false); + + $this->assertSame($parser->evaluateExpression("numne(\"10\", \"10\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numne(\"10\", 10)")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numne(10, \"10\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("numne(10, 10)")->boolValue(), false); + + $this->assertSame($parser->evaluateExpression("numne(\"10\", \"11\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numne(\"10\", 11)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numne(10, \"11\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("numne(10, 11)")->boolValue(), true); + } + public function testEqExpressionsEvaluation() { $parser = new Parser(); @@ -67,6 +100,9 @@ public function testEqExpressionsEvaluation() $this->assertSame($parser->evaluateExpression("eq(\"la la\", \"la la\")")->boolValue(), true); $this->assertSame($parser->evaluateExpression("eq(\"la la\", \"la,la\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("eq(\"lala\", \"lala\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("eq(\"10\", \"10\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("eq(\"10\", 10)")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("eq(10, 10)")->boolValue(), true); $this->assertSame($parser->evaluateExpression("ne(100.123, 100.321)")->boolValue(), true); $this->assertSame($parser->evaluateExpression("not(eq(undefined, undefined))")->boolValue(), false); $this->assertSame($parser->evaluateExpression("not(eq(not(undefined), undefined))")->boolValue(), true); @@ -84,14 +120,23 @@ public function testComparisonExpressionsEvaluation() $this->assertSame($parser->evaluateExpression("lt(500, 100)")->boolValue(), false); $this->assertSame($parser->evaluateExpression("lt(500, 500)")->boolValue(), false); $this->assertSame($parser->evaluateExpression("lt(500, 500.54)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("lt(500, \"500.54\")")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("lt(500, \"500.54a\")")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("lte(500, 500)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("lte(\"500\", 501)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("lte(\"501\", \"500\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("gt(500, 100)")->boolValue(), true); $this->assertSame($parser->evaluateExpression("gt(500, 500)")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("gt(500, \"500\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("gt(500.54, 500)")->boolValue(), true); - $this->assertSame($parser->evaluateExpression("gte(500, 500)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("gt(\"500.54\", 500)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("gt(\"50a\", 500)")->boolValue(), false); - $this->assertSame($parser->evaluateExpression("gte(\"500\", 500)")->boolValue(), false); + $this->assertSame($parser->evaluateExpression("gte(500, 500)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("gte(\"500\", 500)")->boolValue(), true); + $this->assertSame($parser->evaluateExpression("gte(\"505a\", 500)")->boolValue(), false); } public function testSemVerComparisonEvaluation() From 596e2a1c95b41e6807e41f553a4ca86ea329a703 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Sat, 25 Sep 2021 22:57:34 +0300 Subject: [PATCH 04/15] Global context set with Rox.setContext(globalContext) is not always used --- src/Rox/Core/Core.php | 12 +++++------- src/Rox/Core/Entities/RoxStringBase.php | 13 ++----------- src/Rox/Core/Roxx/Parser.php | 15 +++++++++++++++ src/Rox/Core/Roxx/ParserInterface.php | 10 ++++++++++ src/Rox/Server/Rox.php | 4 ++-- tests/Rox/E2E/RoxE2ETests.php | 15 ++++++++++++++- tests/Rox/Server/Flags/RoxStringTests.php | 15 ++++++++++++++- 7 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/Rox/Core/Core.php b/src/Rox/Core/Core.php index 7aa7c01..cfb289d 100644 --- a/src/Rox/Core/Core.php +++ b/src/Rox/Core/Core.php @@ -195,9 +195,9 @@ public function __construct() * @param RoxOptionsInterface|null $roxOptions */ public function setup( - SdkSettingsInterface $sdkSettings, + SdkSettingsInterface $sdkSettings, DevicePropertiesInterface $deviceProperties, - $roxOptions) + $roxOptions) { $roxyUrl = ($roxOptions != null) ? $roxOptions->getRoxyURL() : null; if ($roxyUrl == null) { @@ -320,13 +320,11 @@ public function register($ns, $roxContainer) } /** - * @param ContextInterface $context + * @param ContextInterface|null $context */ - public function setContext(ContextInterface $context) + public function setContext($context) { - foreach (array_values($this->_flagRepository->getAllFlags()) as $flag) { - $flag->setContext($context); - } + $this->_parser->setGlobalContext($context); } /** diff --git a/src/Rox/Core/Entities/RoxStringBase.php b/src/Rox/Core/Entities/RoxStringBase.php index ce0d899..4f5614e 100644 --- a/src/Rox/Core/Entities/RoxStringBase.php +++ b/src/Rox/Core/Entities/RoxStringBase.php @@ -42,11 +42,6 @@ abstract class RoxStringBase */ private $_parser; - /** - * @var ContextInterface $_globalContext - */ - private $_globalContext; - /** * @var ImpressionInvokerInterface $_impressionInvoker */ @@ -235,11 +230,6 @@ public function setForEvaluation($parser, $experiment, $impressionInvoker) $this->_impressionInvoker = $impressionInvoker; } - public function setContext(ContextInterface $globalContext) - { - $this->_globalContext = $globalContext; - } - /** * @param ContextInterface|null $context * @param string|null $alternativeDefaultValue @@ -322,7 +312,8 @@ private function _getFlagValue(FlagValueConverter $converter, $context = null, $ */ private function _getExperimentValue($context, $evaluationContext = null) { - $mergedContext = new MergedContext($this->_globalContext, $context); + $globalContext = ($this->_parser != null) ? $this->_parser->getGlobalContext() : null; + $mergedContext = new MergedContext($globalContext, $context); if ($this->_parser != null && $this->_condition) { return $this->_parser->evaluateExpression($this->_condition, $mergedContext, $evaluationContext); } diff --git a/src/Rox/Core/Roxx/Parser.php b/src/Rox/Core/Roxx/Parser.php index ff3187f..128464d 100644 --- a/src/Rox/Core/Roxx/Parser.php +++ b/src/Rox/Core/Roxx/Parser.php @@ -22,6 +22,11 @@ class Parser implements ParserInterface */ private $_operatorsMap = []; + /** + * @var ContextInterface $_globalContext + */ + private $_globalContext; + /** * Parser constructor. * @throws Exception @@ -266,4 +271,14 @@ public function evaluateExpression($expression, $context = null, $evaluationCont return new EvaluationResult($result, $context); } + + function setGlobalContext($context) + { + $this->_globalContext = $context; + } + + function getGlobalContext() + { + return $this->_globalContext; + } } diff --git a/src/Rox/Core/Roxx/ParserInterface.php b/src/Rox/Core/Roxx/ParserInterface.php index 5d9698c..673f633 100644 --- a/src/Rox/Core/Roxx/ParserInterface.php +++ b/src/Rox/Core/Roxx/ParserInterface.php @@ -19,4 +19,14 @@ function evaluateExpression($expression, $context, $evaluationContext = null); * @param callable $operation */ function addOperator($name, $operation); + + /** + * @param ContextInterface $context + */ + function setGlobalContext($context); + + /** + * @return ContextInterface|null + */ + function getGlobalContext(); } diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index 0d6ed74..8478e4f 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -101,9 +101,9 @@ public static function register($namespace, $roxContainer) } /** - * @param ContextInterface $context + * @param ContextInterface|null $context */ - public static function setContext(ContextInterface $context) + public static function setContext($context) { self::getCore()->setContext($context); } diff --git a/tests/Rox/E2E/RoxE2ETests.php b/tests/Rox/E2E/RoxE2ETests.php index c3a924a..00c1488 100644 --- a/tests/Rox/E2E/RoxE2ETests.php +++ b/tests/Rox/E2E/RoxE2ETests.php @@ -84,7 +84,7 @@ public function testVariantOverwritten() public function testRegisterAfterSetup() { $this->assertTrue(ContainerTwo::getInstance()->flag2->isEnabled()); - $this->assertEquals("red", ContainerTwo::getInstance()->variant2->getValue()); + $this->assertEquals("red", ContainerTwo::getInstance()->variant2->getValue()); Rox::register("afterSetup", ContainerTwo::getInstance()); $this->assertFalse(ContainerTwo::getInstance()->flag2->isEnabled()); $this->assertEquals("green", ContainerTwo::getInstance()->variant2->getValue()); @@ -133,6 +133,19 @@ public function testVariantWithContext() $this->assertEquals(Container::getInstance()->variantWithContext->getValue($someNegativeContext), "red"); } + public function testVariantWithGlobalContext() + { + $somePositiveContext = (new ContextBuilder())->build([ + "isDuckAndCover" => true + ]); + + $this->assertEquals(Container::getInstance()->variantWithContext->getValue(), "red"); + Rox::setContext($somePositiveContext); + $this->assertEquals(Container::getInstance()->variantWithContext->getValue(), "blue"); + Rox::setContext(null); + $this->assertEquals(Container::getInstance()->variantWithContext->getValue(), "red"); + } + public function testTargetGroupsAllAnyNone() { TestVars::$targetGroup1 = TestVars::$targetGroup2 = true; diff --git a/tests/Rox/Server/Flags/RoxStringTests.php b/tests/Rox/Server/Flags/RoxStringTests.php index 8d64b0a..bbf006d 100644 --- a/tests/Rox/Server/Flags/RoxStringTests.php +++ b/tests/Rox/Server/Flags/RoxStringTests.php @@ -131,7 +131,11 @@ public function testWillReturnDefaultValueWhenNoParserOrCondition() $this->assertEquals('1', $variant->getValue(null)); - $parser = \Mockery::mock(ParserInterface::class); + $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('getGlobalContext') + ->andReturn(null) + ->getMock(); + $variant->setForEvaluation($parser, null, null); $this->assertEquals('1', $variant->getValue(null)); @@ -144,6 +148,9 @@ public function testWillReturnDefaultValueWhenNoParserOrCondition() public function testWillExpressionValueWhenResultNotInOptions() { $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('getGlobalContext') + ->andReturn(null) + ->getMock() ->shouldReceive('evaluateExpression') ->andReturn(new EvaluationResult('xxx')) ->getMock(); @@ -160,6 +167,9 @@ public function testWillExpressionValueWhenResultNotInOptions() public function testWillReturnValueWhenOnEvaluation() { $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('getGlobalContext') + ->andReturn(null) + ->getMock() ->shouldReceive('evaluateExpression') ->andReturn(new EvaluationResult('2')) ->getMock(); @@ -176,6 +186,9 @@ public function testWillReturnValueWhenOnEvaluation() public function testWillRaiseImpression() { $parser = \Mockery::mock(ParserInterface::class) + ->shouldReceive('getGlobalContext') + ->andReturn(null) + ->getMock() ->shouldReceive('evaluateExpression') ->andReturn(new EvaluationResult('2')) ->getMock(); From a3b6fb62e42b79e53d00ce7624bc5c9d88ac33ca Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Sat, 25 Sep 2021 23:05:02 +0300 Subject: [PATCH 05/15] Empty string tokenize bug --- src/Rox/Core/Roxx/TokenizedExpression.php | 2 +- tests/Rox/Core/Roxx/ParserTests.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Rox/Core/Roxx/TokenizedExpression.php b/src/Rox/Core/Roxx/TokenizedExpression.php index 0d5fbdf..3955ac1 100644 --- a/src/Rox/Core/Roxx/TokenizedExpression.php +++ b/src/Rox/Core/Roxx/TokenizedExpression.php @@ -89,7 +89,7 @@ private function _nodeFromToken($o) if ($s == Symbols::RoxxFalse) return new Node(Node::TYPE_RAND, false); if ($s == Symbols::RoxxUndefined) return new Node(Node::TYPE_RAND, TokenType::getUndefined()); if ($tokenType === TokenType::getString()) - return new Node(Node::TYPE_RAND, substr($s, 1, strlen($s) - 1)); + return new Node(Node::TYPE_RAND, substr($s, 1, strlen($s) - 2)); if ($tokenType === TokenType::getNumber()) { if (is_numeric($s)) { if (strpos($s, ".") !== false) { diff --git a/tests/Rox/Core/Roxx/ParserTests.php b/tests/Rox/Core/Roxx/ParserTests.php index ca2d912..781630a 100644 --- a/tests/Rox/Core/Roxx/ParserTests.php +++ b/tests/Rox/Core/Roxx/ParserTests.php @@ -53,6 +53,7 @@ public function testSimpleExpressionEvaluation() { $parser = new Parser(); + $this->assertSame($parser->evaluateExpression("\"\"")->stringValue(), ""); $this->assertSame($parser->evaluateExpression("true")->stringValue(), "true"); $this->assertSame($parser->evaluateExpression("\"red\"")->stringValue(), "red"); $this->assertSame($parser->evaluateExpression("and(true, or(true, true))")->boolValue(), true); From 081e30e7e087301648de3f458d26fb146099304e Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Sun, 26 Sep 2021 17:24:10 +0300 Subject: [PATCH 06/15] Rox.Register overloading - No need for Namespace --- src/Rox/Server/Rox.php | 4 ++-- tests/Rox/E2E/RoxE2ETests.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index 8478e4f..bae441c 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -92,10 +92,10 @@ public static function setup($apiKey, RoxOptions $roxOptions = null) } /** - * @param string $namespace * @param object $roxContainer + * @param string $namespace */ - public static function register($namespace, $roxContainer) + public static function register($roxContainer, $namespace = "") { self::getCore()->register($namespace, $roxContainer); } diff --git a/tests/Rox/E2E/RoxE2ETests.php b/tests/Rox/E2E/RoxE2ETests.php index 00c1488..80a1481 100644 --- a/tests/Rox/E2E/RoxE2ETests.php +++ b/tests/Rox/E2E/RoxE2ETests.php @@ -47,7 +47,7 @@ public static function setUpBeforeClass() ->setCacheStorage(new VolatileRuntimeStorage()) ->setLogCacheHitsAndMisses(true)); - Rox::register("", Container::getInstance()); + Rox::register(Container::getInstance()); TestCustomPropsCreator::createCustomProps(); Rox::setup("5df8d5e802e23378643705bf", $options); @@ -85,7 +85,7 @@ public function testRegisterAfterSetup() { $this->assertTrue(ContainerTwo::getInstance()->flag2->isEnabled()); $this->assertEquals("red", ContainerTwo::getInstance()->variant2->getValue()); - Rox::register("afterSetup", ContainerTwo::getInstance()); + Rox::register(ContainerTwo::getInstance(), "afterSetup"); $this->assertFalse(ContainerTwo::getInstance()->flag2->isEnabled()); $this->assertEquals("green", ContainerTwo::getInstance()->variant2->getValue()); } From 5a170900de3543d5aa7eb504f8e4dec324d9e4a1 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Mon, 27 Sep 2021 16:41:39 +0300 Subject: [PATCH 07/15] User Space Error handling --- composer.lock | 12 +- .../AbstractConfigurationFetchedInvoker.php | 23 ++- src/Rox/Core/Core.php | 29 +++- .../CustomProperties/DynamicProperties.php | 10 +- .../DynamicPropertiesInterface.php | 5 + .../Core/ErrorHandling/ExceptionTrigger.php | 11 ++ .../UserspaceHandlerException.php | 54 +++++++ .../UserspaceUnhandledErrorArgs.php | 60 ++++++++ .../UserspaceUnhandledErrorInvoker.php | 50 +++++++ ...serspaceUnhandledErrorInvokerInterface.php | 21 +++ src/Rox/Core/Impression/ImpressionInvoker.php | 27 +++- src/Rox/Core/Roxx/Parser.php | 25 +++- src/Rox/Core/Roxx/PropertiesExtensions.php | 26 +++- .../XConfigurationFetchedInvoker.php | 38 ----- .../XPack/Impression/XImpressionInvoker.php | 24 +++- src/Rox/Server/Rox.php | 8 ++ .../ConfigurationFetchedInvokerTests.php} | 60 ++++++-- .../ConfigurationParserTests.php | 15 +- .../UserspaceUnhandledErrorInvokerTests.php | 103 +++++++++++++ .../Impressions/ImpressionInvokerTests.php | 96 ++++++++----- .../Network/ConfigurationFetcherRoxyTests.php | 26 ++-- .../Network/ConfigurationFetcherTests.php | 44 +++--- .../Core/Roxx/ExperimentsExtensionsTests.php | 32 +++-- tests/Rox/Core/Roxx/ParserTests.php | 32 +++-- .../Core/Roxx/PropertiesExtensionsTests.php | 135 ++++++++++++++++-- tests/Rox/Server/Flags/RoxFlagsTestCase.php | 14 +- 26 files changed, 780 insertions(+), 200 deletions(-) create mode 100644 src/Rox/Core/ErrorHandling/ExceptionTrigger.php create mode 100644 src/Rox/Core/ErrorHandling/UserspaceHandlerException.php create mode 100644 src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorArgs.php create mode 100644 src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvoker.php create mode 100644 src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerInterface.php delete mode 100644 src/Rox/Core/XPack/Configuration/XConfigurationFetchedInvoker.php rename tests/Rox/Core/{XPack/Configuration/XConfigurationFetchedInvokerTests.php => Configuration/ConfigurationFetchedInvokerTests.php} (64%) create mode 100644 tests/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerTests.php diff --git a/composer.lock b/composer.lock index ed142da..d8a1493 100644 --- a/composer.lock +++ b/composer.lock @@ -653,22 +653,22 @@ }, { "name": "ramsey/uuid", - "version": "3.9.5", + "version": "3.9.6", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "e62af90c1cf1552167e69e68c4c96b6a07a919c7" + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/e62af90c1cf1552167e69e68c4c96b6a07a919c7", - "reference": "e62af90c1cf1552167e69e68c4c96b6a07a919c7", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ffa80ab953edd85d5b6c004f96181a538aad35a3", + "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3", "shasum": "" }, "require": { "ext-json": "*", "paragonie/random_compat": "^1 | ^2 | ^9.99.99", - "php": "^5.4 | ^7 | ~8.0.0 | ~8.1.0", + "php": "^5.4 | ^7.0 | ^8.0", "symfony/polyfill-ctype": "^1.8" }, "replace": { @@ -754,7 +754,7 @@ "type": "tidelift" } ], - "time": "2021-09-24T18:27:08+00:00" + "time": "2021-09-25T23:07:42+00:00" }, { "name": "symfony/polyfill-ctype", diff --git a/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php b/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php index 40395d8..e537dcc 100644 --- a/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php +++ b/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php @@ -2,6 +2,10 @@ namespace Rox\Core\Configuration; +use Exception; +use Rox\Core\ErrorHandling\ExceptionTrigger; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; + abstract class AbstractConfigurationFetchedInvoker implements ConfigurationFetchedInvokerInterface { /** @@ -9,6 +13,19 @@ abstract class AbstractConfigurationFetchedInvoker implements ConfigurationFetch */ private $_eventHandlers = []; + /** + * @var UserspaceUnhandledErrorInvokerInterface $_userUnhandledErrorInvoker + */ + protected $_userUnhandledErrorInvoker; + + /** + * @param UserspaceUnhandledErrorInvokerInterface $userUnhandledErrorInvoker + */ + public function __construct(UserspaceUnhandledErrorInvokerInterface $userUnhandledErrorInvoker) + { + $this->_userUnhandledErrorInvoker = $userUnhandledErrorInvoker; + } + /** * @param callable $handler */ @@ -54,7 +71,11 @@ protected abstract function internalInvoke(ConfigurationFetchedArgs $cfa); protected final function fireConfigurationFetched(ConfigurationFetchedArgs $args) { foreach ($this->_eventHandlers as $eventHandler) { - $eventHandler($args); + try { + $eventHandler($args); + } catch (Exception $e) { + $this->_userUnhandledErrorInvoker->invoke($this, ExceptionTrigger::ConfigurationFetchedHandler, $e); + } } } } diff --git a/src/Rox/Core/Core.php b/src/Rox/Core/Core.php index cfb289d..5ec64fc 100644 --- a/src/Rox/Core/Core.php +++ b/src/Rox/Core/Core.php @@ -29,6 +29,8 @@ use Rox\Core\CustomProperties\DynamicPropertiesInterface; use Rox\Core\Entities\EntitiesProviderInterface; use Rox\Core\Entities\FlagSetter; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvoker; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Impression\ImpressionInvoker; use Rox\Core\Impression\ImpressionInvokerInterface; use Rox\Core\Impression\XImpressionInvoker; @@ -59,7 +61,6 @@ use Rox\Core\Security\SignatureVerifier; use Rox\Core\XPack\Analytics\AnalyticsClient; use Rox\Core\XPack\Client\XBUID; -use Rox\Core\XPack\Configuration\XConfigurationFetchedInvoker; use Rox\Core\XPack\Network\StateSender; use Rox\Core\XPack\Reporting\XErrorReporter; use Rox\Core\XPack\Security\XAPIKeyVerifier; @@ -165,6 +166,11 @@ class Core */ private $_dynamicProperties; + /** + * @var UserspaceUnhandledErrorInvokerInterface $_userUnhandledErrorInvoker + */ + private $_userUnhandledErrorInvoker; + /** * @var LoggerInterface $_log */ @@ -180,7 +186,8 @@ public function __construct() $this->_targetGroupRepository = new TargetGroupRepository(); $this->_experimentRepository = new ExperimentRepository(); $this->_dynamicProperties = new DynamicProperties(); - $this->_parser = new Parser(); + $this->_userUnhandledErrorInvoker = new UserspaceUnhandledErrorInvoker(); + $this->_parser = new Parser($this->_userUnhandledErrorInvoker); $experimentsExtensions = new ExperimentsExtensions($this->_parser, $this->_targetGroupRepository, $this->_flagRepository, $this->_experimentRepository); $propertiesExtensions = new PropertiesExtensions($this->_parser, $this->_customPropertyRepository, $this->_dynamicProperties); $experimentsExtensions->extend(); @@ -189,6 +196,11 @@ public function __construct() $this->_log = LoggerFactory::getInstance()->createLogger(self::class); } + public function setUserspaceUnhandledErrorHandler(callable $userspaceUnhandledErrorHandler) + { + $this->_userUnhandledErrorInvoker->register($userspaceUnhandledErrorHandler); + } + /** * @param SdkSettingsInterface $sdkSettings * @param DevicePropertiesInterface $deviceProperties @@ -228,21 +240,24 @@ public function setup( $apiKeyVerifier = null; $this->_errorReporter = new XErrorReporter($httpClientFactory->createHttpClient(), $deviceProperties, $buid); + $this->_configurationFetchedInvoker = new ConfigurationFetchedInvoker($this->_userUnhandledErrorInvoker); if ($roxyUrl != null) { - $this->_configurationFetchedInvoker = new ConfigurationFetchedInvoker(); $this->_configurationFetcher = new ConfigurationFetcherRoxy($httpClient, $deviceProperties, $buid, $this->_configurationFetchedInvoker, $roxyUrl, $this->_errorReporter); - $this->_impressionInvoker = new ImpressionInvoker(); + $this->_impressionInvoker = new ImpressionInvoker($this->_userUnhandledErrorInvoker); $signature = new SignatureVerifier(); $apiKeyVerifier = new APIKeyVerifier(); } else { $stateSenderHttpClient = $this->_createHttpClientFactory($roxOptions, self::STATE_STORE_CACHE_TTL_SECONDS) ->createHttpClient(); $this->_stateSender = new StateSender($stateSenderHttpClient, $deviceProperties, $this->_flagRepository, $this->_customPropertyRepository); - $this->_configurationFetchedInvoker = new XConfigurationFetchedInvoker($this); $this->_configurationFetcher = new ConfigurationFetcher($httpClient, $buid, $deviceProperties, $this->_configurationFetchedInvoker, $this->_errorReporter); - $this->_impressionInvoker = new XImpressionInvoker($this->_internalFlags, $this->_customPropertyRepository, - new AnalyticsClient($this->_deviceProperties, $this->_internalFlags, $httpClientFactory->createHttpClient())); + $analyticsClient = new AnalyticsClient($this->_deviceProperties, $this->_internalFlags, $httpClientFactory->createHttpClient()); + $this->_impressionInvoker = new XImpressionInvoker( + $this->_internalFlags, + $this->_userUnhandledErrorInvoker, + $this->_customPropertyRepository, + $analyticsClient); $signature = new XSignatureVerifier(); $apiKeyVerifier = new XAPIKeyVerifier($sdkSettings); } diff --git a/src/Rox/Core/CustomProperties/DynamicProperties.php b/src/Rox/Core/CustomProperties/DynamicProperties.php index d2fe804..59192ad 100644 --- a/src/Rox/Core/CustomProperties/DynamicProperties.php +++ b/src/Rox/Core/CustomProperties/DynamicProperties.php @@ -28,7 +28,15 @@ function getDynamicPropertiesRule() return $this->_handler != null ? $this->_handler : function ($propName, ContextInterface $context) { - return ($context != null) ? $context->get($propName) : null; + return $context->get($propName); }; } + + /** + * @return bool + */ + function isDefault() + { + return !$this->_handler; + } } diff --git a/src/Rox/Core/CustomProperties/DynamicPropertiesInterface.php b/src/Rox/Core/CustomProperties/DynamicPropertiesInterface.php index aeeb568..449891a 100644 --- a/src/Rox/Core/CustomProperties/DynamicPropertiesInterface.php +++ b/src/Rox/Core/CustomProperties/DynamicPropertiesInterface.php @@ -14,4 +14,9 @@ function setDynamicPropertiesRule(callable $handler); * @return callable */ function getDynamicPropertiesRule(); + + /** + * @return bool + */ + function isDefault(); } \ No newline at end of file diff --git a/src/Rox/Core/ErrorHandling/ExceptionTrigger.php b/src/Rox/Core/ErrorHandling/ExceptionTrigger.php new file mode 100644 index 0000000..006fbed --- /dev/null +++ b/src/Rox/Core/ErrorHandling/ExceptionTrigger.php @@ -0,0 +1,11 @@ +_exceptionSource = $exceptionSource; + $this->_exceptionTrigger = $exceptionTrigger; + } + + /** + * @return mixed + */ + public function getExceptionSource() + { + return $this->_exceptionSource; + } + + /** + * @return int + */ + public function getExceptionTrigger() + { + return $this->_exceptionTrigger; + } + + /** + * @return Exception + */ + public function getException() + { + return $this->getPrevious(); + } +} diff --git a/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorArgs.php b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorArgs.php new file mode 100644 index 0000000..d940940 --- /dev/null +++ b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorArgs.php @@ -0,0 +1,60 @@ +_exceptionTrigger = $_exceptionTrigger; + $this->_exceptionSource = $_exceptionSource; + $this->_exception = $_exception; + } + + /** + * @return mixed + */ + public function getExceptionSource() + { + return $this->_exceptionSource; + } + + /** + * @return int + */ + public function getExceptionTrigger() + { + return $this->_exceptionTrigger; + } + + /** + * @return Exception + */ + public function getException() + { + return $this->_exception; + } +} \ No newline at end of file diff --git a/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvoker.php b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvoker.php new file mode 100644 index 0000000..f08747d --- /dev/null +++ b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvoker.php @@ -0,0 +1,50 @@ +_logger = LoggerFactory::getInstance() + ->createLogger(UserspaceUnhandledErrorInvoker::class); + } + + function register(callable $handler) + { + $this->_userUnhandledErrorHandler = $handler; + } + + function invoke($source, $exceptionTrigger, $exception) + { + if (!($handler = $this->_userUnhandledErrorHandler)) { + $this->_logger->error("User Unhandled Error occurred, no fallback handler was set, exception ignored.", [ + 'exception' => $exception + ]); + return; + } + + try { + $handler(new UserspaceUnhandledErrorArgs($source, $exceptionTrigger, $exception)); + } catch (Exception $ex) { + $this->_logger->error("User Unhandled Error Handler itself threw an exception. original exception: {$ex->getMessage()}", [ + 'ex' => $ex, + 'exception' => $exception + ]); + } + } +} diff --git a/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerInterface.php b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerInterface.php new file mode 100644 index 0000000..ae07d86 --- /dev/null +++ b/src/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerInterface.php @@ -0,0 +1,21 @@ +_userUnhandledErrorInvoker = $userUnhandledErrorInvoker; + } + /** * @inheritDoc */ @@ -27,8 +43,8 @@ function register(callable $handler) * @inheritDoc */ function invoke( - ReportingValue $value, - ExperimentModel $experiment = null, + ReportingValue $value, + ExperimentModel $experiment = null, ContextInterface $context = null) { $this->_fireImpression($value, $experiment, $context); @@ -43,7 +59,12 @@ private function _fireImpression(ReportingValue $value, $experiment, $context) { $args = new ImpressionArgs($value, $context); foreach ($this->_handlers as $handler) { - $handler($args); + try { + $handler($args); + } catch (Exception $e) { + $this->_userUnhandledErrorInvoker + ->invoke($this, ExceptionTrigger::ImpressionHandler, $e); + } } } } diff --git a/src/Rox/Core/Roxx/Parser.php b/src/Rox/Core/Roxx/Parser.php index 128464d..d95248e 100644 --- a/src/Rox/Core/Roxx/Parser.php +++ b/src/Rox/Core/Roxx/Parser.php @@ -6,6 +6,8 @@ use Psr\Log\LoggerInterface; use Rox\Core\Context\ContextBuilder; use Rox\Core\Context\ContextInterface; +use Rox\Core\ErrorHandling\UserspaceHandlerException; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Logging\LoggerFactory; use Rox\Core\Utils\NumericUtils; use Rox\Core\Utils\TimeUtils; @@ -22,6 +24,11 @@ class Parser implements ParserInterface */ private $_operatorsMap = []; + /** + * @var UserspaceUnhandledErrorInvokerInterface $_userUnhandledErrorInvoker + */ + private $_userUnhandledErrorInvoker; + /** * @var ContextInterface $_globalContext */ @@ -29,10 +36,11 @@ class Parser implements ParserInterface /** * Parser constructor. - * @throws Exception + * @param UserspaceUnhandledErrorInvokerInterface|null $userUnhandledErrorInvoker */ - public function __construct() + public function __construct($userUnhandledErrorInvoker) { + $this->_userUnhandledErrorInvoker = $userUnhandledErrorInvoker; $this->_log = LoggerFactory::getInstance()->createLogger(self::class); $this->_setBasicOperators(); } @@ -262,6 +270,19 @@ public function evaluateExpression($expression, $context = null, $evaluationCont $result = $stack->pop(); + } catch (UserspaceHandlerException $ex) { + + $this->_log->warning("Roxx Exception: Failed evaluate expression, user unhandled expression {$ex->getMessage()}", [ + 'exception' => $ex + ]); + + if ($this->_userUnhandledErrorInvoker) { + $this->_userUnhandledErrorInvoker->invoke( + $ex->getExceptionSource(), + $ex->getExceptionTrigger(), + $ex->getException()); + } + } catch (Exception $exception) { $this->_log->warning("Roxx Exception: Failed evaluate expression ${expression}", [ diff --git a/src/Rox/Core/Roxx/PropertiesExtensions.php b/src/Rox/Core/Roxx/PropertiesExtensions.php index acff559..4a1c381 100644 --- a/src/Rox/Core/Roxx/PropertiesExtensions.php +++ b/src/Rox/Core/Roxx/PropertiesExtensions.php @@ -2,8 +2,11 @@ namespace Rox\Core\Roxx; +use Exception; use Rox\Core\Context\ContextInterface; use Rox\Core\CustomProperties\DynamicPropertiesInterface; +use Rox\Core\ErrorHandling\ExceptionTrigger; +use Rox\Core\ErrorHandling\UserspaceHandlerException; use Rox\Core\Repositories\CustomPropertyRepositoryInterface; class PropertiesExtensions @@ -30,9 +33,9 @@ class PropertiesExtensions * @param DynamicPropertiesInterface $dynamicProperties */ public function __construct( - ParserInterface $parser, + ParserInterface $parser, CustomPropertyRepositoryInterface $propertiesRepository, - DynamicPropertiesInterface $dynamicProperties) + DynamicPropertiesInterface $dynamicProperties) { $this->_parser = $parser; $this->_propertiesRepository = $propertiesRepository; @@ -49,7 +52,17 @@ function (ParserInterface $parser, StackInterface $stack, ContextInterface $cont if ($property == null) { $dynamicPropertiesRule = $this->_dynamicProperties->getDynamicPropertiesRule(); if ($dynamicPropertiesRule != null) { - $value = $dynamicPropertiesRule($propName, $context); + if ($this->_dynamicProperties->isDefault()) { + // this is our implementation, if there's an exception, shouldn't throw to user error handler + $value = $dynamicPropertiesRule($propName, $context); + } else { + try { + $value = $dynamicPropertiesRule($propName, $context); + } catch (Exception $ex) { + // throwing exception, so the whole evaluation will exit, and default will be applied + throw new UserspaceHandlerException($dynamicPropertiesRule, ExceptionTrigger::DynamicPropertiesRule, $ex); + } + } if ($value != null) { if (is_string($value) || is_bool($value)) { $stack->push($value); @@ -65,7 +78,12 @@ function (ParserInterface $parser, StackInterface $stack, ContextInterface $cont } $propValue = $property->getValue(); - $value = $propValue($context); + try { + $value = $propValue($context); + } catch (Exception $ex) { + throw new UserspaceHandlerException($propValue, + ExceptionTrigger::CustomPropertyGenerator, $ex); + } if ($value !== null) { $stack->push($value); return; diff --git a/src/Rox/Core/XPack/Configuration/XConfigurationFetchedInvoker.php b/src/Rox/Core/XPack/Configuration/XConfigurationFetchedInvoker.php deleted file mode 100644 index 8221287..0000000 --- a/src/Rox/Core/XPack/Configuration/XConfigurationFetchedInvoker.php +++ /dev/null @@ -1,38 +0,0 @@ -_log = LoggerFactory::getInstance()->createLogger(self::class); - $this->_core = $_core; - } - - protected function internalInvoke(ConfigurationFetchedArgs $cfa) - { - // rox.internal.pushUpdates code omitted - $this->fireConfigurationFetched($cfa); - } -} diff --git a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php index 4fb3b37..c091652 100644 --- a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php +++ b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php @@ -9,6 +9,8 @@ use Rox\Core\Consts\PropertyType; use Rox\Core\Context\ContextInterface; use Rox\Core\CustomProperties\CustomPropertyType; +use Rox\Core\ErrorHandling\ExceptionTrigger; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Impression\Models\ReportingValue; use Rox\Core\Logging\LoggerFactory; use Rox\Core\Repositories\CustomPropertyRepositoryInterface; @@ -42,6 +44,11 @@ class XImpressionInvoker implements ImpressionInvokerInterface */ private $_eventHandlers = []; + /** + * @var UserspaceUnhandledErrorInvokerInterface $_userUnhandledErrorInvoker + */ + protected $_userUnhandledErrorInvoker; + /** * XImpressionInvoker constructor. * @param InternalFlagsInterface $internalFlags @@ -49,12 +56,14 @@ class XImpressionInvoker implements ImpressionInvokerInterface * @param ClientInterface|null $analyticsClient */ public function __construct( - InternalFlagsInterface $internalFlags, - CustomPropertyRepositoryInterface $customPropertyRepository = null, - ClientInterface $analyticsClient = null) + InternalFlagsInterface $internalFlags, + UserspaceUnhandledErrorInvokerInterface $userUnhandledErrorInvoker = null, + CustomPropertyRepositoryInterface $customPropertyRepository = null, + ClientInterface $analyticsClient = null) { $this->_log = LoggerFactory::getInstance()->createLogger(self::class); $this->_customPropertyRepository = $customPropertyRepository; + $this->_userUnhandledErrorInvoker = $userUnhandledErrorInvoker; $this->_internalFlags = $internalFlags; $this->_analyticsClient = $analyticsClient; } @@ -115,7 +124,14 @@ function invoke( private function _fireImpression(ImpressionArgs $args) { foreach ($this->_eventHandlers as $handler) { - $handler($args); + try { + $handler($args); + } catch (Exception $e) { + if ($this->_userUnhandledErrorInvoker) { + $this->_userUnhandledErrorInvoker + ->invoke($this, ExceptionTrigger::ImpressionHandler, $e); + } + } } } } diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index bae441c..1151b26 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -209,6 +209,14 @@ public static function setCustomComputedSemverProperty($name, callable $value) self::getCore()->addCustomProperty(new CustomProperty($name, CustomPropertyType::getSemver(), $value)); } + /** + * @param callable $userspaceUnhandledErrorHandler + */ + public static function setUserspaceUnhandledErrorHandler(callable $userspaceUnhandledErrorHandler) + { + self::getCore()->setUserspaceUnhandledErrorHandler($userspaceUnhandledErrorHandler); + } + /** * @return DynamicApiInterface */ diff --git a/tests/Rox/Core/XPack/Configuration/XConfigurationFetchedInvokerTests.php b/tests/Rox/Core/Configuration/ConfigurationFetchedInvokerTests.php similarity index 64% rename from tests/Rox/Core/XPack/Configuration/XConfigurationFetchedInvokerTests.php rename to tests/Rox/Core/Configuration/ConfigurationFetchedInvokerTests.php index e04f73c..76d9f0b 100644 --- a/tests/Rox/Core/XPack/Configuration/XConfigurationFetchedInvokerTests.php +++ b/tests/Rox/Core/Configuration/ConfigurationFetchedInvokerTests.php @@ -1,23 +1,24 @@ invokeWithError(FetcherError::Unknown); - $configurationFetchedInvoker2 = new XConfigurationFetchedInvoker(\Mockery::mock(Core::class)); + $configurationFetchedInvoker2 = new ConfigurationFetchedInvoker( + Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $configurationFetchedInvoker2->invoke(FetcherStatus::AppliedFromEmbedded, TimeUtils::currentTimeMillis(), true); @@ -55,7 +56,8 @@ public function testNoPushConfigurationInvokerInvokeWithError() $this->expectNoErrors(); $isConfigurationHandlerInvokerRaised = [false]; - $configurationFetchedInvoker = new ConfigurationFetchedInvoker(); + $configurationFetchedInvoker = new ConfigurationFetchedInvoker( + Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $configurationFetchedInvoker->register(function (ConfigurationFetchedArgs $e) use ($configurationFetchedInvoker, &$isConfigurationHandlerInvokerRaised) { @@ -79,7 +81,8 @@ public function testConfigurationInvokerInvokeWithError() $this->expectNoErrors(); $isConfigurationHandlerInvokerRaised = [false]; - $configurationFetchedInvoker = new XConfigurationFetchedInvoker(\Mockery::mock(Core::class)); + $configurationFetchedInvoker = new ConfigurationFetchedInvoker( + Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $configurationFetchedInvoker->register(function (ConfigurationFetchedArgs $e) use ($configurationFetchedInvoker, &$isConfigurationHandlerInvokerRaised) { @@ -100,7 +103,8 @@ public function testConfigurationInvokerInvokeWithError() public function testConfigurationInvokerInvokeOK() { $isConfigurationHandlerInvokerRaised = [false]; - $configurationFetchedInvoker = new XConfigurationFetchedInvoker(\Mockery::mock(Core::class)); + $configurationFetchedInvoker = new ConfigurationFetchedInvoker( + Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $now = TimeUtils::currentTimeMillis(); $status = FetcherStatus::AppliedFromNetwork; @@ -121,4 +125,36 @@ public function testConfigurationInvokerInvokeOK() $this->assertTrue($isConfigurationHandlerInvokerRaised[0]); } + + public function testConfigurationInvokerRaisedUserUnhandledError() + { + $invokedCalled = false; + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturnUsing(function ($sender, $trigger, $ex) use (&$invokedCalled) { + $this->assertSame(ExceptionTrigger::ConfigurationFetchedHandler, $trigger); + $invokedCalled = true; + }) + ->byDefault() + ->getMock(); + + $ex = new Exception("user error"); + + $now = time(); + $status = FetcherStatus::AppliedFromNetwork; + $hasChanges = true; + + $configurationFetchedHandled = function (ConfigurationFetchedArgs $e) use ($status, $now, $ex) { + $this->assertSame($status, $e->getFetcherStatus()); + $this->assertSame($now, $e->getCreationDate()); + $this->assertTrue($e->isHasChanges()); + $this->assertSame(FetcherError::NoError, $e->getErrorDetails()); + throw $ex; + }; + + $configurationFetchedInvoker = new ConfigurationFetchedInvoker($userUnhandledErrorInvoker); + $configurationFetchedInvoker->register($configurationFetchedHandled); + $configurationFetchedInvoker->invoke($status, $now, $hasChanges); + $this->assertTrue($invokedCalled); + } } diff --git a/tests/Rox/Core/Configuration/ConfigurationParserTests.php b/tests/Rox/Core/Configuration/ConfigurationParserTests.php index 2312473..d754f20 100644 --- a/tests/Rox/Core/Configuration/ConfigurationParserTests.php +++ b/tests/Rox/Core/Configuration/ConfigurationParserTests.php @@ -2,14 +2,14 @@ namespace Rox\Core\Configuration; +use Mockery; use Rox\Core\Client\SdkSettingsInterface; -use Rox\Core\Core; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Network\ConfigurationFetchResult; use Rox\Core\Network\ConfigurationSource; use Rox\Core\Reporting\ErrorReporterInterface; use Rox\Core\Security\APIKeyVerifierInterface; use Rox\Core\Security\SignatureVerifierInterface; -use Rox\Core\XPack\Configuration\XConfigurationFetchedInvoker; use Rox\RoxTestCase; class ConfigurationParserTests extends RoxTestCase @@ -48,30 +48,31 @@ protected function setUp() { parent::setUp(); - $this->_sdk = \Mockery::mock(SdkSettingsInterface::class) + $this->_sdk = Mockery::mock(SdkSettingsInterface::class) ->shouldReceive('getApiKey') ->andReturn("12345") ->byDefault() ->getMock(); - $this->_sf = \Mockery::mock(SignatureVerifierInterface::class) + $this->_sf = Mockery::mock(SignatureVerifierInterface::class) ->shouldReceive('verify') ->andReturn(true) ->byDefault() ->getMock(); - $this->_kf = \Mockery::mock(APIKeyVerifierInterface::class) + $this->_kf = Mockery::mock(APIKeyVerifierInterface::class) ->shouldReceive('verify') ->andReturn(true) ->byDefault() ->getMock(); - $this->_errRe = \Mockery::mock(ErrorReporterInterface::class) + $this->_errRe = Mockery::mock(ErrorReporterInterface::class) ->shouldReceive('report') ->byDefault() ->getMock(); - $this->_cfi = new XConfigurationFetchedInvoker(\Mockery::mock(Core::class)); + $this->_cfi = new ConfigurationFetchedInvoker( + Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->_cfi->register(function (ConfigurationFetchedArgs $e) { $this->_cfiEvent = $e; diff --git a/tests/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerTests.php b/tests/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerTests.php new file mode 100644 index 0000000..d0606ff --- /dev/null +++ b/tests/Rox/Core/ErrorHandling/UserspaceUnhandledErrorInvokerTests.php @@ -0,0 +1,103 @@ +shouldReceive('error') + ->andReturnUsing(function ($message, $context) use (&$msg, &$exThrown) { + echo 'MSG=' . $message; + $msg = $message; + $exThrown = $context['exception']; + }) + ->getMock(); + + $loggerFactory = Mockery::mock(LoggerFactoryInterface::class) + ->shouldReceive('createLogger') + ->andReturn($log) + ->byDefault() + ->getMock(); + + LoggerFactory::setup($loggerFactory); + + $userUnhandledErrorInvoker = new UserspaceUnhandledErrorInvoker(); + $userUnhandledErrorInvoker->invoke($obj, ExceptionTrigger::ConfigurationFetchedHandler, $exception); + + $this->assertStringStartsWith("User Unhandled Error occurred", $msg); + $this->assertSame($exception, $exThrown); + } + + public function testWillWriteErrorWhenInvokeUserUnhandledErrorInvokerThrewException() + { + $exception = new Exception("some exception"); + $exHandlerException = new Exception("userUnhandledError exception"); + $obj = "123"; + + $msg = null; + $exThrown = null; + + $log = Mockery::mock(LoggerInterface::class) + ->shouldReceive('error') + ->andReturnUsing(function ($message, $context) use (&$msg, &$exThrown) { + echo 'MSG=' . $message; + $msg = $message; + $exThrown = $context['exception']; + }) + ->getMock(); + + $loggerFactory = Mockery::mock(LoggerFactoryInterface::class) + ->shouldReceive('createLogger') + ->andReturn($log) + ->byDefault() + ->getMock(); + + LoggerFactory::setup($loggerFactory); + + $handler = function (UserspaceUnhandledErrorArgs $uueArgs) use ($exHandlerException) { + throw $exHandlerException; + }; + + $userUnhandledErrorInvoker = new UserspaceUnhandledErrorInvoker(); + $userUnhandledErrorInvoker->register($handler); + $userUnhandledErrorInvoker->invoke($obj, ExceptionTrigger::ConfigurationFetchedHandler, $exception); + + $this->assertStringStartsWith("User Unhandled Error Handler itself", $msg); + $this->assertSame($exception, $exThrown); + } + + public function testWillInvokeUserUnhandledErrorInvoker() + { + $userUnhandledErrorInvoker = new UserspaceUnhandledErrorInvoker(); + $ex = new Exception("some exception"); + $exTrigger = ExceptionTrigger::DynamicPropertiesRule; + $obj = "123"; + $invoked = 0; + + $handler = function (UserspaceUnhandledErrorArgs $uueArgs) use ($obj, &$invoked, $exTrigger, $ex) { + $invoked++; + $this->assertSame($obj, $uueArgs->getExceptionSource()); + $this->assertSame($exTrigger, $uueArgs->getExceptionTrigger()); + $this->assertSame($ex, $uueArgs->getException()); + }; + + $userUnhandledErrorInvoker->register($handler); + $userUnhandledErrorInvoker->invoke($obj, $exTrigger, $ex); + + $this->assertEquals(1, $invoked); + } +} \ No newline at end of file diff --git a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php index fc0cd30..c71b625 100644 --- a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php +++ b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php @@ -2,6 +2,8 @@ namespace Rox\Core\Impressions; +use Exception; +use Mockery; use Rox\Core\Client\DevicePropertiesInterface; use Rox\Core\Client\InternalFlagsInterface; use Rox\Core\Configuration\Models\ExperimentModel; @@ -9,6 +11,8 @@ use Rox\Core\Context\ContextBuilder; use Rox\Core\CustomProperties\CustomProperty; use Rox\Core\CustomProperties\CustomPropertyType; +use Rox\Core\ErrorHandling\ExceptionTrigger; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Impression\ImpressionArgs; use Rox\Core\Impression\Models\ReportingValue; use Rox\Core\Impression\XImpressionInvoker; @@ -20,9 +24,31 @@ class ImpressionInvokerTests extends RoxTestCase { + public function testWillCallUserUnhandledErrorInvoker() + { + $ex = new Exception("user exception"); + $internalFlags = Mockery::mock(InternalFlagsInterface::class); + $unhandledErrorInvokeCalled = false; + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturnUsing(function ($sender, $trigger, $ex) use (&$unhandledErrorInvokeCalled) { + $this->assertSame(ExceptionTrigger::ImpressionHandler, $trigger); + $unhandledErrorInvokeCalled = true; + }) + ->byDefault() + ->getMock(); + + $impressionInvoker = new XImpressionInvoker($internalFlags, $userUnhandledErrorInvoker, null, null); + $impressionInvoker->register(function ($sender, $e) use ($ex) { + throw $ex; + }); + $impressionInvoker->invoke(new ReportingValue("test", "value"), null, null); + $this->assertTrue($unhandledErrorInvokeCalled); + } + public function testWillSetImpressionInvokerEmptyInvokeNotThrowingException() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->andThrow(\Exception::class) ->getMock(); @@ -35,7 +61,7 @@ public function testWillSetImpressionInvokerEmptyInvokeNotThrowingException() public function testWillTestImpressionInvokerInvokeAndParameters() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $internalFlags = Mockery::mock(InternalFlagsInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -83,7 +109,7 @@ public function testImpressionArgsConstructor() public function testWillInvokeAnalyticsWhenNoExperiment() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $internalFlags = Mockery::mock(InternalFlagsInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -104,7 +130,7 @@ public function testWillInvokeAnalyticsWhenNoExperiment() public function testWillInvokeAnalyticsOnExperiment() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $internalFlags = Mockery::mock(InternalFlagsInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -126,7 +152,7 @@ public function testWillInvokeAnalyticsOnExperiment() public function testWillNotInvokeAnalyticsWhenFlagIsOff() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class); + $internalFlags = Mockery::mock(InternalFlagsInterface::class); $impressionInvoker = new XImpressionInvoker($internalFlags, null, null); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -148,13 +174,13 @@ public function testWillNotInvokeAnalyticsWhenFlagIsOff() public function testWillNotInvokeAnalyticsWhenIsRoxy() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->with('rox.internal.analytics') ->andReturn(true) ->getMock(); - $customProps = \Mockery::mock(CustomPropertyRepositoryInterface::class) + $customProps = Mockery::mock(CustomPropertyRepositoryInterface::class) ->shouldReceive('getCustomProperty') ->andReturnUsing(function ($arg) { if ($arg == 'rox.' . PropertyType::getDistinctId()->getName()) { @@ -164,18 +190,18 @@ public function testWillNotInvokeAnalyticsWhenIsRoxy() }) ->getMock(); - $deviceProps = \Mockery::mock(DevicePropertiesInterface::class) + $deviceProps = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getDistinctId') ->andReturn('stamId') ->getMock(); - $analytics = \Mockery::mock(ClientInterface::class) + $analytics = Mockery::mock(ClientInterface::class) ->shouldNotReceive('track') ->getMock(); // FIXME: bad test, it doesn't pass analytics client into constructor and doesn't expect it to be called. // FIXME: (ported from .NET code as is). - $impressionInvoker = new XImpressionInvoker($internalFlags, $customProps, null); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, $customProps, null); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -194,13 +220,13 @@ public function testWillNotInvokeAnalyticsWhenIsRoxy() public function testWillInvokeAnalytics() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->with('rox.internal.analytics') ->andReturn(true) ->getMock(); - $customProps = \Mockery::mock(CustomPropertyRepositoryInterface::class) + $customProps = Mockery::mock(CustomPropertyRepositoryInterface::class) ->shouldReceive('getCustomProperty') ->andReturnUsing(function ($arg) { if ($arg == 'rox.' . PropertyType::getDistinctId()->getName()) { @@ -210,22 +236,22 @@ public function testWillInvokeAnalytics() }) ->getMock(); - $deviceProps = \Mockery::mock(DevicePropertiesInterface::class) + $deviceProps = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getDistinctId') ->andReturn('stamId') ->getMock(); $outEvent = [null]; - $analytics = \Mockery::mock(ClientInterface::class); + $analytics = Mockery::mock(ClientInterface::class); $analytics->shouldReceive('track') - ->with(\Mockery::on(function (Event $args) use (&$outEvent) { + ->with(Mockery::on(function (Event $args) use (&$outEvent) { $outEvent[0] = $args; return true; })) ->once() ->getMock(); - $impressionInvoker = new XImpressionInvoker($internalFlags, $customProps, $analytics); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, $customProps, $analytics); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -250,13 +276,13 @@ public function testWillInvokeAnalytics() public function testWillInvokeAnalyticsWithStickinessProp() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->with('rox.internal.analytics') ->andReturn(true) ->getMock(); - $customProps = \Mockery::mock(CustomPropertyRepositoryInterface::class) + $customProps = Mockery::mock(CustomPropertyRepositoryInterface::class) ->shouldReceive('getCustomProperty') ->andReturnUsing(function ($arg) { if ($arg == 'rox.' . PropertyType::getDistinctId()->getName()) { @@ -269,22 +295,22 @@ public function testWillInvokeAnalyticsWithStickinessProp() }) ->getMock(); - $deviceProps = \Mockery::mock(DevicePropertiesInterface::class) + $deviceProps = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getDistinctId') ->andReturn('stamId') ->getMock(); $outEvent = [null]; - $analytics = \Mockery::mock(ClientInterface::class); + $analytics = Mockery::mock(ClientInterface::class); $analytics->shouldReceive('track') - ->with(\Mockery::on(function (Event $args) use (&$outEvent) { + ->with(Mockery::on(function (Event $args) use (&$outEvent) { $outEvent[0] = $args; return true; })) ->once() ->getMock(); - $impressionInvoker = new XImpressionInvoker($internalFlags, $customProps, $analytics); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, $customProps, $analytics); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -310,13 +336,13 @@ public function testWillInvokeAnalyticsWithStickinessProp() public function testWillInvokeAnalyticsWithDefaultPropWhenNoStickinessProp() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->with('rox.internal.analytics') ->andReturn(true) ->getMock(); - $customProps = \Mockery::mock(CustomPropertyRepositoryInterface::class) + $customProps = Mockery::mock(CustomPropertyRepositoryInterface::class) ->shouldReceive('getCustomProperty') ->andReturnUsing(function ($arg) { if ($arg == 'rox.' . PropertyType::getDistinctId()->getName()) { @@ -329,22 +355,22 @@ public function testWillInvokeAnalyticsWithDefaultPropWhenNoStickinessProp() }) ->getMock(); - $deviceProps = \Mockery::mock(DevicePropertiesInterface::class) + $deviceProps = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getDistinctId') ->andReturn('stamId') ->getMock(); $outEvent = [null]; - $analytics = \Mockery::mock(ClientInterface::class); + $analytics = Mockery::mock(ClientInterface::class); $analytics->shouldReceive('track') - ->with(\Mockery::on(function (Event $args) use (&$outEvent) { + ->with(Mockery::on(function (Event $args) use (&$outEvent) { $outEvent[0] = $args; return true; })) ->once() ->getMock(); - $impressionInvoker = new XImpressionInvoker($internalFlags, $customProps, $analytics); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, $customProps, $analytics); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -370,33 +396,33 @@ public function testWillInvokeAnalyticsWithDefaultPropWhenNoStickinessProp() public function testWillInvokeAnalyticsWithBadDistinctId() { - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->with('rox.internal.analytics') ->andReturn(true) ->getMock(); - $customProps = \Mockery::mock(CustomPropertyRepositoryInterface::class) + $customProps = Mockery::mock(CustomPropertyRepositoryInterface::class) ->shouldReceive('getCustomProperty') ->andReturn(null) ->getMock(); - $deviceProps = \Mockery::mock(DevicePropertiesInterface::class) + $deviceProps = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getDistinctId') ->andReturn('stamId') ->getMock(); $outEvent = [null]; - $analytics = \Mockery::mock(ClientInterface::class); + $analytics = Mockery::mock(ClientInterface::class); $analytics->shouldReceive('track') - ->with(\Mockery::on(function (Event $args) use (&$outEvent) { + ->with(Mockery::on(function (Event $args) use (&$outEvent) { $outEvent[0] = $args; return true; })) ->once() ->getMock(); - $impressionInvoker = new XImpressionInvoker($internalFlags, $customProps, $analytics); + $impressionInvoker = new XImpressionInvoker($internalFlags, null, $customProps, $analytics); $context = (new ContextBuilder())->build(['obj1' => 1]); @@ -423,7 +449,7 @@ public function testWillInvokeAnalyticsWithBadDistinctId() public function ignoreNoAssertationTest() { $this->addToAssertionCount( - \Mockery::getContainer()->mockery_getExpectationCount() + Mockery::getContainer()->mockery_getExpectationCount() ); } } diff --git a/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php b/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php index 6a41628..a399f7a 100644 --- a/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php +++ b/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php @@ -3,10 +3,12 @@ namespace Rox\Core\Network; use Exception; +use Mockery; use Rox\Core\Client\BUIDInterface; use Rox\Core\Client\DevicePropertiesInterface; use Rox\Core\Configuration\ConfigurationFetchedArgs; use Rox\Core\Configuration\ConfigurationFetchedInvoker; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Reporting\ErrorReporterInterface; use Rox\RoxTestCase; @@ -19,7 +21,7 @@ protected function setUp() { parent::setUp(); - $this->_dp = \Mockery::mock(DevicePropertiesInterface::class) + $this->_dp = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getAllProperties') ->andReturn([ "app_key" => "123", @@ -30,20 +32,20 @@ protected function setUp() ->byDefault() ->getMock(); - $this->_bu = \Mockery::mock(BUIDInterface::class); + $this->_bu = Mockery::mock(BUIDInterface::class); } public function testWillReturnDataWhenSuccessful() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); - $errorReporter = \Mockery::mock(ErrorReporterInterface::class); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); + $errorReporter = Mockery::mock(ErrorReporterInterface::class); $numberOfTimersCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimersCalled) { $numberOfTimersCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(200, "{\"a\": \"harti\"}")) ->getMock(); @@ -58,15 +60,15 @@ public function testWillReturnDataWhenSuccessful() public function testWillReturnNullWhenRoxyFailsWithException() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); - $errorReporter = \Mockery::mock(ErrorReporterInterface::class); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); + $errorReporter = Mockery::mock(ErrorReporterInterface::class); $numberOfTimersCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimersCalled) { $numberOfTimersCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andThrow(Exception::class) ->getMock(); @@ -80,10 +82,10 @@ public function testWillReturnNullWhenRoxyFailsWithException() public function testWillReturnNullWhenRoxyFailsWithHttpStatus() { - $request = \Mockery::mock(HttpClientInterface::class); - $errorReporter = \Mockery::mock(ErrorReporterInterface::class); + $request = Mockery::mock(HttpClientInterface::class); + $errorReporter = Mockery::mock(ErrorReporterInterface::class); - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimersCalled = 0; $numberOfTimersCalled = [0]; @@ -91,7 +93,7 @@ public function testWillReturnNullWhenRoxyFailsWithHttpStatus() $numberOfTimersCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(HttpResponseInterface::STATUS_NOT_FOUND)) ->getMock(); diff --git a/tests/Rox/Core/Network/ConfigurationFetcherTests.php b/tests/Rox/Core/Network/ConfigurationFetcherTests.php index 09289a5..3841dd1 100644 --- a/tests/Rox/Core/Network/ConfigurationFetcherTests.php +++ b/tests/Rox/Core/Network/ConfigurationFetcherTests.php @@ -3,11 +3,13 @@ namespace Rox\Core\Network; use Exception; +use Mockery; use Rox\Core\Client\BUIDInterface; use Rox\Core\Client\DevicePropertiesInterface; use Rox\Core\Configuration\ConfigurationFetchedArgs; use Rox\Core\Configuration\ConfigurationFetchedInvoker; use Rox\Core\Consts\PropertyType; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Reporting\ErrorReporterInterface; use Rox\RoxTestCase; @@ -32,7 +34,7 @@ protected function setUp() { parent::setUp(); - $this->_dp = \Mockery::mock(DevicePropertiesInterface::class) + $this->_dp = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getAllProperties') ->andReturn([ "app_key" => "123", @@ -42,13 +44,13 @@ protected function setUp() ->byDefault() ->getMock(); - $this->_bu = \Mockery::mock(BUIDInterface::class) + $this->_bu = Mockery::mock(BUIDInterface::class) ->shouldReceive('getQueryStringParts') ->andReturn(["buid" => "buid"]) ->byDefault() ->getMock(); - $this->_errorReporter = \Mockery::mock(ErrorReporterInterface::class) + $this->_errorReporter = Mockery::mock(ErrorReporterInterface::class) ->shouldReceive('report') ->byDefault() ->getMock(); @@ -56,7 +58,7 @@ protected function setUp() public function testWillReturnCDNDataWhenSuccessful() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { @@ -66,7 +68,7 @@ public function testWillReturnCDNDataWhenSuccessful() $response = new TestHttpResponse(200, "{\"a\": \"harti\"}"); $reqData = [null]; - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturnUsing(function ($req) use ($response, &$reqData) { $reqData[0] = $req; @@ -89,14 +91,14 @@ public function testWillReturnCDNDataWhenSuccessful() public function testWillReturnNullWhenCDNFailsWithException() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andThrow(new Exception('not found')) ->getMock() @@ -113,14 +115,14 @@ public function testWillReturnNullWhenCDNFailsWithException() public function testWillReturnNullWhenCDNSucceedWithEmptyResponse() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(200, "")) ->once() @@ -142,13 +144,13 @@ public function testWillReturnNullWhenCDNSucceedWithEmptyResponse() public function testWillReturnNullWhenCDNSucceedWithNotJsonResponse() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->once() ->andReturn(new TestHttpResponse(200, "{fdsadf/:")) @@ -170,14 +172,14 @@ public function testWillReturnNullWhenCDNSucceedWithNotJsonResponse() public function testWillReturnNullWhenCDNFails404APIWithException() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(HttpResponseInterface::STATUS_NOT_FOUND)) ->once() @@ -196,7 +198,7 @@ public function testWillReturnNullWhenCDNFails404APIWithException() public function testWillReturnAPIDataWhenCDNFailsWithResult404APIOK() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { @@ -204,7 +206,7 @@ public function testWillReturnAPIDataWhenCDNFailsWithResult404APIOK() }); $reqData = [null]; - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(HttpResponseInterface::STATUS_OK, "{\"result\": \"404\"}")) ->once() @@ -244,14 +246,14 @@ public function testWillReturnAPIDataWhenCDNFailsWithResult404APIOK() public function testWillReturnAPIDataWhenCDNSucceedWithResult200() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(200, "{\"result\": \"200\"}")) ->once() @@ -271,14 +273,14 @@ public function testWillReturnAPIDataWhenCDNSucceedWithResult200() public function testWillReturnAPIDataWhenCDNFails404APIOK() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(HttpResponseInterface::STATUS_NOT_FOUND)) ->once() @@ -298,14 +300,14 @@ public function testWillReturnAPIDataWhenCDNFails404APIOK() public function testWillReturnNullDataWhenBothNotFound() { - $confFetchInvoker = new ConfigurationFetchedInvoker(); + $confFetchInvoker = new ConfigurationFetchedInvoker(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $numberOfTimerCalled = [0]; $confFetchInvoker->register(function (ConfigurationFetchedArgs $e) use (&$numberOfTimerCalled) { $numberOfTimerCalled[0]++; }); - $request = \Mockery::mock(HttpClientInterface::class) + $request = Mockery::mock(HttpClientInterface::class) ->shouldReceive('sendGet') ->andReturn(new TestHttpResponse(HttpResponseInterface::STATUS_NOT_FOUND)) ->once() diff --git a/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php b/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php index 5b4cec0..b8c4804 100644 --- a/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php +++ b/tests/Rox/Core/Roxx/ExperimentsExtensionsTests.php @@ -2,6 +2,7 @@ namespace Rox\Core\Roxx; +use Mockery; use Rox\Core\Client\InternalFlagsInterface; use Rox\Core\Configuration\Models\ExperimentModel; use Rox\Core\Context\ContextBuilder; @@ -11,6 +12,7 @@ use Rox\Core\CustomProperties\CustomPropertyType; use Rox\Core\CustomProperties\DynamicProperties; use Rox\Core\Entities\FlagSetter; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Impression\ImpressionArgs; use Rox\Core\Impression\XImpressionInvoker; use Rox\Core\Repositories\ExperimentRepository; @@ -26,7 +28,7 @@ class ExperimentsExtensionsTests extends RoxTestCase { public function testCustomPropertyWithSimpleValue() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentsExtensions = new ExperimentsExtensions($parser, $targetGroupsRepository, \Mockery::mock(FlagRepositoryInterface::class), @@ -38,7 +40,7 @@ public function testCustomPropertyWithSimpleValue() public function testIsInPercentageRange() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentsExtensions = new ExperimentsExtensions($parser, $targetGroupsRepository, @@ -51,7 +53,7 @@ public function testIsInPercentageRange() public function testNotIsInPercentageRange() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentsExtensions = new ExperimentsExtensions($parser, $targetGroupsRepository, @@ -65,7 +67,7 @@ public function testNotIsInPercentageRange() public function testGetBucket() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentsExtensions = new ExperimentsExtensions($parser, $targetGroupsRepository, @@ -79,7 +81,7 @@ public function testGetBucket() public function testFlagValueNoFlagNoExperiment() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -93,7 +95,7 @@ public function testFlagValueNoFlagNoExperiment() public function testFlagValueNoFlagEvaluateExperiment() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -112,7 +114,7 @@ public function testFlagValueNoFlagEvaluateExperiment() public function testFlagValueFlagEvaluationDefault() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -128,7 +130,7 @@ public function testFlagValueFlagEvaluationDefault() public function testFlagDependencyValue() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -151,7 +153,7 @@ public function testFlagDependencyValue() public function testFlagDependencyImpressionHandler() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -189,7 +191,7 @@ public function testFlagDependencyImpressionHandler() public function testFlagDependency2LevelsBottomNotExists() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $targetGroupsRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); $flagRepository = new FlagRepository(); @@ -213,7 +215,7 @@ public function testFlagDependency2LevelsBottomNotExists() public function testFlagDependencyUnexistingFlagButExistingExperiment() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $flagRepository = new FlagRepository(); $targetGroupRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); @@ -242,7 +244,7 @@ public function testFlagDependencyUnexistingFlagButExistingExperiment() public function testFlagDependencyUnexistingFlagAndExperimentUndefined() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $flagRepository = new FlagRepository(); $targetGroupRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); @@ -271,7 +273,7 @@ public function testFlagDependencyUnexistingFlagAndExperimentUndefined() public function testFlagDependencyWithContext() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $flagRepository = new FlagRepository(); $targetGroupRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); @@ -301,7 +303,7 @@ public function testFlagDependencyWithContext() public function testFlagDependencyWithContextUsedOnExperimentWithNoFlag() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $flagRepository = new FlagRepository(); $targetGroupRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); @@ -330,7 +332,7 @@ public function testFlagDependencyWithContextUsedOnExperimentWithNoFlag() public function testFlagDependencyWithContext2LevelMidLevelNoFlagEvalExperiment() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $flagRepository = new FlagRepository(); $targetGroupRepository = new TargetGroupRepository(); $experimentRepository = new ExperimentRepository(); diff --git a/tests/Rox/Core/Roxx/ParserTests.php b/tests/Rox/Core/Roxx/ParserTests.php index 781630a..300413d 100644 --- a/tests/Rox/Core/Roxx/ParserTests.php +++ b/tests/Rox/Core/Roxx/ParserTests.php @@ -2,8 +2,10 @@ namespace Rox\Core\Roxx; +use Mockery; use Rox\Core\CustomProperties\CustomPropertyRepository; use Rox\Core\CustomProperties\DynamicProperties; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Repositories\ExperimentRepository; use Rox\Core\Repositories\FlagRepository; use Rox\Core\Repositories\TargetGroupRepository; @@ -51,7 +53,7 @@ public function testTokenType() public function testSimpleExpressionEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("\"\"")->stringValue(), ""); $this->assertSame($parser->evaluateExpression("true")->stringValue(), "true"); @@ -63,7 +65,7 @@ public function testSimpleExpressionEvaluation() public function testNumeqExpressionsEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("numeq(\"la la\", \"la la\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("numeq(\"la la\", \"la,la\")")->boolValue(), false); @@ -96,7 +98,7 @@ public function testNumeqExpressionsEvaluation() public function testEqExpressionsEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("eq(\"la la\", \"la la\")")->boolValue(), true); $this->assertSame($parser->evaluateExpression("eq(\"la la\", \"la,la\")")->boolValue(), false); @@ -116,7 +118,7 @@ public function testEqExpressionsEvaluation() public function testComparisonExpressionsEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("lt(500, 100)")->boolValue(), false); $this->assertSame($parser->evaluateExpression("lt(500, 500)")->boolValue(), false); @@ -142,7 +144,7 @@ public function testComparisonExpressionsEvaluation() public function testSemVerComparisonEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("semverLt(\"1.1.0\", \"1.1\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("semverLte(\"1.1.0\", \"1.1\")")->boolValue(), false); @@ -159,7 +161,7 @@ public function testSemVerComparisonEvaluation() public function testComparisonWithUndefinedEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("gte(500, undefined)")->boolValue(), false); $this->assertSame($parser->evaluateExpression("gt(500, undefined)")->boolValue(), false); @@ -174,7 +176,7 @@ public function testComparisonWithUndefinedEvaluation() public function testUnknownOperatorEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("NOT_AN_OPERATOR(500, 500)")->boolValue(), false); $this->assertSame($parser->evaluateExpression("JUSTAWORD(500, 500)")->boolValue(), false); @@ -182,7 +184,7 @@ public function testUnknownOperatorEvaluation() public function testUndefinedEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("isUndefined(undefined)")->boolValue(), true); $this->assertSame($parser->evaluateExpression("isUndefined(123123)")->boolValue(), false); @@ -191,7 +193,7 @@ public function testUndefinedEvaluation() public function testNowEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("gte(now(), now())")->boolValue(), true); $this->assertSame($parser->evaluateExpression("gte(now(), 2458.123)")->boolValue(), true); @@ -200,7 +202,7 @@ public function testNowEvaluation() public function testRegularExpressionEvaluation() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame($parser->evaluateExpression("match(\"111\", \"222\", \"\")")->boolValue(), false); $this->assertSame($parser->evaluateExpression("match(\".*\", \"222\", \"\")")->boolValue(), false); @@ -222,7 +224,7 @@ public function testRegularExpressionEvaluation() public function testIfThenExpressionEvaluationString() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame("AB", $parser->evaluateExpression("ifThen(and(true, or(true, true)), \"AB\", \"CD\")")->stringValue()); $this->assertSame("CD", $parser->evaluateExpression("ifThen(and(false, or(true, true)), \"AB\", \"CD\")")->stringValue()); @@ -236,7 +238,7 @@ public function testIfThenExpressionEvaluationString() public function testIfThenExpressionEvaluationIntNumber() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame(1, $parser->evaluateExpression("ifThen(and(true, or(true, true)), 1, 2)")->integerValue()); $this->assertSame(2, $parser->evaluateExpression("ifThen(and(false, or(true, true)), 1, 2)")->integerValue()); @@ -250,7 +252,7 @@ public function testIfThenExpressionEvaluationIntNumber() public function testIfThenExpressionEvaluationDoubleNumber() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame(1.1, $parser->evaluateExpression("ifThen(and(true, or(true, true)), 1.1, 2.2)")->doubleValue()); $this->assertSame(2.2, $parser->evaluateExpression("ifThen(and(false, or(true, true)), 1.1, 2.2)")->doubleValue()); @@ -264,7 +266,7 @@ public function testIfThenExpressionEvaluationDoubleNumber() public function testIfThenExpressionEvaluationBoolean() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->assertSame(true, $parser->evaluateExpression("ifThen(and(true, or(true, true)), true, false)")->boolValue()); $this->assertSame(false, $parser->evaluateExpression("ifThen(and(false, or(true, true)), true, false)")->boolValue()); @@ -281,7 +283,7 @@ public function testIfThenExpressionEvaluationBoolean() public function testInArray() { - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $experimentsExtensions = new ExperimentsExtensions($parser, new TargetGroupRepository(), new FlagRepository(), new ExperimentRepository()); $roxxPropertiesExtensions = new PropertiesExtensions($parser, new CustomPropertyRepository(), new DynamicProperties()); $experimentsExtensions->extend(); diff --git a/tests/Rox/Core/Roxx/PropertiesExtensionsTests.php b/tests/Rox/Core/Roxx/PropertiesExtensionsTests.php index bae18c9..5791141 100644 --- a/tests/Rox/Core/Roxx/PropertiesExtensionsTests.php +++ b/tests/Rox/Core/Roxx/PropertiesExtensionsTests.php @@ -2,12 +2,16 @@ namespace Rox\Core\Roxx; +use Exception; +use Mockery; use Rox\Core\Context\ContextBuilder; use Rox\Core\Context\ContextInterface; use Rox\Core\CustomProperties\CustomProperty; use Rox\Core\CustomProperties\CustomPropertyRepository; use Rox\Core\CustomProperties\CustomPropertyType; use Rox\Core\CustomProperties\DynamicProperties; +use Rox\Core\ErrorHandling\ExceptionTrigger; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\RoxTestCase; class PropertiesExtensionsTests extends RoxTestCase @@ -15,7 +19,7 @@ class PropertiesExtensionsTests extends RoxTestCase public function testRoxxPropertiesExtensionsString() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -31,7 +35,7 @@ public function testRoxxPropertiesExtensionsString() public function testRoxxPropertiesExtensionsInt() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -47,7 +51,7 @@ public function testRoxxPropertiesExtensionsInt() public function testRoxxPropertiesExtensionsDouble() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -64,7 +68,7 @@ public function testRoxxPropertiesExtensionsDouble() public function testRoxxPropertiesExtensionsWithContextString() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -83,7 +87,7 @@ public function testRoxxPropertiesExtensionsWithContextString() public function testRoxxPropertiesExtensionsWithContextInt() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -102,7 +106,7 @@ public function testRoxxPropertiesExtensionsWithContextInt() public function testRoxxPropertiesExtensionsWithContextIntWithString() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -121,7 +125,7 @@ public function testRoxxPropertiesExtensionsWithContextIntWithString() public function testRoxxPropertiesExtensionsWithContextIntNotEqual() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -140,7 +144,7 @@ public function testRoxxPropertiesExtensionsWithContextIntNotEqual() public function testUnknownProperty() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -156,7 +160,7 @@ public function testUnknownProperty() public function testNullProperty() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -174,7 +178,7 @@ public function testNullProperty() public function testDefaultDynamicRule() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -188,7 +192,7 @@ public function testDefaultDynamicRule() public function testCustomDynamicRule() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -206,7 +210,7 @@ public function testCustomDynamicRule() public function testDynamicRuleReturnsNull() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -220,7 +224,7 @@ public function testDynamicRuleReturnsNull() public function testDynamicRuleReturnsSupportedType() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -243,7 +247,7 @@ public function testDynamicRuleReturnsSupportedType() public function testDynamicRuleReturnUnsupportedType() { $customPropertiesRepository = new CustomPropertyRepository(); - $parser = new Parser(); + $parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $dynamicProperties = new DynamicProperties(); $roxxPropertiesExtensions = new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); @@ -253,4 +257,107 @@ public function testDynamicRuleReturnUnsupportedType() $context = (new ContextBuilder())->build(["testKeyRule", []]); $this->assertEquals($parser->evaluateExpression("eq(undefined, property(\"testKeyRule\"))", $context)->boolValue(), true); } + + public function testDynamicRulesInvokeUserUnhandledErrorInvokerOnCustomPropertyException() + { + $unhandledErrorInvokeCalled = false; + $ex = new Exception("user exception"); + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturnUsing(function ($sender, $trigger, $exception) use (&$unhandledErrorInvokeCalled, $ex) { + $this->assertSame(ExceptionTrigger::CustomPropertyGenerator, $trigger); + $this->assertSame($ex, $exception); + $unhandledErrorInvokeCalled = true; + }) + ->byDefault() + ->getMock(); + + $customPropertiesRepository = new CustomPropertyRepository(); + $parser = new Parser($userUnhandledErrorInvoker); + $dynamicProperties = new DynamicProperties(); + $dynamicProperties->setDynamicPropertiesRule(function () { + return null; + }); + + $roxxPropertiesExtensions = + new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); + + $customPropertiesRepository->addCustomProperty(new CustomProperty("testKeyRule", + CustomPropertyType::getInt(), + function () use ($ex) { + throw $ex; + })); + + $roxxPropertiesExtensions->extend(); + + $context = (new ContextBuilder())->build(["testKey" => 5]); + $this->assertFalse($parser->evaluateExpression("eq(5, property(\"testKeyRule\"))", $context)->boolValue()); + $this->assertTrue($unhandledErrorInvokeCalled); + } + + public function testDynamicRulesInvokeUserUnhandledErrorInvokerOnDynamicRuleException() + { + $unhandledErrorInvokeCalled = false; + $ex = new Exception("user exception"); + + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturnUsing(function ($sender, $trigger, $exception) use (&$unhandledErrorInvokeCalled, $ex) { + $this->assertSame(ExceptionTrigger::DynamicPropertiesRule, $trigger); + $this->assertSame($ex, $exception); + $unhandledErrorInvokeCalled = true; + }) + ->byDefault() + ->getMock(); + + $dynamicRule = function () use ($ex) { + throw $ex; + }; + + $customPropertiesRepository = new CustomPropertyRepository(); + $parser = new Parser($userUnhandledErrorInvoker); + $dynamicProperties = new DynamicProperties(); + $dynamicProperties->setDynamicPropertiesRule($dynamicRule); + + $roxxPropertiesExtensions = + new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); + + $roxxPropertiesExtensions->extend(); + + $context = (new ContextBuilder())->build(["testKeyRule", []]); + $this->assertFalse($parser->evaluateExpression("eq(undefined, property(\"testKeyRule\"))", $context)->boolValue()); + $this->assertTrue($unhandledErrorInvokeCalled); + } + + public function testDynamicRulesDoesntInvokeUserUnhandledErrorInvokerOnDefaultDynamicRuleException() + { + $unhandledErrorInvokeCalled = false; + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturnUsing(function () use (&$unhandledErrorInvokeCalled) { + $unhandledErrorInvokeCalled = true; + }) + ->byDefault() + ->getMock(); + + $customPropertiesRepository = new CustomPropertyRepository(); + $parser = new Parser($userUnhandledErrorInvoker); + $dynamicProperties = new DynamicProperties(); + + $roxxPropertiesExtensions = + new PropertiesExtensions($parser, $customPropertiesRepository, $dynamicProperties); + + $roxxPropertiesExtensions->extend(); + + $mockedContext = Mockery::mock(ContextInterface::class) + ->shouldReceive('get') + ->andReturnUsing(function () { + throw new Exception("our exception"); + }) + ->byDefault() + ->getMock(); + + $this->assertFalse($parser->evaluateExpression("eq(undefined, property(\"testKeyRule\"))", $mockedContext)->boolValue()); + $this->assertFalse($unhandledErrorInvokeCalled); + } } diff --git a/tests/Rox/Server/Flags/RoxFlagsTestCase.php b/tests/Rox/Server/Flags/RoxFlagsTestCase.php index 6e43e37..6fbf3c0 100644 --- a/tests/Rox/Server/Flags/RoxFlagsTestCase.php +++ b/tests/Rox/Server/Flags/RoxFlagsTestCase.php @@ -4,10 +4,12 @@ namespace Rox\Server\Flags; +use Mockery; use Rox\Core\Client\InternalFlagsInterface; use Rox\Core\Configuration\Models\ExperimentModel; use Rox\Core\Entities\FlagSetter; use Rox\Core\Entities\RoxStringBase; +use Rox\Core\ErrorHandling\UserspaceUnhandledErrorInvokerInterface; use Rox\Core\Impression\ImpressionArgs; use Rox\Core\Impression\ImpressionInvoker; use Rox\Core\Impression\XImpressionInvoker; @@ -55,18 +57,24 @@ protected function setUp() { parent::setUp(); - $internalFlags = \Mockery::mock(InternalFlagsInterface::class) + $internalFlags = Mockery::mock(InternalFlagsInterface::class) ->shouldReceive('isEnabled') ->andReturn(false) ->byDefault() ->getMock(); - $this->_impressionInvoker = new XImpressionInvoker($internalFlags, null, null); + $userUnhandledErrorInvoker = Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class) + ->shouldReceive('invoke') + ->andReturn() + ->byDefault() + ->getMock(); + + $this->_impressionInvoker = new XImpressionInvoker($internalFlags, $userUnhandledErrorInvoker, null); $this->_impressionInvoker->register(function (ImpressionArgs $e) { $this->_lastImpression = $e; }); - $this->_parser = new Parser(); + $this->_parser = new Parser(Mockery::mock(UserspaceUnhandledErrorInvokerInterface::class)); $this->_flagRepo = new FlagRepository(); $this->_expRepo = new ExperimentRepository(); $this->_flagSetter = new FlagSetter($this->_flagRepo, $this->_parser, $this->_expRepo, $this->_impressionInvoker); From a3eb4c3ca26a575a13a8834bc48f03bc6ea68997 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Tue, 28 Sep 2021 10:05:50 +0300 Subject: [PATCH 08/15] Fix tests failing when ran by PHP 7 --- tests/Rox/Core/Impressions/ImpressionInvokerTests.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php index c71b625..00967ec 100644 --- a/tests/Rox/Core/Impressions/ImpressionInvokerTests.php +++ b/tests/Rox/Core/Impressions/ImpressionInvokerTests.php @@ -39,7 +39,7 @@ public function testWillCallUserUnhandledErrorInvoker() ->getMock(); $impressionInvoker = new XImpressionInvoker($internalFlags, $userUnhandledErrorInvoker, null, null); - $impressionInvoker->register(function ($sender, $e) use ($ex) { + $impressionInvoker->register(function () use ($ex) { throw $ex; }); $impressionInvoker->invoke(new ReportingValue("test", "value"), null, null); From b59427ae7854e03497211058e0cc3e14dd19873d Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Tue, 28 Sep 2021 12:02:35 +0300 Subject: [PATCH 09/15] Rox.Setup exceptions should be thrown --- src/Rox/Core/Register/Registerer.php | 3 +++ src/Rox/Server/Rox.php | 8 +------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Rox/Core/Register/Registerer.php b/src/Rox/Core/Register/Registerer.php index 0ab965e..f3f1ee1 100644 --- a/src/Rox/Core/Register/Registerer.php +++ b/src/Rox/Core/Register/Registerer.php @@ -9,6 +9,7 @@ use Rox\Core\Entities\RoxStringBase; use Rox\Core\Logging\LoggerFactory; use Rox\Core\Repositories\FlagRepositoryInterface; +use RuntimeException; class Registerer { @@ -67,6 +68,8 @@ public function registerInstance($container, $ns) $this->_log->error("Failed to obtain properties of class ${type}", [ 'exception' => $e ]); + throw new RuntimeException( + "Failed to obtain properties of class ${type}. See inner exception for details.", 0, $e); } foreach ($properties as $name => $value) { diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index 1151b26..a20a6f7 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -110,13 +110,7 @@ public static function setContext($context) public static function fetch() { - try { - self::getCore()->fetch(); - } catch (Exception $ex) { - self::getLog()->error("Failed in Rox::fetch", [ - 'exception' => $ex - ]); - } + self::getCore()->fetch(); } /** From 4fc880141e96cec27de611df66c6760b85c05e1c Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Tue, 28 Sep 2021 15:22:41 +0300 Subject: [PATCH 10/15] Add Rox.Shutdown to gracefully close background SDK operations Pretty useless feature for PHP SDK (I guess), but need to have consistency between SDKs. --- src/Rox/Core/Core.php | 2 +- src/Rox/Server/Rox.php | 112 +++++++++++++++++++-------- src/Rox/Server/RoxOptions.php | 2 +- src/Rox/Server/RoxOptionsBuilder.php | 2 +- src/Rox/Server/RoxState.php | 12 +++ tests/Rox/E2E/RoxE2ETests.php | 9 ++- 6 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 src/Rox/Server/RoxState.php diff --git a/src/Rox/Core/Core.php b/src/Rox/Core/Core.php index 5ec64fc..dbe7491 100644 --- a/src/Rox/Core/Core.php +++ b/src/Rox/Core/Core.php @@ -66,7 +66,7 @@ use Rox\Core\XPack\Security\XAPIKeyVerifier; use Rox\Core\XPack\Security\XSignatureVerifier; -class Core +final class Core { const MIN_CACHE_TTL_SECONDS = 30; const STATE_STORE_CACHE_TTL_SECONDS = 31556952; // 1 year diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index a20a6f7..366fa30 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -18,7 +18,7 @@ use Rox\Server\Flags\ServerEntitiesProvider; use RuntimeException; -class Rox +final class Rox { /** * @var Core $_core @@ -30,12 +30,27 @@ class Rox */ private static $_log; + /** + * @var int $_state + * @see RoxState + */ + private static $_state = RoxState::Idle; + + /** + * @return int + * @see RoxState + */ + public static function getState() + { + return self::$_state; + } + /** * @return Core */ private static function getCore() { - if (self::$_core == null) { + if (!self::$_core) { self::$_core = new Core(); } return self::$_core; @@ -46,7 +61,7 @@ private static function getCore() */ private static function getLog() { - if (self::$_log == null) { + if (!self::$_log) { self::$_log = LoggerFactory::getInstance()->createLogger(self::class); } return self::$_log; @@ -58,39 +73,74 @@ private static function getLog() */ public static function setup($apiKey, RoxOptions $roxOptions = null) { - try { - if ($roxOptions == null) { - $roxOptions = new RoxOptions(new RoxOptionsBuilder()); + if (self::$_state !== RoxState::Idle && + self::$_state !== RoxState::Corrupted) { + self::getLog()->warning("Rox was already initialized, skipping Setup"); + return; + } + + if (self::$_state === RoxState::Corrupted) { + self::reset(); + } + + if (self::$_state === RoxState::Idle) { + self::$_state = RoxState::SettingUp; + + try { + if (!$roxOptions) { + $roxOptions = new RoxOptions(new RoxOptionsBuilder()); + } + + $sdkSettings = new SdkSettings($apiKey, $roxOptions->getDevModeKey()); + $serverProperties = new ServerProperties($sdkSettings, $roxOptions); + + $props = $serverProperties->getAllProperties(); + $core = self::getCore(); + $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getPlatform()->getName(), CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getAppRelease()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getAppRelease()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getDistinctId()->getName(), CustomPropertyType::getString(), function ($c) { + return Uuid::uuid4()->toString(); + })); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.realPlatform", CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.customPlatform", CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.appKey", CustomPropertyType::getString(), $serverProperties->getRolloutKey())); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getLibVersion()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getLibVersion()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getApiVersion()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getApiVersion()->getName()])); + $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getDistinctId()->getName(), CustomPropertyType::getString(), function ($c) { + return Uuid::uuid4()->toString(); + })); + + $core->setup($sdkSettings, $serverProperties, $roxOptions); + self::$_state = RoxState::Set; + + } catch (Exception $ex) { + self::$_state = RoxState::Corrupted; + self::getLog()->error("Failed in Rox::setup", [ + 'exception' => $ex + ]); + throw new RuntimeException("Rox::setup failed. see innerException", $ex); } + } + } - $sdkSettings = new SdkSettings($apiKey, $roxOptions->getDevModeKey()); - $serverProperties = new ServerProperties($sdkSettings, $roxOptions); - - $props = $serverProperties->getAllProperties(); - $core = self::getCore(); - $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getPlatform()->getName(), CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getAppRelease()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getAppRelease()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty(PropertyType::getDistinctId()->getName(), CustomPropertyType::getString(), function ($c) { - return Uuid::uuid4()->toString(); - })); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.realPlatform", CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.customPlatform", CustomPropertyType::getString(), (string)$props[PropertyType::getPlatform()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal.appKey", CustomPropertyType::getString(), $serverProperties->getRolloutKey())); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getLibVersion()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getLibVersion()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getApiVersion()->getName(), CustomPropertyType::getSemver(), (string)$props[PropertyType::getApiVersion()->getName()])); - $core->addCustomPropertyIfNotExists(new DeviceProperty("internal." . PropertyType::getDistinctId()->getName(), CustomPropertyType::getString(), function ($c) { - return Uuid::uuid4()->toString(); - })); - - $core->setup($sdkSettings, $serverProperties, $roxOptions); - } catch (Exception $ex) { - self::getLog()->error("Failed in Rox::setup", [ - 'exception' => $ex - ]); - throw new RuntimeException("Rox::setup failed. see innerException", $ex); + public static function shutdown() + { + if (self::$_state !== RoxState::Set && + self::$_state !== RoxState::Corrupted) { + self::getLog()->warning("Rox can only be shutdown when it is already Set up, skipping Shutdown"); + } else { + self::reset(); } } + private static function reset() + { + self::$_state = RoxState::ShuttingDown; + // TODO: call some core->shutdown or something? + self::$_core = null; + self::$_state = RoxState::Idle; + } + /** * @param object $roxContainer * @param string $namespace diff --git a/src/Rox/Server/RoxOptions.php b/src/Rox/Server/RoxOptions.php index 1711b77..67955ca 100644 --- a/src/Rox/Server/RoxOptions.php +++ b/src/Rox/Server/RoxOptions.php @@ -5,7 +5,7 @@ use Kevinrob\GuzzleCache\Storage\CacheStorageInterface; use Rox\Core\Client\RoxOptionsInterface; -class RoxOptions implements RoxOptionsInterface +final class RoxOptions implements RoxOptionsInterface { /** * @var string $_devModeKey diff --git a/src/Rox/Server/RoxOptionsBuilder.php b/src/Rox/Server/RoxOptionsBuilder.php index e4996ad..bdc3173 100644 --- a/src/Rox/Server/RoxOptionsBuilder.php +++ b/src/Rox/Server/RoxOptionsBuilder.php @@ -4,7 +4,7 @@ use Kevinrob\GuzzleCache\Storage\CacheStorageInterface; -class RoxOptionsBuilder +final class RoxOptionsBuilder { /** * @var string $_devModeKey diff --git a/src/Rox/Server/RoxState.php b/src/Rox/Server/RoxState.php new file mode 100644 index 0000000..d3afdb6 --- /dev/null +++ b/src/Rox/Server/RoxState.php @@ -0,0 +1,12 @@ +setConfigurationFetchedHandler(function (ConfigurationFetchedArgs $args) { - if ($args != null && $args->getFetcherStatus() == FetcherStatus::AppliedFromNetwork) { + if ($args->getFetcherStatus() == FetcherStatus::AppliedFromNetwork) { TestVars::$configurationFetchedCount++; } }) ->setImpressionHandler(function (ImpressionArgs $args) { - if ($args != null && $args->getReportingValue() != null) { + if ($args->getReportingValue() != null) { if ($args->getReportingValue()->getName() == "flagForImpression") { TestVars::$isImpressionRaised = true; } @@ -53,6 +53,11 @@ public static function setUpBeforeClass() Rox::setup("5df8d5e802e23378643705bf", $options); } + public static function tearDownAfterClass() + { + Rox::shutdown(); + } + protected function setUp() { parent::setUp(); From 2b756a3a7a5442214cc8cea3d1e5729d3901c5d7 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Tue, 28 Sep 2021 16:26:22 +0300 Subject: [PATCH 11/15] fixes #20 PHP 8 support Starting with SDK 5, PHP 5.6 is no longer supported (it's impossible to support both 5.6 and 8.0 using the same codebase). MIN PHP version is now 7.3. --- .github/workflows/test.yml | 9 +- README.md | 2 +- composer.json | 12 +- composer.lock | 2102 +++++++++++------ src/Rox/Core/Entities/RoxStringBase.php | 16 +- .../Repositories/TargetGroupRepository.php | 3 + src/Rox/Core/Roxx/StringTokenizer.php | 2 +- src/Rox/Core/XPack/Network/StateSender.php | 13 +- src/Rox/Server/Flags/RoxFlag.php | 5 +- .../ConfigurationParserTests.php | 2 +- tests/Rox/Core/CoreTests.php | 2 +- tests/Rox/Core/Entities/FlagSetterTests.php | 2 +- .../Network/ConfigurationFetcherRoxyTests.php | 2 +- .../Network/ConfigurationFetcherTests.php | 2 +- .../Core/XPack/Network/StateSenderTests.php | 2 +- tests/Rox/E2E/RoxE2ETests.php | 6 +- tests/Rox/RoxTestCase.php | 7 +- tests/Rox/Server/Flags/DynamicApiTests.php | 2 +- tests/Rox/Server/Flags/RoxFlagsTestCase.php | 2 +- 19 files changed, 1405 insertions(+), 788 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 460295b..331bbcb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: operating-system: [ubuntu-latest, windows-latest, macOS-latest] - php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4'] + php-versions: ['7.3', '7.4', '8.0'] name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }} @@ -36,13 +36,8 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate - # TODO: remove the following step once Mockery releases backport of https://github.com/mockery/mockery/pull/980 to v0.9.XX - - name: PHP v7.4 workaround - run: composer require --dev hamcrest/hamcrest-php:2.0 mockery/mockery:1.3.1 --ignore-platform-reqs - - # TODO: remove --ignore-platform-reqs once Mockery releases backport of https://github.com/mockery/mockery/pull/980 to v0.9.XX - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest --ignore-platform-reqs + run: composer install --prefer-dist --no-progress --no-suggest - name: Run test suite run: composer test diff --git a/README.md b/README.md index 919009c..8433743 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Please see the detailed instructions in our docs how to [add Rollout to your pro ## Requirements -PHP 5.6 or later. +PHP 7.3 or later. The following extensions should be enabled in php.ini: diff --git a/composer.json b/composer.json index d38450b..6ece11c 100644 --- a/composer.json +++ b/composer.json @@ -21,25 +21,25 @@ }, "config": { "platform": { - "php": "5.6" + "php": "7.3" } }, "minimum-stability": "stable", "require": { - "php": ">=5.5", + "php": ">=7.3", "ext-json": "*", "ext-openssl": "*", - "guzzlehttp/guzzle": "^6.4", + "guzzlehttp/guzzle": "^6.4|^7.3.0", "psr/http-message": "^1.0", "psr/log": "^1.1", - "ramsey/uuid": "^3.9", + "ramsey/uuid": "^3.9|^4.1.1", "monolog/monolog": "^1.25|^2", "kevinrob/guzzle-cache-middleware": "^3.2", "doctrine/cache": "^1.6" }, "require-dev": { - "phpunit/phpunit": "^5.7.10", - "mockery/mockery": "^1.3.1", + "phpunit/phpunit": "^9.5", + "mockery/mockery": "^1.4", "hamcrest/hamcrest-php": "^2.0" }, "scripts": { diff --git a/composer.lock b/composer.lock index d8a1493..599686c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,39 +4,103 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d56279a29d6608b4caae7760228caff2", + "content-hash": "0c59af4d7c436188b4e3f06e94dd5593", "packages": [ + { + "name": "brick/math", + "version": "0.9.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae", + "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0", + "vimeo/psalm": "4.9.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.9.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2021-08-15T20:50:18+00:00" + }, { "name": "doctrine/cache", - "version": "v1.6.2", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b" + "reference": "4cf401d14df219fa6f38b671f5493449151c9ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/eb152c5100571c7a45470ff2a35095ab3f3b900b", - "reference": "eb152c5100571c7a45470ff2a35095ab3f3b900b", + "url": "https://api.github.com/repos/doctrine/cache/zipball/4cf401d14df219fa6f38b671f5493449151c9ad8", + "reference": "4cf401d14df219fa6f38b671f5493449151c9ad8", "shasum": "" }, "require": { - "php": "~5.5|~7.0" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "phpunit/phpunit": "~4.8|~5.0", + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", "predis/predis": "~1.0", - "satooshi/php-coveralls": "~0.6" + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", + "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" }, + "type": "library", "autoload": { "psr-4": { "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" @@ -47,6 +111,10 @@ "MIT" ], "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, { "name": "Roman Borschel", "email": "roman@code-factory.org" @@ -55,10 +123,6 @@ "name": "Benjamin Eberlei", "email": "kontakt@beberlei.de" }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, { "name": "Jonathan Wage", "email": "jonwage@gmail.com" @@ -68,51 +132,79 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", "keywords": [ + "abstraction", + "apcu", "cache", - "caching" + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.6.x" + "source": "https://github.com/doctrine/cache/tree/1.12.1" }, - "time": "2017-07-22T12:49:21+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-07-17T14:39:21+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.5.5", + "version": "7.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" + "reference": "7008573787b430c1c1f650e3722d9bba59967628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", - "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.6.1", - "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.17.0" + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", "ext-curl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", "psr/log": "^1.1" }, "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5-dev" + "dev-master": "7.3-dev" } }, "autoload": { @@ -132,6 +224,11 @@ "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], "description": "Guzzle is a PHP HTTP client library", @@ -142,14 +239,34 @@ "framework", "http", "http client", + "psr-18", + "psr-7", "rest", "web service" ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/6.5" + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" }, - "time": "2020-06-16T21:01:06+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2021-03-23T11:33:13+00:00" }, { "name": "guzzlehttp/promises", @@ -366,51 +483,64 @@ }, { "name": "monolog/monolog", - "version": "1.26.1", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" + "reference": "437e7a1c50044b92773b361af77620efb76fff59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/437e7a1c50044b92773b361af77620efb76fff59", + "reference": "437e7a1c50044b92773b361af77620efb76fff59", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" }, "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", + "elasticsearch/elasticsearch": "^7", + "graylog2/gelf-php": "^1.4.2", + "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", + "phpspec/prophecy": "^1.6.1", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5", + "predis/predis": "^1.1", + "rollbar/rollbar": "^1.3", + "ruflin/elastica": ">=0.90@dev", "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, "autoload": { "psr-4": { "Monolog\\": "src/Monolog" @@ -424,11 +554,11 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", @@ -436,7 +566,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/1.26.1" + "source": "https://github.com/Seldaek/monolog/tree/2.3.4" }, "funding": [ { @@ -448,36 +578,36 @@ "type": "tidelift" } ], - "time": "2021-05-28T08:32:12+00:00" + "time": "2021-09-15T11:27:21+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.20", + "name": "psr/http-client", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a" + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a", - "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", "shasum": "" }, "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, "autoload": { - "files": [ - "lib/random.php" - ] + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -485,24 +615,22 @@ ], "authors": [ { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" } ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" + "http", + "http-client", + "psr", + "psr-18" ], "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" + "source": "https://github.com/php-fig/http-client/tree/master" }, - "time": "2021-04-17T09:33:01+00:00" + "time": "2020-06-29T06:28:15+00:00" }, { "name": "psr/http-message", @@ -651,57 +779,147 @@ }, "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "ramsey/collection", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/eaca1dc1054ddd10cbd83c1461907bee6fb528fa", + "reference": "eaca1dc1054ddd10cbd83c1461907bee6fb528fa", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-08-06T03:41:06+00:00" + }, { "name": "ramsey/uuid", - "version": "3.9.6", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3" + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ffa80ab953edd85d5b6c004f96181a538aad35a3", - "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", "shasum": "" }, "require": { + "brick/math": "^0.8 || ^0.9", "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | ^9.99.99", - "php": "^5.4 | ^7.0 | ^8.0", - "symfony/polyfill-ctype": "^1.8" + "php": "^7.2 || ^8.0", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-php80": "^1.14" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1 | ^2", - "doctrine/annotations": "^1.2", - "goaop/framework": "1.0.0-alpha.2 | ^1 | >=2.1.0 <=2.3.2", - "mockery/mockery": "^0.9.11 | ^1", + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", "moontoast/math": "^1.1", - "nikic/php-parser": "<=4.5.0", "paragonie/random-lib": "^2", - "php-mock/php-mock-phpunit": "^0.3 | ^1.1 | ^2.6", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpunit/phpunit": ">=4.8.36 <9.0.0 | >=9.3.0", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5 || ^9", + "slevomat/coding-standard": "^7.0", "squizlabs/php_codesniffer": "^3.5", - "yoast/phpunit-polyfills": "^1.0" + "vimeo/psalm": "^4.9" }, "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-main": "4.x-dev" + }, + "captainhook": { + "force-install": true } }, "autoload": { @@ -716,23 +934,7 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - }, - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "keywords": [ "guid", "identifier", @@ -740,9 +942,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "rss": "https://github.com/ramsey/uuid/releases.atom", - "source": "https://github.com/ramsey/uuid", - "wiki": "https://github.com/ramsey/uuid/wiki" + "source": "https://github.com/ramsey/uuid/tree/4.2.3" }, "funding": [ { @@ -754,24 +954,24 @@ "type": "tidelift" } ], - "time": "2021-09-25T23:07:42+00:00" + "time": "2021-09-25T23:10:38+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.19.0", + "version": "v1.23.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b" + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/aed596913b70fae57be53d86faa2e9ef85a2297b", - "reference": "aed596913b70fae57be53d86faa2e9ef85a2297b", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.1" }, "suggest": { "ext-ctype": "For best performance" @@ -779,7 +979,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -817,7 +1017,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0" }, "funding": [ { @@ -833,35 +1033,29 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2021-02-19T12:13:01+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.19.0", + "name": "symfony/polyfill-php80", + "version": "v1.23.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "4ad5115c0f5d5172a9fe8147675ec6de266d8826" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/4ad5115c0f5d5172a9fe8147675ec6de266d8826", - "reference": "4ad5115c0f5d5172a9fe8147675ec6de266d8826", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php70": "^1.10", - "symfony/polyfill-php72": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -870,10 +1064,13 @@ }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" + "Symfony\\Polyfill\\Php80\\": "" }, "files": [ "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -882,30 +1079,28 @@ ], "authors": [ { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" }, { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" }, "funding": [ { @@ -921,32 +1116,29 @@ "type": "tidelift" } ], - "time": "2020-10-21T09:57:48+00:00" + "time": "2021-07-28T13:41:28+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.19.0", + "name": "symfony/polyfill-php81", + "version": "v1.23.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8db0ae7936b42feb370840cf24de1a144fb0ef27" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "e66119f3de95efc359483f810c4c3e6436279436" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8db0ae7936b42feb370840cf24de1a144fb0ef27", - "reference": "8db0ae7936b42feb370840cf24de1a144fb0ef27", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436", + "reference": "e66119f3de95efc359483f810c4c3e6436279436", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-intl": "For best performance" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" + "dev-main": "1.23-dev" }, "thanks": { "name": "symfony/polyfill", @@ -955,7 +1147,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "files": [ "bootstrap.php" @@ -978,18 +1170,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.19.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0" }, "funding": [ { @@ -1005,46 +1195,41 @@ "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" - }, + "time": "2021-05-21T13:25:03+00:00" + } + ], + "packages-dev": [ { - "name": "symfony/polyfill-php70", - "version": "v1.19.0", + "name": "doctrine/instantiator", + "version": "1.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e" + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3fe414077251a81a1b15b1c709faf5c2fbae3d4e", - "reference": "3fe414077251a81a1b15b1c709faf5c2fbae3d4e", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" + "php": "^7.1 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1052,259 +1237,252 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "constructor", + "instantiate" ], "support": { - "source": "https://github.com/symfony/polyfill-php70/tree/v1.19.0" + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", + "url": "https://www.doctrine-project.org/sponsorship.html", "type": "custom" }, { - "url": "https://github.com/fabpot", - "type": "github" + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", "type": "tidelift" } ], - "time": "2020-10-23T09:01:57+00:00" + "time": "2020-11-10T18:47:58+00:00" }, { - "name": "symfony/polyfill-php72", - "version": "v1.19.0", + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "beecef6b463b06954638f02378f52496cb84bacc" + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/beecef6b463b06954638f02378f52496cb84bacc", - "reference": "beecef6b463b06954638f02378f52496cb84bacc", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.19-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-master": "2.1-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, - "files": [ - "bootstrap.php" + "classmap": [ + "hamcrest" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "This is the PHP port of Hamcrest Matchers", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "test" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.19.0" + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-10-23T09:01:57+00:00" - } - ], - "packages-dev": [ + "time": "2020-07-09T08:09:16+00:00" + }, { - "name": "doctrine/instantiator", - "version": "1.0.5", + "name": "mockery/mockery", + "version": "1.4.4", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "url": "https://github.com/mockery/mockery.git", + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e01123a0e847d52d186c5eb4b9bf58b0c6d00346", + "reference": "e01123a0e847d52d186c5eb4b9bf58b0c6d00346", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "^8.5 || ^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "psr-0": { + "Mockery": "library/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", "keywords": [ - "constructor", - "instantiate" + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" ], "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/master" + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.4.4" }, - "time": "2015-06-14T21:17:01+00:00" + "time": "2021-09-13T15:28:59+00:00" }, { - "name": "hamcrest/hamcrest-php", - "version": "v2.0.1", + "name": "myclabs/deep-copy", + "version": "1.10.2", "source": { "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", - "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", "shasum": "" }, "require": { - "php": "^5.3|^7.0|^8.0" + "php": "^7.1 || ^8.0" }, "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" + "myclabs/deep-copy": "self.version" }, "require-dev": { - "phpunit/php-file-iterator": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, "autoload": { - "classmap": [ - "hamcrest" + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "This is the PHP port of Hamcrest Matchers", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "test" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" }, - "time": "2020-07-09T08:09:16+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" }, { - "name": "mockery/mockery", - "version": "1.3.5", + "name": "nikic/php-parser", + "version": "v4.13.0", "source": { "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "472fa8ca4e55483d55ee1e73c963718c4393791d" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/472fa8ca4e55483d55ee1e73c963718c4393791d", - "reference": "472fa8ca4e55483d55ee1e73c963718c4393791d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/50953a2691a922aa1769461637869a0a2faa3f53", + "reference": "50953a2691a922aa1769461637869a0a2faa3f53", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "ext-tokenizer": "*", + "php": ">=7.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.10|^6.5|^7.5|^8.5|^9.3" + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "4.9-dev" } }, "autoload": { - "psr-0": { - "Mockery": "library/" + "psr-4": { + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", @@ -1313,116 +1491,157 @@ ], "authors": [ { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "name": "Nikita Popov" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", + "description": "A PHP parser written in PHP", "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" + "parser", + "php" ], "support": { - "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.3.5" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.0" }, - "time": "2021-09-13T15:33:03+00:00" + "time": "2021-09-20T12:20:58+00:00" }, { - "name": "myclabs/deep-copy", - "version": "1.7.0", + "name": "phar-io/manifest", + "version": "2.0.3", "source": { "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, - "files": [ - "src/DeepCopy/deep_copy.php" + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } ], + "description": "Library for handling version information and constraints", "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.x" + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.1.0" }, - "time": "2017-10-19T19:58:43+00:00" + "time": "2021-02-23T14:00:09+00:00" }, { "name": "phpdocumentor/reflection-common", - "version": "1.0.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1446,40 +1665,43 @@ ], "support": { "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2017-09-11T18:02:19+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.3.2", + "version": "5.2.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2" + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bf329f6c1aadea3299f08ee804682b7c45b326a2", - "reference": "bf329f6c1aadea3299f08ee804682b7c45b326a2", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" + "mockery/mockery": "~1.3.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1490,48 +1712,50 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/release/3.x" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" }, - "time": "2017-11-10T14:09:06+00:00" + "time": "2020-09-03T19:13:55+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.4.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30f38bffc6f24293dadd1823936372dfa9e86e2f", + "reference": "30f38bffc6f24293dadd1823936372dfa9e86e2f", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" + "ext-tokenizer": "*", + "psalm/phar": "^4.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1544,41 +1768,42 @@ "email": "me@mikevanriel.com" } ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/master" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.0" }, - "time": "2017-07-14T14:27:02+00:00" + "time": "2021-09-17T15:28:14+00:00" }, { "name": "phpspec/prophecy", - "version": "v1.10.3", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "451c3cd1418cf640de218914901e51b064abb093" + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", - "reference": "451c3cd1418cf640de218914901e51b064abb093", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", + "reference": "d86dfc2e2a3cd366cee475e52c6bb3bbc371aa0e", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", - "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.2", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5 || ^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + "phpspec/phpspec": "^6.0 || ^7.0", + "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.10.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -1613,46 +1838,50 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + "source": "https://github.com/phpspec/prophecy/tree/1.14.0" }, - "time": "2020-03-05T15:02:03+00:00" + "time": "2021-09-10T09:02:12+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "4.0.8", + "version": "9.2.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d4c798ed8d51506800b441f7a13ecb0f76f12218", + "reference": "d4c798ed8d51506800b441f7a13ecb0f76f12218", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "^1.3", - "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", - "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" + "nikic/php-parser": "^4.12.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-pcov": "*", + "ext-xdebug": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -1667,7 +1896,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1679,33 +1908,41 @@ "xunit" ], "support": { - "irc": "irc://irc.freenode.net/phpunit", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.7" }, - "time": "2017-04-02T07:44:40+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-09-17T05:39:03+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8", + "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1720,7 +1957,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1731,30 +1968,47 @@ "iterator" ], "support": { - "irc": "irc://irc.freenode.net/phpunit", "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.5" }, - "time": "2017-11-27T13:52:08+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:57:25+00:00" }, { - "name": "phpunit/php-text-template", - "version": "1.2.1", + "name": "phpunit/php-invoker", + "version": "3.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -1771,41 +2025,47 @@ "role": "lead" } ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "template" + "process" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" }, - "time": "2015-06-21T13:50:34+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" }, { - "name": "phpunit/php-timer", - "version": "1.0.9", + "name": "phpunit/php-text-template", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1820,46 +2080,51 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "timer" + "template" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" }, - "time": "2017-02-26T11:10:40+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "1.4.12", + "name": "phpunit/php-timer", + "version": "5.0.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", - "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1874,68 +2139,78 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "tokenizer" + "timer" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, - "abandoned": true, - "time": "2017-12-04T08:55:13+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" }, { "name": "phpunit/phpunit", - "version": "5.7.27", + "version": "9.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c" + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", - "reference": "b7803aeca3ccb99ad0a506fa80b64cd6a56bbc0c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a", + "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "^1.4.3", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "^1.0.6|^2.0.1", - "symfony/yaml": "~2.1|~3.0|~4.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpspec/prophecy": "^1.12.1", + "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.5", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.3", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^2.3.4", + "sebastian/version": "^3.0.2" }, "require-dev": { - "ext-pdo": "*" + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0.1" }, "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "ext-soap": "*", + "ext-xdebug": "*" }, "bin": [ "phpunit" @@ -1943,12 +2218,15 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7.x-dev" + "dev-master": "9.5-dev" } }, "autoload": { "classmap": [ "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -1971,43 +2249,44 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10" }, - "time": "2018-02-01T05:50:59+00:00" + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-09-25T07:38:51+00:00" }, { - "name": "phpunit/phpunit-mock-objects", - "version": "3.4.4", + "name": "sebastian/cli-parser", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118", - "reference": "a23b761686d50a560cc56233b9ecf49597cc9118", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-soap": "*" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -2022,48 +2301,104 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "irc": "irc://irc.freenode.net/phpunit", - "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", - "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" }, - "abandoned": true, - "time": "2017-06-30T09:13:00+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2085,7 +2420,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -2093,34 +2428,34 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "55f4261989e546dc112258c7a75935a81a7ce382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", + "reference": "55f4261989e546dc112258c7a75935a81a7ce382", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2133,6 +2468,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2144,14 +2483,10 @@ { "name": "Bernhard Schussek", "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", + "homepage": "https://github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", @@ -2159,34 +2494,98 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:49:45+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" }, - "time": "2017-01-29T09:50:25+00:00" + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" }, { "name": "sebastian/diff", - "version": "1.4.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", - "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2199,50 +2598,62 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" } ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" }, - "time": "2017-05-22T07:24:03+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "388b6ced16caa751030f6a69e588299fa09200ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", + "reference": "388b6ced16caa751030f6a69e588299fa09200ac", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -2269,36 +2680,42 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/master" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" }, - "time": "2016-11-26T07:53:53+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:52:38+00:00" }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65", + "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2311,6 +2728,10 @@ "BSD-3-Clause" ], "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, { "name": "Jeff Welch", "email": "whatthejeff@gmail.com" @@ -2319,17 +2740,13 @@ "name": "Volker Dusch", "email": "github@wallbash.com" }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, { "name": "Adam Harvey", "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" } ], "description": "Provides the functionality to export PHP variables for visualization", @@ -2340,29 +2757,38 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/master" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.3" }, - "time": "2016-11-19T08:54:04+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:24:23+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "ext-dom": "*", + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -2370,7 +2796,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2395,35 +2821,99 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-11T13:31:12+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] }, - "time": "2015-10-12T03:26:01+00:00" + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", - "version": "2.0.1", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2445,34 +2935,95 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, - "time": "2017-02-18T15:18:39+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "2.0.0", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2485,14 +3036,14 @@ "BSD-3-Clause" ], "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, { "name": "Sebastian Bergmann", "email": "sebastian@phpunit.de" }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, { "name": "Adam Harvey", "email": "aharvey@php.net" @@ -2502,31 +3053,40 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" }, - "time": "2016-11-19T07:33:16+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2548,31 +3108,93 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" }, - "time": "2015-07-28T20:34:47+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2595,107 +3217,97 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, - "time": "2016-10-03T07:35:21+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { - "name": "symfony/yaml", - "version": "v3.4.47", + "name": "theseer/tokenizer", + "version": "1.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "88289caa3c166321883f67fe5130188ebbb47094" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", - "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/console": "<3.4" - }, - "require-dev": { - "symfony/console": "~3.4|~4.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { - "source": "https://github.com/symfony/yaml/tree/v3.4.47" + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/theseer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2021-07-28T10:34:58+00:00" }, { "name": "webmozart/assert", - "version": "1.9.1", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", + "php": "^7.2 || ^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" + "vimeo/psalm": "<4.6.1 || 4.6.2" }, "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" + "phpunit/phpunit": "^8.5.13" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, "autoload": { "psr-4": { "Webmozart\\Assert\\": "src/" @@ -2719,9 +3331,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" + "source": "https://github.com/webmozarts/assert/tree/1.10.0" }, - "time": "2020-07-08T17:02:28+00:00" + "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], @@ -2730,13 +3342,13 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.5", + "php": ">=7.3", "ext-json": "*", "ext-openssl": "*" }, "platform-dev": [], "platform-overrides": { - "php": "5.6" + "php": "7.3" }, "plugin-api-version": "2.1.0" } diff --git a/src/Rox/Core/Entities/RoxStringBase.php b/src/Rox/Core/Entities/RoxStringBase.php index 4f5614e..e525ea4 100644 --- a/src/Rox/Core/Entities/RoxStringBase.php +++ b/src/Rox/Core/Entities/RoxStringBase.php @@ -83,15 +83,16 @@ public function __construct($defaultValue, $variations = []) /** * @param mixed $value - * @retrun mixed Value. + * @return string * @throws InvalidArgumentException */ protected function checkValueType($value) { - if (!$this->getConverter()->isValid($value)) { + $converter = $this->getConverter(); + if (!$converter->isValid($value)) { throw new InvalidArgumentException("Invalid value type: ${value}"); } - return $value; + return $converter->convertToString($value); } /** @@ -101,13 +102,16 @@ protected function checkValueType($value) */ protected function checkVariationsType(array $variations) { - if ($invalidVariations = array_filter($variations, function ($value) { - return !$this->getConverter()->isValid($value); + $converter = $this->getConverter(); + if ($invalidVariations = array_filter($variations, function ($value) use ($converter) { + return !$converter->isValid($value); })) { $invalidValue = array_shift($invalidVariations); throw new InvalidArgumentException("Invalid variation type: ${invalidValue}"); } - return $variations; + return array_map(function ($v) use ($converter) { + return $converter->convertToString($v); + }, $variations); } /** diff --git a/src/Rox/Core/Repositories/TargetGroupRepository.php b/src/Rox/Core/Repositories/TargetGroupRepository.php index 0299192..85eb147 100644 --- a/src/Rox/Core/Repositories/TargetGroupRepository.php +++ b/src/Rox/Core/Repositories/TargetGroupRepository.php @@ -26,6 +26,9 @@ function setTargetGroups($targetGroups) */ function getTargetGroup($id) { + if (!is_array($this->_targetGroups)) { + return null; + } return current(array_filter($this->_targetGroups, function (TargetGroupModel $element) use ($id) { return $element->getId() == $id; })); diff --git a/src/Rox/Core/Roxx/StringTokenizer.php b/src/Rox/Core/Roxx/StringTokenizer.php index 5315f00..ab1a019 100644 --- a/src/Rox/Core/Roxx/StringTokenizer.php +++ b/src/Rox/Core/Roxx/StringTokenizer.php @@ -149,7 +149,7 @@ private static function isSurrogate($c) */ private static function charCount($codePoint) { - return $codePoint >= self::MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1; + return ord($codePoint) >= self::MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1; } /** diff --git a/src/Rox/Core/XPack/Network/StateSender.php b/src/Rox/Core/XPack/Network/StateSender.php index c418ec8..f1dfa98 100644 --- a/src/Rox/Core/XPack/Network/StateSender.php +++ b/src/Rox/Core/XPack/Network/StateSender.php @@ -67,9 +67,9 @@ class StateSender * @param CustomPropertyRepositoryInterface $customPropertyRepository */ public function __construct( - HttpClientInterface $request, - DevicePropertiesInterface $deviceProperties, - FlagRepositoryInterface $flagRepository, + HttpClientInterface $request, + DevicePropertiesInterface $deviceProperties, + FlagRepositoryInterface $flagRepository, CustomPropertyRepositoryInterface $customPropertyRepository) { $this->_relevantAPICallParams = [ @@ -235,7 +235,12 @@ function send() $responseAsString = $fetchResult->getContent()->readAsString(); $responseJSON = json_decode($responseAsString, true); - if (array_key_exists("result", $responseJSON)) { + if (!is_array($responseJSON)) { + $this->_log->error( + sprintf("Failed to send state. The returned response is not a valid JSON: %s, Source: %s", + $responseAsString, + ConfigurationSource::toString($source))); + } else if (array_key_exists("result", $responseJSON)) { $responseResultValue = $responseJSON["result"]; if ((int)$responseResultValue == 404) { $shouldRetry = true; diff --git a/src/Rox/Server/Flags/RoxFlag.php b/src/Rox/Server/Flags/RoxFlag.php index 7a52f85..9b82916 100644 --- a/src/Rox/Server/Flags/RoxFlag.php +++ b/src/Rox/Server/Flags/RoxFlag.php @@ -17,10 +17,7 @@ class RoxFlag extends RoxStringBase implements BooleanFlagInterface public function __construct($defaultValue = false) { parent::__construct( - FlagValueConverters::getInstance() - ->getBool() - ->convertToString( - $this->checkValueType($defaultValue)), + $this->checkValueType($defaultValue), [BoolFlagValueConverter::FLAG_FALSE_VALUE, BoolFlagValueConverter::FLAG_TRUE_VALUE]); } diff --git a/tests/Rox/Core/Configuration/ConfigurationParserTests.php b/tests/Rox/Core/Configuration/ConfigurationParserTests.php index d754f20..7ed0011 100644 --- a/tests/Rox/Core/Configuration/ConfigurationParserTests.php +++ b/tests/Rox/Core/Configuration/ConfigurationParserTests.php @@ -44,7 +44,7 @@ class ConfigurationParserTests extends RoxTestCase */ private $_cfiEvent; - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/Core/CoreTests.php b/tests/Rox/Core/CoreTests.php index 4fcf428..a8a6237 100644 --- a/tests/Rox/Core/CoreTests.php +++ b/tests/Rox/Core/CoreTests.php @@ -21,7 +21,7 @@ class CoreTests extends RoxTestCase */ private $_mockedOptions; - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/Core/Entities/FlagSetterTests.php b/tests/Rox/Core/Entities/FlagSetterTests.php index 3bd04ce..14648af 100644 --- a/tests/Rox/Core/Entities/FlagSetterTests.php +++ b/tests/Rox/Core/Entities/FlagSetterTests.php @@ -154,6 +154,6 @@ public function testWillSetDataForAddedFlag() $this->assertEquals($flagRepo->getFlag("f1")->getImpressionInvoker(), $impressionInvoker); $this->assertEquals($flagRepo->getFlag("f2")->getImpressionInvoker(), $impressionInvoker); $this->assertEquals($flagRepo->getFlag("f1")->getExperiment()->getId(), "1"); - $this->assertNull($flagRepo->getFlag("f2")->getExperiment(), null); + $this->assertNull($flagRepo->getFlag("f2")->getExperiment()); } } diff --git a/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php b/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php index a399f7a..4528baa 100644 --- a/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php +++ b/tests/Rox/Core/Network/ConfigurationFetcherRoxyTests.php @@ -17,7 +17,7 @@ class ConfigurationFetcherRoxyTests extends RoxTestCase private $_dp; private $_bu; - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/Core/Network/ConfigurationFetcherTests.php b/tests/Rox/Core/Network/ConfigurationFetcherTests.php index 3841dd1..87ab9cd 100644 --- a/tests/Rox/Core/Network/ConfigurationFetcherTests.php +++ b/tests/Rox/Core/Network/ConfigurationFetcherTests.php @@ -30,7 +30,7 @@ class ConfigurationFetcherTests extends RoxTestCase */ private $_errorReporter; - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/Core/XPack/Network/StateSenderTests.php b/tests/Rox/Core/XPack/Network/StateSenderTests.php index df1ef3e..807223d 100644 --- a/tests/Rox/Core/XPack/Network/StateSenderTests.php +++ b/tests/Rox/Core/XPack/Network/StateSenderTests.php @@ -60,7 +60,7 @@ private function _createNewDeviceProp() ]; } - public function setUp() + public function setUp(): void { parent::setUp(); diff --git a/tests/Rox/E2E/RoxE2ETests.php b/tests/Rox/E2E/RoxE2ETests.php index ad56b05..41ad11d 100644 --- a/tests/Rox/E2E/RoxE2ETests.php +++ b/tests/Rox/E2E/RoxE2ETests.php @@ -22,7 +22,7 @@ class RoxE2ETests extends RoxTestCase */ private static $_staticLoggerFactory; - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { $_ENV[Environment::ENV_VAR_NAME] = Environment::QA; @@ -53,12 +53,12 @@ public static function setUpBeforeClass() Rox::setup("5df8d5e802e23378643705bf", $options); } - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { Rox::shutdown(); } - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/RoxTestCase.php b/tests/Rox/RoxTestCase.php index 7ec8e67..be4fab8 100644 --- a/tests/Rox/RoxTestCase.php +++ b/tests/Rox/RoxTestCase.php @@ -2,6 +2,7 @@ namespace Rox; +use Mockery; use PHPUnit\Framework\TestCase; use Psr\Log\LogLevel; use Rox\Core\Logging\LoggerFactory; @@ -24,7 +25,7 @@ abstract class RoxTestCase extends TestCase */ private $_noErrorsExpected; - protected function setUp() + protected function setUp(): void { parent::setUp(); @@ -35,11 +36,11 @@ protected function setUp() $this->_noErrorsExpected = false; } - protected function tearDown() + protected function tearDown(): void { parent::tearDown(); - \Mockery::close(); + Mockery::close(); $testLogger = $this->_loggerFactory->getLogger(); if ($this->_noWarningsExpected) { diff --git a/tests/Rox/Server/Flags/DynamicApiTests.php b/tests/Rox/Server/Flags/DynamicApiTests.php index f75d95c..3945c95 100644 --- a/tests/Rox/Server/Flags/DynamicApiTests.php +++ b/tests/Rox/Server/Flags/DynamicApiTests.php @@ -17,7 +17,7 @@ class DynamicApiTests extends RoxFlagsTestCase */ private $_ep; - protected function setUp() + protected function setUp(): void { parent::setUp(); diff --git a/tests/Rox/Server/Flags/RoxFlagsTestCase.php b/tests/Rox/Server/Flags/RoxFlagsTestCase.php index 6fbf3c0..fc1f844 100644 --- a/tests/Rox/Server/Flags/RoxFlagsTestCase.php +++ b/tests/Rox/Server/Flags/RoxFlagsTestCase.php @@ -53,7 +53,7 @@ class RoxFlagsTestCase extends RoxTestCase */ private $_lastImpression; - protected function setUp() + protected function setUp(): void { parent::setUp(); From 5d640c9001f7c37d93e1a579b78e13fd5b2c64e9 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Thu, 7 Oct 2021 22:02:00 +0300 Subject: [PATCH 12/15] SFM-1408 Add additional metadata to S3 configuration request PHP SDK --- src/Rox/Core/Network/ConfigurationFetcher.php | 10 +++++++++- tests/Rox/Core/Network/ConfigurationFetcherTests.php | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Rox/Core/Network/ConfigurationFetcher.php b/src/Rox/Core/Network/ConfigurationFetcher.php index 55ee3f5..a310607 100644 --- a/src/Rox/Core/Network/ConfigurationFetcher.php +++ b/src/Rox/Core/Network/ConfigurationFetcher.php @@ -59,7 +59,15 @@ private function _fetchFromCDN(array $properties) { return $this->_request->sendGet(new RequestData($this->_getCDNUrl($properties), [ PropertyType::getDistinctId()->getName() => - (string)$properties[PropertyType::getDistinctId()->getName()] + (string)$properties[PropertyType::getDistinctId()->getName()], + 'realPlatform' => + (string)@$properties['platform'], + 'sdkVersion' => + (string)@$properties['lib_version'], + 'platformVersion' => + php_sapi_name(), + 'languageVersion' => + PHP_VERSION ])); } diff --git a/tests/Rox/Core/Network/ConfigurationFetcherTests.php b/tests/Rox/Core/Network/ConfigurationFetcherTests.php index 87ab9cd..57d7fb8 100644 --- a/tests/Rox/Core/Network/ConfigurationFetcherTests.php +++ b/tests/Rox/Core/Network/ConfigurationFetcherTests.php @@ -37,6 +37,8 @@ protected function setUp(): void $this->_dp = Mockery::mock(DevicePropertiesInterface::class) ->shouldReceive('getAllProperties') ->andReturn([ + "lib_version" => "1.2.3", + "platform" => "PHP-test", "app_key" => "123", "api_version" => "4.0.0", "distinct_id" => "id" @@ -80,8 +82,14 @@ public function testWillReturnCDNDataWhenSuccessful() $result = $confFetcher->fetch(); $this->assertEquals($reqData[0]->getUrl(), "https://conf.rollout.io/123/buid"); - $this->assertEquals(count($reqData[0]->getQueryParams()), 1); - $this->assertEquals($reqData[0]->getQueryParams()[PropertyType::getDistinctId()->getName()], "id"); + + $qp = $reqData[0]->getQueryParams(); + $this->assertEquals(count($qp), 5); + $this->assertEquals($qp[PropertyType::getDistinctId()->getName()], "id"); + $this->assertEquals($qp['realPlatform'], "PHP-test"); + $this->assertEquals($qp['sdkVersion'], "1.2.3"); + $this->assertEquals($qp['platformVersion'], php_sapi_name()); + $this->assertEquals($qp['languageVersion'], PHP_VERSION); $this->assertEquals("harti", $result->getParsedData()["a"]); $this->assertEquals(ConfigurationSource::CDN, $result->getSource()); From fb1c4024aecc4871737a186da05af7ec102b5812 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Fri, 22 Oct 2021 18:15:20 +0300 Subject: [PATCH 13/15] Fix issue with flag value evaluation - special case of empty/zero values. When setting strings to empty string in dashboard, and numbers to zeroes, flags were evaluated to their defaults. --- src/Rox/Core/Entities/DoubleFlagValueConverter.php | 2 +- src/Rox/Core/Entities/IntFlagValueConverter.php | 2 +- src/Rox/Core/Entities/RoxStringBase.php | 12 ++---------- src/Rox/Core/Entities/StringFlagValueConverter.php | 4 +++- tests/Rox/Server/Flags/RoxDoubleTests.php | 8 ++++++++ tests/Rox/Server/Flags/RoxIntTests.php | 8 ++++++++ tests/Rox/Server/Flags/RoxStringTests.php | 8 ++++++++ 7 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Rox/Core/Entities/DoubleFlagValueConverter.php b/src/Rox/Core/Entities/DoubleFlagValueConverter.php index 5eff386..4206312 100644 --- a/src/Rox/Core/Entities/DoubleFlagValueConverter.php +++ b/src/Rox/Core/Entities/DoubleFlagValueConverter.php @@ -35,7 +35,7 @@ function convertToString($value) */ function normalizeValue($stringValue, $alternativeValue, LoggerInterface $log = null) { - if ($stringValue && NumericUtils::parseNumber($stringValue, $doubleValue)) { + if (!is_null($stringValue) && NumericUtils::parseNumber($stringValue, $doubleValue)) { return $doubleValue; } $log->warning("Experiment type mismatch (double), returning default value"); diff --git a/src/Rox/Core/Entities/IntFlagValueConverter.php b/src/Rox/Core/Entities/IntFlagValueConverter.php index 1c6be75..8375581 100644 --- a/src/Rox/Core/Entities/IntFlagValueConverter.php +++ b/src/Rox/Core/Entities/IntFlagValueConverter.php @@ -33,7 +33,7 @@ function convertToString($value) */ function normalizeValue($stringValue, $alternativeValue, LoggerInterface $log = null) { - if ($stringValue) { + if (!is_null($stringValue)) { if (!preg_match('/\d+\.\d+/', $stringValue) && ((($intValue = intval($stringValue)) !== 0) || $stringValue === '0')) { return $intValue; } else if ($log) { diff --git a/src/Rox/Core/Entities/RoxStringBase.php b/src/Rox/Core/Entities/RoxStringBase.php index e525ea4..ce71a58 100644 --- a/src/Rox/Core/Entities/RoxStringBase.php +++ b/src/Rox/Core/Entities/RoxStringBase.php @@ -67,7 +67,7 @@ public function __construct($defaultValue, $variations = []) if (is_null($defaultValue)) { throw new InvalidArgumentException("Default value cannot be null"); } - if (in_array(null, $variations)) { + if (in_array(null, $variations, true)) { throw new InvalidArgumentException("Variation cannot be null"); } if (!in_array($defaultValue, $variations)) { @@ -183,14 +183,6 @@ public function setParser($parser) $this->_parser = $parser; } - /** - * @return ContextInterface - */ - public function getGlobalContext() - { - return $this->_globalContext; - } - /** * @return ImpressionInvokerInterface */ @@ -318,7 +310,7 @@ private function _getExperimentValue($context, $evaluationContext = null) { $globalContext = ($this->_parser != null) ? $this->_parser->getGlobalContext() : null; $mergedContext = new MergedContext($globalContext, $context); - if ($this->_parser != null && $this->_condition) { + if ($this->_parser && isset($this->_condition) && ($this->_condition !== '')) { return $this->_parser->evaluateExpression($this->_condition, $mergedContext, $evaluationContext); } return new EvaluationResult(null, $mergedContext); diff --git a/src/Rox/Core/Entities/StringFlagValueConverter.php b/src/Rox/Core/Entities/StringFlagValueConverter.php index 17f44ad..9f6d190 100644 --- a/src/Rox/Core/Entities/StringFlagValueConverter.php +++ b/src/Rox/Core/Entities/StringFlagValueConverter.php @@ -33,6 +33,8 @@ function convertToString($value) */ function normalizeValue($stringValue, $alternativeValue, LoggerInterface $log = null) { - return $stringValue ?: $alternativeValue; + return is_string($stringValue) + ? $stringValue + : $alternativeValue; } } diff --git a/tests/Rox/Server/Flags/RoxDoubleTests.php b/tests/Rox/Server/Flags/RoxDoubleTests.php index dc37513..a13742a 100644 --- a/tests/Rox/Server/Flags/RoxDoubleTests.php +++ b/tests/Rox/Server/Flags/RoxDoubleTests.php @@ -116,4 +116,12 @@ public function testWillUseContext() $this->assertEquals(2.2, $variant->getValue($context)); $this->checkLastImpression('test', '2.2', true, 'key', 55); } + + public function testWillNotReturnDefaultValueWhenValueIsSetToZeroInDashboard() + { + $variant = new RoxDouble(1.1, [0.0]); + $this->setupFlag($variant, 'test', '0.0'); + $this->assertEquals(0.0, $variant->getValue()); + $this->checkLastImpression('test', '0', true); + } } \ No newline at end of file diff --git a/tests/Rox/Server/Flags/RoxIntTests.php b/tests/Rox/Server/Flags/RoxIntTests.php index 8fa1b26..bef2ff8 100644 --- a/tests/Rox/Server/Flags/RoxIntTests.php +++ b/tests/Rox/Server/Flags/RoxIntTests.php @@ -118,4 +118,12 @@ public function testWillUseContext() $this->assertEquals(2, $variant->getValue($context)); $this->checkLastImpression('test', '2', true, 'key', 55); } + + public function testWillNotReturnDefaultValueWhenValueIsSetToZeroInDashboard() + { + $variant = new RoxInt(1, [0]); + $this->setupFlag($variant, 'test', '0'); + $this->assertEquals(0, $variant->getValue()); + $this->checkLastImpression('test', '0', true); + } } \ No newline at end of file diff --git a/tests/Rox/Server/Flags/RoxStringTests.php b/tests/Rox/Server/Flags/RoxStringTests.php index bbf006d..bf32258 100644 --- a/tests/Rox/Server/Flags/RoxStringTests.php +++ b/tests/Rox/Server/Flags/RoxStringTests.php @@ -213,4 +213,12 @@ public function testWillRaiseImpression() $this->assertEquals('2', $variant->getValue()); $this->assertTrue($isImpressionRaised[0]); } + + public function testWillNotReturnDefaultValueWhenValueIsSetToEmptyStringInDashboard() + { + $variant = new RoxString('val', ['']); + $this->setupFlag($variant, 'test', '""'); + $this->assertEquals('', $variant->getValue()); + $this->checkLastImpression('test', '', true); + } } From d6226f21d3d25cd7876c455301a5aa35c229fe11 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Sat, 23 Oct 2021 08:11:29 +0300 Subject: [PATCH 14/15] Switch arguments in Rox::register method. Allow either 1 or 2 arguments. Namespace goes first always, for the consistency with other SDKs. --- src/Rox/Server/Rox.php | 14 +++++++++++--- tests/Rox/E2E/RoxE2ETests.php | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Rox/Server/Rox.php b/src/Rox/Server/Rox.php index 366fa30..8edb276 100644 --- a/src/Rox/Server/Rox.php +++ b/src/Rox/Server/Rox.php @@ -142,12 +142,20 @@ private static function reset() } /** - * @param object $roxContainer * @param string $namespace + * @param object $roxContainer */ - public static function register($roxContainer, $namespace = "") + public static function register() { - self::getCore()->register($namespace, $roxContainer); + if (func_num_args() == 2) { + self::getCore()->register( + func_get_arg(0), // ns + func_get_arg(1)); // container + } else { + self::getCore()->register("", + func_get_arg(0)); + } + } /** diff --git a/tests/Rox/E2E/RoxE2ETests.php b/tests/Rox/E2E/RoxE2ETests.php index 41ad11d..8ba7054 100644 --- a/tests/Rox/E2E/RoxE2ETests.php +++ b/tests/Rox/E2E/RoxE2ETests.php @@ -90,7 +90,7 @@ public function testRegisterAfterSetup() { $this->assertTrue(ContainerTwo::getInstance()->flag2->isEnabled()); $this->assertEquals("red", ContainerTwo::getInstance()->variant2->getValue()); - Rox::register(ContainerTwo::getInstance(), "afterSetup"); + Rox::register("afterSetup", ContainerTwo::getInstance()); $this->assertFalse(ContainerTwo::getInstance()->flag2->isEnabled()); $this->assertEquals("green", ContainerTwo::getInstance()->variant2->getValue()); } From 04561c74d85bdfc08a6754e20bfdb470597ea4a1 Mon Sep 17 00:00:00 2001 From: Andrew Anisimov Date: Wed, 27 Oct 2021 21:49:29 +0300 Subject: [PATCH 15/15] Few post-review follow up updates. 1. Change FetcherError.NoError const value to 0 (like in C/C++ SDK). 2. Set handler function as a source of error instead of the invoker itself. --- .../Core/Configuration/AbstractConfigurationFetchedInvoker.php | 2 +- src/Rox/Core/Configuration/FetcherError.php | 2 +- src/Rox/Core/Impression/ImpressionInvoker.php | 2 +- src/Rox/Core/XPack/Impression/XImpressionInvoker.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php b/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php index e537dcc..1b5c872 100644 --- a/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php +++ b/src/Rox/Core/Configuration/AbstractConfigurationFetchedInvoker.php @@ -74,7 +74,7 @@ protected final function fireConfigurationFetched(ConfigurationFetchedArgs $args try { $eventHandler($args); } catch (Exception $e) { - $this->_userUnhandledErrorInvoker->invoke($this, ExceptionTrigger::ConfigurationFetchedHandler, $e); + $this->_userUnhandledErrorInvoker->invoke($eventHandler, ExceptionTrigger::ConfigurationFetchedHandler, $e); } } } diff --git a/src/Rox/Core/Configuration/FetcherError.php b/src/Rox/Core/Configuration/FetcherError.php index f571fef..ae1c5a4 100644 --- a/src/Rox/Core/Configuration/FetcherError.php +++ b/src/Rox/Core/Configuration/FetcherError.php @@ -4,11 +4,11 @@ final class FetcherError { + const NoError = 0; const CorruptedJson = 1; const EmptyJson = 2; const SignatureVerificationError = 3; const NetworkError = 4; const MismatchAppKey = 5; const Unknown = 6; - const NoError = 7; } diff --git a/src/Rox/Core/Impression/ImpressionInvoker.php b/src/Rox/Core/Impression/ImpressionInvoker.php index 46c48b8..311d69a 100644 --- a/src/Rox/Core/Impression/ImpressionInvoker.php +++ b/src/Rox/Core/Impression/ImpressionInvoker.php @@ -63,7 +63,7 @@ private function _fireImpression(ReportingValue $value, $experiment, $context) $handler($args); } catch (Exception $e) { $this->_userUnhandledErrorInvoker - ->invoke($this, ExceptionTrigger::ImpressionHandler, $e); + ->invoke($handler, ExceptionTrigger::ImpressionHandler, $e); } } } diff --git a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php index c091652..2090742 100644 --- a/src/Rox/Core/XPack/Impression/XImpressionInvoker.php +++ b/src/Rox/Core/XPack/Impression/XImpressionInvoker.php @@ -129,7 +129,7 @@ private function _fireImpression(ImpressionArgs $args) } catch (Exception $e) { if ($this->_userUnhandledErrorInvoker) { $this->_userUnhandledErrorInvoker - ->invoke($this, ExceptionTrigger::ImpressionHandler, $e); + ->invoke($handler, ExceptionTrigger::ImpressionHandler, $e); } } }