From 04f5c202ffdb7b7dd02cc4d73b38ab6e9b39b7c0 Mon Sep 17 00:00:00 2001 From: Matjaz Pirnovar Date: Mon, 26 Jun 2023 11:30:36 -0700 Subject: [PATCH 1/6] [FSSDK-9022] Dev Containers and bug bash (#269) * Add bug bash autoloader * Initial version of decide tests * Fix autoloads; DRY print to console * Refactor and clean using * Refactor; Fix array outputs * Better output in linux env * Add notification on decide test * Output log to ensure logx impression sent * Verify error is logged for invalid flag * Add devcontainer config * Install dependencies in devcontainer * Update decide instructions * Add postCreateCommand shell postCreateCommand * Fix path on postCreateCommand * Finish devcontainer config * Refine decide.php * Renamed files & their refs * Instruction update * Add DecideAll skeleton * Initial DecideAll test * Finish DecideAll test; Decide fixes * DecideForKeys skeleton * Add decide for keys; update other decide classes * Comment DecideForKeys test calls * Track event test class skeleton * Update attributes in decide tests * Add track event tests * DRY $onTrackEvent and use in negative test * Event Key instead of "name" * Forced Decision skeleton * Add PHP documentation links * Initial attempt forced decision tests * Remove sensitive info * Added second part for forced decisions - not necessary, just a advanced addition. And Optimizely config. * Delete ForcedDecision.php removing this file, duplicate * Rename ForcedDecisionPart2.php to ForcedDecision.php * Update ForcedDecision.php * Update OptiConfig.php * Update EventBuilder.php SDK minor release bump * Update EventBuilderTest.php release version bump in the test to 3.10.0 * Update EventBuilder.php-revert * Update EventBuilderTest.php - revert --------- Co-authored-by: Mike Chu Co-authored-by: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> --- .devcontainer/devcontainer.json | 41 ++++ bug-bash/Decide.php | 144 ++++++++++++++ bug-bash/DecideAll.php | 134 +++++++++++++ bug-bash/DecideForKeys.php | 150 +++++++++++++++ bug-bash/ForcedDecision.php | 323 ++++++++++++++++++++++++++++++++ bug-bash/OptiConfig.php | 116 ++++++++++++ bug-bash/TrackEvent.php | 118 ++++++++++++ bug-bash/_bug-bash-autoload.php | 15 ++ 8 files changed, 1041 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 bug-bash/Decide.php create mode 100644 bug-bash/DecideAll.php create mode 100644 bug-bash/DecideForKeys.php create mode 100644 bug-bash/ForcedDecision.php create mode 100644 bug-bash/OptiConfig.php create mode 100644 bug-bash/TrackEvent.php create mode 100644 bug-bash/_bug-bash-autoload.php diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..7316d069 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,41 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/php +{ + "name": "PHP", + + "remoteEnv": { + "SDK_ROOT": "/workspaces/php-sdk", + "XDEBUG_CONFIG": "log_level=0", + }, + + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/php:0-8.2", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + "postStartCommand": "composer install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 8080 + ], + "customizations": { + "vscode": { + "extensions": [ + "bmewburn.vscode-intelephense-client", + "xdebug.php-debug", + "DEVSENSE.composer-php-vscode" + ] + } + } + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/bug-bash/Decide.php b/bug-bash/Decide.php new file mode 100644 index 00000000..d68ebe1c --- /dev/null +++ b/bug-bash/Decide.php @@ -0,0 +1,144 @@ +'; + +// 2. Change this to your flag key +const FLAG_KEY = ''; + +// 3. Uncomment each scenario 1 by 1 modifying the contents of the method +// to test additional scenarios. + +$test = new DecideTests(); +$test->verifyDecisionProperties(); +// $test->testWithVariationsOfDecideOptions(); +// $test->verifyLogsImpressionsEventsDispatched(); +// $test->verifyResultsPageInYourProjectShowsImpressionEvent(); +// $test->verifyDecisionListenerWasCalled(); +// $test->verifyAnInvalidFlagKeyIsHandledCorrectly(); + +// 4. Change the current folder into the bug-bash directory +// cd bug-bash/ + +// 5. Run the following command to execute the uncommented tests above: +// php Decide.php + +// https://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-php +class DecideTests +{ + // verify decision return properties with default DecideOptions + public function verifyDecisionProperties(): void + { + $decision = $this->userContext->decide(FLAG_KEY); + + $this->printDecision($decision, "Check that the following decision properties are expected for user $this->userId"); + } + + // test decide w all options: DISABLE_DECISION_EVENT, ENABLED_FLAGS_ONLY, IGNORE_USER_PROFILE_SERVICE, INCLUDE_REASONS, EXCLUDE_VARIABLES (will need to add variables) + public function testWithVariationsOfDecideOptions(): void + { + $options = [ + OptimizelyDecideOption::INCLUDE_REASONS, + // OptimizelyDecideOption::DISABLE_DECISION_EVENT, + // OptimizelyDecideOption::ENABLED_FLAGS_ONLY, // ⬅️ Disable some of your flags + // OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE, + // OptimizelyDecideOption::EXCLUDE_VARIABLES, + ]; + + $decision = $this->userContext->decide(FLAG_KEY, $options); + + $this->printDecision($decision, 'Modify the OptimizelyDecideOptions and check the decision variables expected'); + } + + // verify in logs that impression event of this decision was dispatched + public function verifyLogsImpressionsEventsDispatched(): void + { + // 💡️ Create a new flag with an A/B Test eg "product_version" + $featureFlagKey = 'product_version'; + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + + // review the DEBUG output, ensuring you see an impression log + // "Dispatching impression event to URL https://logx.optimizely.com/v1/events with params..." + $localUserContext->decide($featureFlagKey); + } + + // verify on Results page that impression even was created + public function verifyResultsPageInYourProjectShowsImpressionEvent(): void + { + print "Go to your project's results page and verify decisions events are showing (5 min delay)"; + } + + // verify that decision listener contains correct information + public function verifyDecisionListenerWasCalled(): void + { + // Check that this was called during the... + $onDecision = function ($type, $userId, $attributes, $decisionInfo) { + print ">>> [$this->outputTag] OnDecision: + type: $type, + userId: $userId, + attributes: " . print_r($attributes, true) . " + decisionInfo: " . print_r($decisionInfo, true) . "\r\n"; + }; + $this->optimizelyClient->notificationCenter->addNotificationListener( + NotificationType::DECISION, + $onDecision + ); + + // ...decide. + $this->userContext->decide(FLAG_KEY); + } + + // verify that invalid flag key is handled correctly + public function verifyAnInvalidFlagKeyIsHandledCorrectly(): void + { + $logger = new DefaultLogger(Logger::ERROR); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $userId = 'user-' . mt_rand(10, 99); + $localUserContext = $localOptimizelyClient->createUserContext($userId); + + // ensure you see an error -- Optimizely.ERROR: FeatureFlag Key "a_key_not_in_the_project" is not in datafile. + $localUserContext->decide("a_key_not_in_the_project"); + } + + private Optimizely $optimizelyClient; + private string $userId; + private ?OptimizelyUserContext $userContext; + private string $outputTag = "Decide"; + + public function __construct() + { + $this->optimizelyClient = OptimizelyFactory::createDefaultInstance(SDK_KEY); + + $this->userId = 'user-' . mt_rand(10, 99); + $attributes = ['age' => 25, 'country' => 'canada', 'abandoned_cart' => false]; + $this->userContext = $this->optimizelyClient->createUserContext($this->userId, $attributes); + } + + private function printDecision($decision, $message): void + { + $enabled = $decision->getEnabled() ? "true" : "false"; + + print ">>> [$this->outputTag] $message: + enabled: $enabled, + flagKey: {$decision->getFlagKey()}, + ruleKey: {$decision->getRuleKey()}, + variationKey: {$decision->getVariationKey()}, + variables: " . print_r($decision->getVariables(), true) . ", + reasons: " . print_r($decision->getReasons(), true) . "\r\n"; + } +} diff --git a/bug-bash/DecideAll.php b/bug-bash/DecideAll.php new file mode 100644 index 00000000..92c3cffd --- /dev/null +++ b/bug-bash/DecideAll.php @@ -0,0 +1,134 @@ +'; + +// 2. Create additional flag keys in your project (2+) + +// 3. Uncomment each scenario 1 by 1 modifying the contents of the method +// to test additional scenarios. + +$test = new DecideAllTests(); +$test->verifyDecisionProperties(); +// $test->testWithVariousCombinationsOfOptions(); +// $test->verifyLogImpressionEventDispatched(); +// $test->verifyResultsPageShowsImpressionEvents(); +// $test->verifyDecisionListenerContainsCorrectInformation(); + +// 4. Change the current folder into the bug-bash directory if you're not already there: +// cd bug-bash/ + +// 5. Run the following command to execute the uncommented tests above: +// php DecideAll.php + +// https://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-php +class DecideAllTests +{ + // verify decide all returns properties without specifying default options + public function verifyDecisionProperties(): void + { + $decision = $this->userContext->decideAll(); + + $this->printDecisions($decision, "Check that all of the decisions' multiple properties are expected for user `$this->userId`"); + } + + // test with all and variations/combinations of options + public function testWithVariousCombinationsOfOptions(): void + { + $options = [ + OptimizelyDecideOption::INCLUDE_REASONS, + // OptimizelyDecideOption::DISABLE_DECISION_EVENT, + // OptimizelyDecideOption::ENABLED_FLAGS_ONLY, // ⬅️ Disable some of your flags + // OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE, + OptimizelyDecideOption::EXCLUDE_VARIABLES, + ]; + + $decisions = $this->userContext->decideAll($options); + + $this->printDecisions($decisions, "Check that all of your flags' decisions respected the options passed."); + } + + // verify in logs that impression event of this decision was dispatched + public function verifyLogImpressionEventDispatched(): void + { + // 💡️ Be sure you have >=1 of your project's flags has an EXPERIMENT type + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + + // review the DEBUG output, ensuring you see an impression log for each *EXPERIMENT* with a message like + // "Dispatching impression event to URL https://logx.optimizely.com/v1/events with params..." + // ⚠️ Rollout flag types should not dispatch and impression event + $localUserContext->decideAll(); + } + + // verify on Results page that impression events was created + public function verifyResultsPageShowsImpressionEvents(): void + { + print "After about 5-10 minutes, go to your project's results page and verify decisions events are showing."; + } + + // verify that decision listener contains correct information + public function verifyDecisionListenerContainsCorrectInformation(): void + { + // Check that this was called for each of your project flag keys + $onDecision = function ($type, $userId, $attributes, $decisionInfo) { + print ">>> [$this->outputTag] OnDecision: + type: $type, + userId: $userId, + attributes: " . print_r($attributes, true) . " + decisionInfo: " . print_r($decisionInfo, true) . "\r\n"; + }; + $this->optimizelyClient->notificationCenter->addNotificationListener( + NotificationType::DECISION, + $onDecision + ); + + $this->userContext->decideAll(); + } + + private Optimizely $optimizelyClient; + private string $userId; + private ?OptimizelyUserContext $userContext; + private string $outputTag = "Decide All"; + + public function __construct() + { + $this->optimizelyClient = OptimizelyFactory::createDefaultInstance(SDK_KEY); + + $this->userId = 'user-' . mt_rand(10, 99); + $attributes = ['country' => 'nederland', 'age' => 43, 'is_return_visitor' => true]; + $this->userContext = $this->optimizelyClient->createUserContext($this->userId, $attributes); + } + + private function printDecisions($decisions, $message): void + { + $count = 0; + foreach ($decisions as $decision) { + $enabled = $decision->getEnabled() ? "true" : "false"; + + print ">>> [$this->outputTag #$count] $message: + enabled: $enabled, + flagKey: {$decision->getFlagKey()}, + ruleKey: {$decision->getRuleKey()}, + variationKey: {$decision->getVariationKey()}, + variables: " . print_r($decision->getVariables(), true) . ", + reasons: " . print_r($decision->getReasons(), true) . "\r\n"; + + $count++; + } + } +} diff --git a/bug-bash/DecideForKeys.php b/bug-bash/DecideForKeys.php new file mode 100644 index 00000000..b059b49a --- /dev/null +++ b/bug-bash/DecideForKeys.php @@ -0,0 +1,150 @@ +'; + +// 2. Check that you have 3+ flag keys in your project and add them here +const FLAG_KEYS = ['', '', '']; + +// 3. Uncomment each scenario 1 by 1 modifying the contents of the method +// to test additional scenarios. + +$test = new DecideForKeysTests(); +$test->verifyDecisionProperties(); +// $test->testWithVariationsOfDecideOptions(); +// $test->verifyLogsImpressionsEventsDispatched(); +// $test->verifyResultsPageInYourProjectShowsImpressionEvent(); +// $test->verifyDecisionListenerWasCalled(); +// $test->verifyAnInvalidFlagKeyIsHandledCorrectly(); + +// 4. Change the current folder into the bug-bash directory if you've not already +// cd bug-bash/ + +// 5. Run the following command to execute the uncommented tests above: +// php DecideForKeys.php + +// https://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-php +class DecideForKeysTests +{ + + // verify decision return properties with default DecideOptions + public function verifyDecisionProperties(): void + { + $decision = $this->userContext->decideForKeys(FLAG_KEYS); + + $this->printDecisions($decision, "Check that the following decisions' properties are expected"); + } + + // test decide w all options: DISABLE_DECISION_EVENT, ENABLED_FLAGS_ONLY, IGNORE_USER_PROFILE_SERVICE, INCLUDE_REASONS, EXCLUDE_VARIABLES (will need to add variables) + public function testWithVariationsOfDecideOptions(): void + { + $options = [ + OptimizelyDecideOption::INCLUDE_REASONS, + // OptimizelyDecideOption::DISABLE_DECISION_EVENT, + // OptimizelyDecideOption::ENABLED_FLAGS_ONLY, // ⬅️ Disable some of your flags + // OptimizelyDecideOption::IGNORE_USER_PROFILE_SERVICE, + // OptimizelyDecideOption::EXCLUDE_VARIABLES, + ]; + + $decision = $this->userContext->decideForKeys(FLAG_KEYS, $options); + + $this->printDecisions($decision, "Modify the OptimizelyDecideOptions and check all the decisions' are as expected"); + } + + // verify in logs that impression event of this decision was dispatched + public function verifyLogsImpressionsEventsDispatched(): void + { + // 💡️ Be sure that your FLAG_KEYS array above includes A/B Test eg "product_version" + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + + // review the DEBUG output, ensuring you see an impression log for each experiment type in your FLAG_KEYS + // "Dispatching impression event to URL https://logx.optimizely.com/v1/events with params..." + // ⚠️ Your Rollout type flags should not have impression events + $localUserContext->decideForKeys(FLAG_KEYS); + } + + // verify on Results page that impression even was created + public function verifyResultsPageInYourProjectShowsImpressionEvent(): void + { + print "Go to your project's results page and verify decisions events are showing (5 min delay)"; + } + + // verify that decision listener contains correct information + public function verifyDecisionListenerWasCalled(): void + { + // Check that this was called during the... + $onDecision = function ($type, $userId, $attributes, $decisionInfo) { + print ">>> [$this->outputTag] OnDecision: + type: $type, + userId: $userId, + attributes: " . print_r($attributes, true) . " + decisionInfo: " . print_r($decisionInfo, true) . "\r\n"; + }; + $this->optimizelyClient->notificationCenter->addNotificationListener( + NotificationType::DECISION, + $onDecision + ); + + // ...decide. + $this->userContext->decide(FLAG_KEY); + } + + // verify that invalid flag key is handled correctly + public function verifyAnInvalidFlagKeyIsHandledCorrectly(): void + { + $logger = new DefaultLogger(Logger::ERROR); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $userId = 'user-' . mt_rand(10, 99); + $localUserContext = $localOptimizelyClient->createUserContext($userId); + + // ensure you see an error -- Optimizely.ERROR: FeatureFlag Key "a_key_not_in_the_project" is not in datafile. + $localUserContext->decide("a_key_not_in_the_project"); + } + + private Optimizely $optimizelyClient; + private string $userId; + private ?OptimizelyUserContext $userContext; + private string $outputTag = "Decide For Keys"; + + public function __construct() + { + $this->optimizelyClient = OptimizelyFactory::createDefaultInstance(SDK_KEY); + + $this->userId = 'user-' . mt_rand(10, 99); + $attributes = ['likes_yams' => true, 'cart_value' => 34.13, 'country' => 'sweden']; + $this->userContext = $this->optimizelyClient->createUserContext($this->userId, $attributes); + } + + private function printDecisions($decisions, $message): void + { + $count = 0; + foreach ($decisions as $decision) { + $enabled = $decision->getEnabled() ? "true" : "false"; + + print ">>> [$this->outputTag #$count] $message: + enabled: $enabled, + flagKey: {$decision->getFlagKey()}, + ruleKey: {$decision->getRuleKey()}, + variationKey: {$decision->getVariationKey()}, + variables: " . print_r($decision->getVariables(), true) . ", + reasons: " . print_r($decision->getReasons(), true) . "\r\n"; + + $count++; + } + } +} diff --git a/bug-bash/ForcedDecision.php b/bug-bash/ForcedDecision.php new file mode 100644 index 00000000..13b7251b --- /dev/null +++ b/bug-bash/ForcedDecision.php @@ -0,0 +1,323 @@ +"); + + +$userId = 'user' . strval(rand(0, 1000001)); + +echo '==================================='; +echo 'F-to-D (no rule key specified):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, age = 20 (bucketed)'.PHP_EOL; +echo ' Call decide with flag1 ---> expected result is variation a'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$user = $optimizelyClient->createUserContext($userId, array("age"=> 20)); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert('variation_a' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision w flag1 and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', null); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision with flag1 and variation c (invalid)' . PHP_EOL; +echo ' Call decide ----> expected variation a' . PHP_EOL; +echo ' ---------------------' . PHP_EOL; +$context = new OptimizelyDecisionContext('flag1', null); +$decision = new OptimizelyForcedDecision('variation_c'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_a' == $decideDecision->getVariationKey()); + +// E-to-D (rule key = “flag1_experiment”): +// ------------------------------------------- +// Set user context, userId any, age = 20 (bucketed) +// Call decide with flag1 ---> expected result is variation a in the decide decision +// Set forced decision w flag1 and rule key “flag1_experiment”, and variation b +// Call decide -----> expected variation b in decide decision +// Set forced decision with flag1 and rule key “flag1_experiment” and invalid variation c +// Call decide ----> expected variation a + +echo PHP_EOL . PHP_EOL . '==================================='; +echo 'E-to-D (rule key = “flag1_experiment”):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, age = 20 (bucketed)'.PHP_EOL; +echo ' Call decide with flag1 ---> expected result is variation a'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_a' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision with flag1 and rule flag1_experiment and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation_b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_experiment'); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision with flag1 and rule flag1_experiment and variation c (Invalid)' . PHP_EOL; +echo ' Call decide -----> expected variation a in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_experiment'); +$decision = new OptimizelyForcedDecision('variation_c'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_a' == $decideDecision->getVariationKey()); + +// D-to-D (rule key = “flag1_targeted_delivery”): +// ------------------------------------------- +// Set user context, userId any, country = “US” (bucketed) +// Call decide with flag1 ---> expected result is “on” in the decide decision +// Set forced decision w flag1 and rule key “flag1_targeted_delivery”, and variation b +// Call decide -----> expected variation b in decide decision +// Set forced decision with flag1 and rule key “flag1_targeted_delivery” and invalid variation c +// Call decide ----> expected “on” + +echo PHP_EOL . PHP_EOL . '==================================='; +echo 'D-to-D (rule key = “flag1_targeted_delivery”):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, country = US (bucketed)'.PHP_EOL; +echo ' Call decide with flag1 ---> expected result is on on'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$user = $optimizelyClient->createUserContext($userId, array("country"=> "US")); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('on' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision with flag1 and rule “flag1_targeted_delivery” and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation_b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_targeted_delivery'); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision with flag1 and rule flag1_targeted_delivery and variation c (Invalid)' . PHP_EOL; +echo ' Call decide -----> expected on in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_targeted_delivery'); +$decision = new OptimizelyForcedDecision('variation_c'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('on' == $decideDecision->getVariationKey()); + + +// ================================ +// PART 2 b) Repeat above three blocks, this time user DOES NOT meet audience conditions +// ================================ + +// F-to-D (no rule key specified): +// ------------------------------------------- +// Set user context, userId any, age = 0 (not bucketed) +// Call decide with flag1 ---> expected result is “off” (everyone else) +// Set forced decision w flag1, variation b +// Call decide -----> expected variation b in decide decision + +echo PHP_EOL . PHP_EOL . '==================================='; +echo 'F-to-D (no rule key specified):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, age = 0 (not bucketed)'.PHP_EOL; +echo ' Call decide with flag1 ---> expected result is off'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$user = $optimizelyClient->createUserContext($userId, array("age"=> 0)); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert('off' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision w flag1 and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', null); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + + +// E-to-D (rule key = “flag1_experiment”): +// ------------------------------------------- +// Set user context, userId any, age = 0 ( not bucketed) +// Call decide with flag1 ---> expected result is “off” +// Set forced decision w flag1 and rule key “flag1_experiment”, and variation b +// Call decide -----> expected variation b in decide decision + +echo PHP_EOL . PHP_EOL . '==================================='; +echo 'E-to-D (rule key = “flag1_experiment”):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, age = 0 (not bucketed)'.PHP_EOL; +echo ' Call decide with flag1 ---> expected result is off'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$user = $optimizelyClient->createUserContext($userId, array("age"=> 0)); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert('off' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision w flag1 and rule flag1_experiment and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_experiment'); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + + +// D-to-D (rule key = “flag1_targeted_delivery”): +// ------------------------------------------- +// Set user context, userId any, country = “MX” (not bucketed) +// Call decide with flag1 ---> expected result is “off” +// Set forced decision w flag1 and rule key “flag1_targeted_delivery”, and variation b +// Call decide -----> expected variation b in decide decision + + +echo PHP_EOL . PHP_EOL . '==================================='; +echo 'D-to-D (rule key = flag1_targeted_delivery):'; +echo '==================================='.PHP_EOL; + +echo ' Set user context, userId any, country = MX (not bucketed)'.PHP_EOL; +echo ' Call decide with flag1 and rule flag1_targeted_delivery ---> expected result is off'.PHP_EOL; +echo ' ---------------------'.PHP_EOL; + +$user = $optimizelyClient->createUserContext($userId, array("country"=> "MX")); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert('off' == $decideDecision->getVariationKey()); + +echo PHP_EOL . ' Set forced decision w flag1 and rule flag1_targeted_delivery and variation b' . PHP_EOL; +echo ' Call decide -----> expected variation b in decide decision' . PHP_EOL; +echo ' ---------------------'; + +$context = new OptimizelyDecisionContext('flag1', 'flag1_targeted_delivery'); +$decision = new OptimizelyForcedDecision('variation_b'); +$user->setForcedDecision($context, $decision); +$decideDecision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); +echo ' VARIATION >>> ' . $decideDecision->getVariationKey() . PHP_EOL; +// ASSERT YOU GET CORRECT VARIATION +echo ' REASONS >>> ' . json_encode($decideDecision->getReasons()) . PHP_EOL; +// VERIFY REASONS ARE CORRECT +assert ('variation_b' == $decideDecision->getVariationKey()); + +// ================================ +// Part 3 +// ================================ +$user = $optimizelyClient->createUserContext($userId); + +$user->setForcedDecision(new OptimizelyDecisionContext('F1', null), + new OptimizelyForcedDecision('V1')); +$user->setForcedDecision(new OptimizelyDecisionContext('F1', 'E1'), + new OptimizelyForcedDecision('V3')); + +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', null)) == 'V1'); +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', 'E1')) == 'V3'); +$user->setForcedDecision(new OptimizelyDecisionContext('F1', null), + new OptimizelyForcedDecision('V5')); +$user->setForcedDecision(new OptimizelyDecisionContext('F1', 'E1'), + new OptimizelyForcedDecision('V5')); + +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', null)) == 'V5'); +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', 'E1')) == 'V5'); +$user->removeForcedDecision(new OptimizelyDecisionContext('F1', null)); +echo $user->getForcedDecision(new OptimizelyDecisionContext('F1', null)) == null; +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', null)) == null); +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', 'E1')) == 'V5'); + +$user->removeForcedDecision(new OptimizelyDecisionContext('F1', 'E1')); + +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', null)) == null); +assert($user->getForcedDecision(new OptimizelyDecisionContext('F1', 'E1')) == null); + +$user->removeAllForcedDecisions(); + +assert($user->getForcedDecision(new OptimizelyDecisionContext('F2', null)) == null); +assert($user->getForcedDecision(new OptimizelyDecisionContext('F2', 'E1')) == null); + + + +// ?> diff --git a/bug-bash/OptiConfig.php b/bug-bash/OptiConfig.php new file mode 100644 index 00000000..a2b76439 --- /dev/null +++ b/bug-bash/OptiConfig.php @@ -0,0 +1,116 @@ +"; +// $optimizelyClient = new Optimizely($sdkKey); +$optimizelyClient = new Optimizely(null, null, null, null, false, null, null, null, $sdkKey); +$user = $optimizelyClient->createUserContext('user123', ['attribute1' => 'hello']); +$decision = $user->decide('flag1', [OptimizelyDecideOption::INCLUDE_REASONS]); + +$reasons = $decision->getReasons(); +echo "[OptimizelyConfig] reasons: " . json_encode($reasons) . PHP_EOL; +echo "[OptimizelyConfig - flag key]: " . $decision->getFlagKey() . PHP_EOL; +echo "[OptimizelyConfig - rule key]: " . $decision->getFlagKey() . PHP_EOL; +echo "[OptimizelyConfig - enabled]: " . $decision->getEnabled() . PHP_EOL; +echo "[OptimizelyConfig - variation key]: " . $decision->getVariationKey() . PHP_EOL; +$variables = $decision->getVariables(); +echo "[OptimizelyConfig - variables]: " . json_encode($variables) . PHP_EOL; +echo PHP_EOL; + +$user->trackEvent('myevent'); + +echo "===========================" . PHP_EOL; +echo " OPTIMIZELY CONFIG V2 " . PHP_EOL; +echo "===========================" . PHP_EOL . PHP_EOL; + +$config = $optimizelyClient->getOptimizelyConfig(); +// get the revision +echo "[OptimizelyConfig] revision:" . $config->getRevision() . PHP_EOL; + +// get the SDK key +echo "[OptimizelyConfig] SDKKey:" . $config->getSdkKey() . PHP_EOL; + +// get the environment key +echo "[OptimizelyConfig] environmentKey:" . $config->getEnvironmentKey() . PHP_EOL; + +// all attributes +echo "[OptimizelyConfig] attributes:" . PHP_EOL; +$attributes = $config->getAttributes(); +foreach($attributes as $attribute) +{ + echo "[OptimizelyAttribute] -- (id, key) = ((" . $attribute->getId(). "), (". $attribute->getKey() . "))" . PHP_EOL; +} + +// all audiences +echo "[OptimizelyConfig] audiences:" . PHP_EOL; +$audiences = $config->getAudiences(); +foreach($audiences as $audience) +{ + echo "[OptimizelyAudience] -- (id, key, conditions) = ((" . $audience->getId(). "), (". $audience->getName() . "), (". $audience->getConditions() . "))" . PHP_EOL; +} + +// all events +echo "[OptimizelyConfig] events:" . PHP_EOL; +$events = $config->getEvents(); +foreach($events as $event) +{ + echo "[OptimizelyEvent] -- (id, key, experimentIds) = ((" . $event->getId(). "), (". $event->getKey() . "), (". $event->getExperimentIds() . "))" . PHP_EOL; +} + +// all flags +$flags = array_values((array)$config->getFeaturesMap()); +foreach ($flags as $flag) +{ + // Use experimentRules and deliverRules + $experimentRules = $flag->getExperimentRules(); + echo "------ Experiment rules -----" . PHP_EOL; + foreach ($experimentRules as $experimentRule) + { + echo "---" . PHP_EOL; + echo "[OptimizelyExperiment] - experiment rule-key = " . $experimentRule->getKey() . PHP_EOL; + echo "[OptimizelyExperiment] - experiment audiences = " . PHP_EOL;$experimentRule->getExperimentAudiences(); + // all variations + $variations = array_values((array)$experimentRule->getVariationsMap()); + foreach ($variations as $variation) + { + echo "[OptimizelyVariation] -- variation = { key: " . $variation->getKey() . ", . id: " . $variation->getId() . ", featureEnabled: " . $variation->getFeatureEnabled() . " }" . PHP_EOL; + $variables = $variation->getVariablesMap(); + foreach ($variables as $variable) + { + echo "[OptimizelyVariable] --- variable: " . $variable->getKey() . ", " . $variable->getId() . PHP_EOL; + // use variable data here. + } + // use experimentRule data here. + } + } + $deliveryRules = $flag->getDeliveryRules(); + echo "------ Delivery rules -----" . PHP_EOL; + foreach ($deliveryRules as $deliveryRule) + { + echo "---"; + echo "[OptimizelyExperiment] - delivery rule-key = " . $deliveryRule->getKey() . PHP_EOL; + echo "[OptimizelyExperiment] - delivery audiences = " . $deliveryRule->getExperimentAudiences() . PHP_EOL; + + // use delivery rule data here... + } +} +// $optimizelyClient->notificationCenter->addNotificationListener( +// NotificationType::OPTIMIZELY_CONFIG_UPDATE, +// function () { +// $newConfig = $optimizelyClient->getOptimizelyConfig(); +// echo "[OptimizelyConfig] revision = " . $newConfig ? $newConfig->getRevision() : "" . PHP_EOL; + // } +// ); + diff --git a/bug-bash/TrackEvent.php b/bug-bash/TrackEvent.php new file mode 100644 index 00000000..e77bb1e5 --- /dev/null +++ b/bug-bash/TrackEvent.php @@ -0,0 +1,118 @@ +'; + +// 2. Add an event to your project, adding it to your Experiment flag as a metric, then set the key here +const EVENT_KEY = ''; + +// 3. Uncomment each scenario 1 by 1 modifying the contents of the method +// to test additional scenarios. + +$test = new TrackEventTests(); +$test->checkTrackNotificationListenerProducesEvent(); +// $test->checkConversionEventLogDispatchedOnTrackEvent(); +// $test->checkConversionEventLogIsNOTDispatchedOnTrackEventForInvalidEventName(); +// $test->testEventTagsShowInDispatchedEventAndAppOptimizelyCom(); + +// 4. Change the current folder into the bug-bash directory if you've not already +// cd bug-bash/ + +// 5. Run the following command to execute the uncommented tests above: +// php TrackEvent.php + +// https://docs.developers.optimizely.com/feature-experimentation/docs/track-event-php +class TrackEventTests +{ + // check that track notification listener produces event with event key + public function checkTrackNotificationListenerProducesEvent(): void + { + $this->optimizelyClient->notificationCenter->addNotificationListener( + NotificationType::TRACK, + $this->onTrackEvent // ⬅️ This should be called with a valid EVENT_NAME + ); + + // ...send track event. + $this->userContext->trackEvent(EVENT_KEY); + } + + // check that conversion event in the dispatch logs contains event key below + public function checkConversionEventLogDispatchedOnTrackEvent(): void + { + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + + $localUserContext->trackEvent(EVENT_KEY); + } + + // check that event is NOT dispatched if invalid event key is used + // test changing event key in the UI and in the code + public function checkConversionEventLogIsNOTDispatchedOnTrackEventForInvalidEventName(): void + { + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + $this->optimizelyClient->notificationCenter->addNotificationListener( + NotificationType::TRACK, + $this->onTrackEvent // ⬅️ There should not be a Notification Listener OnTrackEvent called on invalid event name + ); + + // You should not see any "Optimizely.DEBUG: Dispatching conversion event" but instead see + // "Optimizely.INFO: Not tracking user "{user-id}" for event "an_invalid_event_name_not_in_the_project". + $localUserContext->trackEvent("an_invalid_event_name_not_in_the_project"); + } + + // try adding event tags (in the project and in the line below) and see if they show in the event body + public function testEventTagsShowInDispatchedEventAndAppOptimizelyCom(): void + { + $logger = new DefaultLogger(Logger::DEBUG); + $localOptimizelyClient = new Optimizely(datafile: null, logger: $logger, sdkKey: SDK_KEY); + $localUserContext = $localOptimizelyClient->createUserContext($this->userId); + $custom_tags = [ + 'shoe_size_paris_points' => 44, + 'shoe_size_us_size' => 11.5, + 'use_us_size' => false, + 'color' => 'blue' + ]; + + // Dispatched event should have the tags added to the payload `params { ... }` and also + // should show on app.optimizely.com Reports tab after 5-10 minutes + $localUserContext->trackEvent(EVENT_KEY, $custom_tags); + } + + private Optimizely $optimizelyClient; + private string $userId; + private ?OptimizelyUserContext $userContext; + private string $outputTag = "Track Event"; + private \Closure $onTrackEvent; + + public function __construct() + { + $this->optimizelyClient = OptimizelyFactory::createDefaultInstance(SDK_KEY); + + $this->userId = 'user-' . mt_rand(10, 99); + $attributes = ['age' => 19, 'country' => 'bangledesh', 'has_purchased' => true]; + $this->userContext = $this->optimizelyClient->createUserContext($this->userId, $attributes); + + $this->onTrackEvent = function ($type, $userId, $attributes, $decisionInfo) { + print ">>> [$this->outputTag] OnTrackEvent: + type: $type, + userId: $userId, + attributes: " . print_r($attributes, true) . " + decisionInfo: " . print_r($decisionInfo, true) . "\r\n"; + }; + } +} diff --git a/bug-bash/_bug-bash-autoload.php b/bug-bash/_bug-bash-autoload.php new file mode 100644 index 00000000..2d7d7424 --- /dev/null +++ b/bug-bash/_bug-bash-autoload.php @@ -0,0 +1,15 @@ + Date: Wed, 9 Aug 2023 10:47:10 -0400 Subject: [PATCH 2/6] [FSSDK-9553] Add GitHub Issues templates (#278) * Clean devcontainer config * Add Issues templates * Made PR suggested edits --- .devcontainer/devcontainer.json | 18 +---- .github/ISSUE_TEMPLATE/BUG-REPORT.yml | 88 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/ENHANCEMENT.yml | 45 ++++++++++++ .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md | 4 ++ .github/ISSUE_TEMPLATE/config.yml | 5 ++ 5 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/BUG-REPORT.yml create mode 100644 .github/ISSUE_TEMPLATE/ENHANCEMENT.yml create mode 100644 .github/ISSUE_TEMPLATE/FEATURE-REQUEST.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7316d069..8730012b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,25 +1,15 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/php { - "name": "PHP", + "name": "PHP SDK", "remoteEnv": { "SDK_ROOT": "/workspaces/php-sdk", "XDEBUG_CONFIG": "log_level=0", }, - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "image": "mcr.microsoft.com/devcontainers/php:0-8.2", - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, "postStartCommand": "composer install", - // Configure tool-specific properties. - // "customizations": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. "forwardPorts": [ 8080 ], @@ -32,10 +22,4 @@ ] } } - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html" - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" } diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 00000000..c6ce8dae --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,88 @@ +name: 🐞 Bug +description: File a bug/issue +title: "[BUG] " +labels: ["bug", "needs-triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: SDK Version + description: Version of the SDK in use? + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 1. With this config... + 1. Run '...' + 1. See error... + validations: + required: true +- type: textarea + attributes: + label: PHP Version + description: What version of PHP are you using? + validations: + required: false +- type: textarea + attributes: + label: Link + description: Link to code demonstrating the problem. + validations: + required: false +- type: textarea + attributes: + label: Logs + description: Logs/stack traces related to the problem (⚠️do not include sensitive information). + validations: + required: false +- type: dropdown + attributes: + label: Severity + description: What is the severity of the problem? + multiple: true + options: + - Blocking development + - Affecting users + - Minor issue + validations: + required: false +- type: textarea + attributes: + label: Workaround/Solution + description: Do you have any workaround or solution in mind for the problem? + validations: + required: false +- type: textarea + attributes: + label: Recent Change + description: Has this issue started happening after an update or experiment change? + validations: + required: false +- type: textarea + attributes: + label: Conflicts + description: Are there other libraries/dependencies potentially in conflict? + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml new file mode 100644 index 00000000..a4a39434 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml @@ -0,0 +1,45 @@ +name: ✨Enhancement +description: Create a new ticket for a Enhancement/Tech-initiative for the benefit of the SDK which would be considered for a minor version update. +title: "[ENHANCEMENT] <title>" +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: Description + description: Briefly describe the enhancement in a few sentences. + placeholder: Short description... + validations: + required: true + - type: textarea + id: benefits + attributes: + label: Benefits + description: How would the enhancement benefit to your product or usage? + placeholder: Benefits... + validations: + required: true + - type: textarea + id: detail + attributes: + label: Detail + description: How would you like the enhancement to work? Please provide as much detail as possible + placeholder: Detailed description... + validations: + required: false + - type: textarea + id: examples + attributes: + label: Examples + description: Are there any examples of this enhancement in other products/services? If so, please provide links or references. + placeholder: Links/References... + validations: + required: false + - type: textarea + id: risks + attributes: + label: Risks/Downsides + description: Do you think this enhancement could have any potential downsides or risks? + placeholder: Risks/Downsides... + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md new file mode 100644 index 00000000..a061f335 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md @@ -0,0 +1,4 @@ +<!-- + Thanks for filing in issue! Are you requesting a new feature? If so, please share your feedback with us on the following link. +--> +## Feedback requesting a new feature can be shared [here.](https://feedback.optimizely.com/) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..17de7159 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💡Feature Requests + url: https://feedback.optimizely.com/ + about: Feedback requesting a new feature can be shared here. From 3fd73a279d2705714ca545f37cfe13805027dea5 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:28:00 -0400 Subject: [PATCH 3/6] [FSSDK-9573] Fix: Deprecation warning (#279) * Add PHP pack ext * Add missing field definition * Add missing field definition * Add phpunit test runner ext * Change to use consistent _privateFieldNaming * Add gitlens * Fix rename Didn't have a proper refactor ext/function * Update copyright header * Refactor CI slightly * Add back EOL for file --- .devcontainer/devcontainer.json | 5 +++- .github/workflows/php.yml | 25 +++++++++++-------- .../HTTPProjectConfigManager.php | 21 ++++++++++------ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8730012b..81a3f58d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,7 +18,10 @@ "extensions": [ "bmewburn.vscode-intelephense-client", "xdebug.php-debug", - "DEVSENSE.composer-php-vscode" + "DEVSENSE.composer-php-vscode", + "xdebug.php-pack", + "recca0120.vscode-phpunit", + "eamodio.gitlens" ] } } diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 574c3a53..109abb8a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,7 +11,8 @@ jobs: name: Linting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - name: Set up PHP uses: shivammathur/setup-php@v2 with: @@ -25,28 +26,24 @@ jobs: name: Source Clear Scan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v3 - name: Source clear scan env: SRCCLR_API_TOKEN: ${{ secrets.SRCCLR_API_TOKEN }} run: curl -sSL https://download.sourceclear.com/ci.sh | bash -s – scan - integration_tests: - name: Integration Tests - uses: optimizely/php-sdk/.github/workflows/integration_test.yml@master - secrets: - CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} - unit_tests: name: Unit Tests ${{ matrix.php-versions }} + needs: [ linting, source_clear ] runs-on: ubuntu-latest strategy: fail-fast: false matrix: php-versions: [ '8.1', '8.2' ] steps: - - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - name: Set up PHP v${{ matrix.php-versions }} uses: shivammathur/setup-php@v2 with: @@ -77,3 +74,11 @@ jobs: run: | composer global require php-coveralls/php-coveralls php-coveralls --coverage_clover=./build/logs/clover.xml -v + + integration_tests: + name: Integration Tests + needs: [ unit_tests ] + uses: optimizely/php-sdk/.github/workflows/integration_test.yml@master + secrets: + CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} + TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} diff --git a/src/Optimizely/ProjectConfigManager/HTTPProjectConfigManager.php b/src/Optimizely/ProjectConfigManager/HTTPProjectConfigManager.php index 7b962aee..83e5f839 100644 --- a/src/Optimizely/ProjectConfigManager/HTTPProjectConfigManager.php +++ b/src/Optimizely/ProjectConfigManager/HTTPProjectConfigManager.php @@ -1,12 +1,12 @@ <?php /** - * Copyright 2019-2020, 2022 Optimizely Inc and Contributors + * Copyright 2019-2020, 2022-2023 Optimizely Inc and Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -75,7 +75,12 @@ class HTTPProjectConfigManager implements ProjectConfigManagerInterface /** * @var String datafile access token. */ - private $datafileAccessToken; + private $_datafileAccessToken; + + /** + * @var boolean Flag indicates that the datafile access token is valid. + */ + private $_isDatafileAccessTokenValid; public function __construct( $sdkKey = null, @@ -93,8 +98,8 @@ public function __construct( $this->_logger = $logger ?: new NoOpLogger(); $this->_errorHandler = $errorHandler ?: new NoOpErrorHandler(); $this->_notificationCenter = $notificationCenter ?: new NotificationCenter($this->_logger, $this->_errorHandler); - $this->datafileAccessToken = $datafileAccessToken; - $this->isDatafileAccessTokenValid = Validator::validateNonEmptyString($this->datafileAccessToken); + $this->_datafileAccessToken = $datafileAccessToken; + $this->_isDatafileAccessTokenValid = Validator::validateNonEmptyString($this->_datafileAccessToken); $this->httpClient = new HttpClient(); $this->_url = $this->getUrl($sdkKey, $url, $urlTemplate); @@ -136,7 +141,7 @@ protected function getUrl($sdkKey, $url, $urlTemplate) } if (!Validator::validateNonEmptyString($urlTemplate)) { - if ($this->isDatafileAccessTokenValid) { + if ($this->_isDatafileAccessTokenValid) { $urlTemplate = ProjectConfigManagerConstants::AUTHENTICATED_DATAFILE_URL_TEMPLATE; } else { $urlTemplate = ProjectConfigManagerConstants::DEFAULT_DATAFILE_URL_TEMPLATE; @@ -179,8 +184,8 @@ protected function fetchDatafile() } // Add Authorization header if access token available. - if ($this->isDatafileAccessTokenValid) { - $headers['Authorization'] = "Bearer {$this->datafileAccessToken}"; + if ($this->_isDatafileAccessTokenValid) { + $headers['Authorization'] = "Bearer {$this->_datafileAccessToken}"; } $options = [ From 4963820f2589f63ddebd435ca88097d531ef115e Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:41:48 -0500 Subject: [PATCH 4/6] [FSSDK-9784] Return Latest Experiment When Duplicate Keys in Config (#280) * feat: log duplicate experiment keys includes some linting * test: updated existing dupe exp keys test --- .../OptimizelyConfigService.php | 37 ++++++++++++++----- .../OptimizelyConfigServiceTest.php | 34 +++++++++-------- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index d2dadb7c..2d29698a 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -1,12 +1,12 @@ <?php /** - * Copyright 2020-2021, Optimizely Inc and Contributors + * Copyright 2020-2021, 2023 Optimizely Inc and Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,9 +16,12 @@ */ namespace Optimizely\OptimizelyConfig; +use Monolog\Logger; use Optimizely\Config\ProjectConfigInterface; use Optimizely\Entity\Experiment; use Optimizely\Entity\Variation; +use Optimizely\Logger\DefaultLogger; +use Optimizely\Logger\LoggerInterface; class OptimizelyConfigService { @@ -73,7 +76,14 @@ class OptimizelyConfigService */ private $featKeyOptlyVariableIdVariableMap; - public function __construct(ProjectConfigInterface $projectConfig) + /** + * Provided or default logger for logging. + * + * @var LoggerInterface $logger + */ + private readonly LoggerInterface $logger; + + public function __construct(ProjectConfigInterface $projectConfig, LoggerInterface $logger = null) { $this->experiments = $projectConfig->getAllExperiments(); $this->featureFlags = $projectConfig->getFeatureFlags(); @@ -82,7 +92,8 @@ public function __construct(ProjectConfigInterface $projectConfig) $this->environmentKey = $projectConfig->getEnvironmentKey(); $this->sdkKey = $projectConfig->getSdkKey(); $this->projectConfig = $projectConfig; - + $this->logger = $logger ?: new DefaultLogger(); + $this->createLookupMaps(); } @@ -258,7 +269,7 @@ protected function getVariablesMap(Experiment $experiment, Variation $variation) // Set default variables for variation. $variablesMap = $this->featKeyOptlyVariableKeyVariableMap[$featureKey]; - + // Return default variable values if feature is not enabled. if (!$variation->getFeatureEnabled()) { return $variablesMap; @@ -267,13 +278,13 @@ protected function getVariablesMap(Experiment $experiment, Variation $variation) // Set variation specific value if any. foreach ($variation->getVariables() as $variableUsage) { $id = $variableUsage->getId(); - + $optVariable = $this->featKeyOptlyVariableIdVariableMap[$featureKey][$id]; - + $key = $optVariable->getKey(); $value = $variableUsage->getValue(); $type = $optVariable->getType(); - + $modifiedOptVariable = new OptimizelyVariable( $id, $key, @@ -287,7 +298,7 @@ protected function getVariablesMap(Experiment $experiment, Variation $variation) return $variablesMap; } - + /** * Generates Variations map for the given Experiment. * @@ -301,7 +312,7 @@ protected function getVariationsMap(Experiment $experiment) foreach ($experiment->getVariations() as $variation) { $variablesMap = $this->getVariablesMap($experiment, $variation); - + $variationKey = $variation->getKey(); $optVariation = new OptimizelyVariation( $variation->getId(), @@ -401,11 +412,17 @@ protected function getExperimentsMaps() foreach ($this->experiments as $exp) { $expId = $exp->getId(); $expKey = $exp->getKey(); + $audiences = ''; if ($exp->getAudienceConditions() != null) { $audienceConditions = $exp->getAudienceConditions(); $audiences = $this->getSerializedAudiences($audienceConditions); } + + if (array_key_exists($expKey, $experimentsKeyMap)) { + $this->logger->log(Logger::WARNING, sprintf('Duplicate experiment keys found in datafile: %s', $expKey)); + } + $optExp = new OptimizelyExperiment( $expId, $expKey, diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index 730ebff4..2a5cf5cf 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -16,9 +16,10 @@ */ namespace Optimizely\Tests; -use Exception; +use Monolog\Logger; use Optimizely\Config\DatafileProjectConfig; use Optimizely\ErrorHandler\NoOpErrorHandler; +use Optimizely\Logger\DefaultLogger; use Optimizely\Logger\NoOpLogger; use Optimizely\OptimizelyConfig\OptimizelyAttribute; use Optimizely\OptimizelyConfig\OptimizelyAudience; @@ -29,10 +30,12 @@ use Optimizely\OptimizelyConfig\OptimizelyFeature; use Optimizely\OptimizelyConfig\OptimizelyVariable; use Optimizely\OptimizelyConfig\OptimizelyVariation; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class OptimizelyConfigServiceTest extends TestCase { + private MockObject $loggerMock; protected function setUp() : void { @@ -149,6 +152,9 @@ protected function setUp() : void $this->expectedExpIdMap['17301270474'] = $abExperiment; $this->expectedExpIdMap['17258450439'] = $groupExperiment; $this->expectedExpIdMap['17279300791'] = $featExperiment; + + // Mock Logger + $this->loggerMock = $this->getMockBuilder(DefaultLogger::class)->getMock(); } protected static function getMethod($name) @@ -203,28 +209,26 @@ public function testGetVariationsMap() public function testGetOptimizelyConfigWithDuplicateExperimentKeys() { + $duplicatedExperimentKey = 'targeted_delivery'; + $secondDuplicatedExperimentId = '9300000007573'; + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::WARNING, + sprintf('Duplicate experiment keys found in datafile: %s', $duplicatedExperimentKey) + ); + $this->datafile = DATAFILE_FOR_DUPLICATE_EXP_KEYS; $this->projectConfig = new DatafileProjectConfig( $this->datafile, new NoOpLogger(), new NoOpErrorHandler() ); - $this->optConfigService = new OptimizelyConfigService($this->projectConfig); + $this->optConfigService = new OptimizelyConfigService($this->projectConfig, $this->loggerMock); $optimizelyConfig = $this->optConfigService->getConfig(); - $this->assertEquals(Count($optimizelyConfig->getExperimentsMap()), 1); - $experimentRulesFlag1 = $optimizelyConfig->getFeaturesMap()['flag1']->getExperimentRules(); // 9300000007569 - $experimentRulesFlag2 = $optimizelyConfig->getFeaturesMap()['flag2']->getExperimentRules(); // 9300000007573 - foreach ($experimentRulesFlag1 as $experimentRule) { - if ($experimentRule->getKey() == 'targeted_delivery') { - $this->assertEquals($experimentRule->getId(), '9300000007569'); - } - } - foreach ($experimentRulesFlag2 as $experimentRule) { - if ($experimentRule->getKey() == 'targeted_delivery') { - $this->assertEquals($experimentRule->getId(), '9300000007573'); - } - } + $this->assertEquals(1, Count($optimizelyConfig->getExperimentsMap())); + $this->assertEquals($optimizelyConfig->getExperimentsMap()[$duplicatedExperimentKey]->getId(), $secondDuplicatedExperimentId); } public function testGetOptimizelyConfigWithDuplicateRuleKeys() From 8fe857448f73ac444f09ea50fc872017b17a7d7e Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:34:56 -0500 Subject: [PATCH 5/6] [FSSDK-9630] Fix: Code examples (#281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Add ReturnTypeWillChange attribute * Enhancement: Keep packages sorted in composer.json * Fix: Wrap arguments in code examples * Fix: Code examples --------- Co-authored-by: Andreas Möller <am@localheinz.com> --- README.md | 38 ++++++++++++++++++- composer.json | 5 ++- composer.lock | 2 +- src/Optimizely/Decide/OptimizelyDecision.php | 1 + .../OptimizelyConfig/OptimizelyAttribute.php | 1 + .../OptimizelyConfig/OptimizelyAudience.php | 1 + .../OptimizelyConfig/OptimizelyConfig.php | 1 + .../OptimizelyConfig/OptimizelyEvent.php | 1 + .../OptimizelyConfig/OptimizelyExperiment.php | 1 + .../OptimizelyConfig/OptimizelyFeature.php | 1 + .../OptimizelyConfig/OptimizelyVariable.php | 1 + .../OptimizelyConfig/OptimizelyVariation.php | 1 + 12 files changed, 50 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aec472c4..6f9d1610 100644 --- a/README.md +++ b/README.md @@ -37,25 +37,55 @@ php composer.phar require optimizely/optimizely-sdk Create the Optimizely client, for example: ```php +<?php + +use Optimizely\Optimizely; + $optimizely = new Optimizely(<<DATAFILE>>); ``` Or you may also use OptimizelyFactory method to create an optimizely client using your SDK key, an optional fallback datafile and an optional datafile access token. Using this method internally creates an HTTPProjectConfigManager. See [HTTPProjectConfigManager](#use-httpprojectconfigmanager) for further detail. ```php -$optimizelyClient = OptimizelyFactory::createDefaultInstance("your-sdk-key", <<DATAFILE>>, <<DATAFILE_AUTH_TOKEN>>); +<?php + +use Optimizely\OptimizelyFactory; + +$optimizelyClient = OptimizelyFactory::createDefaultInstance( + "your-sdk-key", + <<DATAFILE>>, + <<DATAFILE_AUTH_TOKEN>> +); ``` To access your HTTPProjectConfigManager: ```php +<?php + +use Optimizely\Optimizely; + +/** @var Optimizely $optimizelyClient */ $configManager = $optimizelyClient->configManager; ``` Or you can also provide an implementation of the [`ProjectConfigManagerInterface`](https://github.com/optimizely/php-sdk/blob/master/src/Optimizely/ProjectConfigManager/ProjectConfigManagerInterface.php) in the constructor: ```php +<?php + +use Optimizely\Optimizely; +use Optimizely\ProjectConfigManager\HTTPProjectConfigManager; + $configManager = new HTTPProjectConfigManager(<<SDK_KEY>>); -$optimizely = new Optimizely(<<DATAFILE>>, null, null, null, false, null, $configManager); +$optimizely = new Optimizely( + <<DATAFILE>>, + null, + null, + null, + false, + null, + $configManager +); ``` ### ProjectConfigManagerInterface @@ -74,6 +104,10 @@ Calling `fetch` will update the internal ProjectConfig instance that will be ret ### Use HTTPProjectConfigManager ```php +<?php + +use Optimizely\ProjectConfigManager\HTTPProjectConfigManager; + $configManager = new HTTPProjectConfigManager(<<SDK_KEY>>); ``` diff --git a/composer.json b/composer.json index 57cc755c..2a28d4cd 100644 --- a/composer.json +++ b/composer.json @@ -16,9 +16,9 @@ }, "require": { "php": ">=8.1", + "guzzlehttp/guzzle": ">=6.2", "justinrainbow/json-schema": "^1.6 || ^2.0 || ^4.0 || ^5.0", "lastguest/murmurhash": "^1.3.0", - "guzzlehttp/guzzle": ">=6.2", "monolog/monolog": ">=1.21" }, "require-dev": { @@ -31,5 +31,8 @@ "psr-4": { "Optimizely\\": "src/Optimizely" } + }, + "config": { + "sort-packages": true } } diff --git a/composer.lock b/composer.lock index 030d3673..50ece972 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2fad7123e2b1cb75c4a03d2332468c70", + "content-hash": "82b1237943c87584a7fd69a882570e14", "packages": [ { "name": "guzzlehttp/guzzle", diff --git a/src/Optimizely/Decide/OptimizelyDecision.php b/src/Optimizely/Decide/OptimizelyDecision.php index 3cd7fe1c..a69cc8a5 100644 --- a/src/Optimizely/Decide/OptimizelyDecision.php +++ b/src/Optimizely/Decide/OptimizelyDecision.php @@ -81,6 +81,7 @@ public function getReasons() return $this->reasons; } + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php b/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php index d3eee9b9..a38182d5 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php @@ -53,6 +53,7 @@ public function getKey() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php b/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php index e331871a..d11e4b32 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php @@ -68,6 +68,7 @@ public function getConditions() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 26093779..c8d6f33e 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -166,6 +166,7 @@ public function getEvents() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php b/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php index 8a3a90de..17ff39ed 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php @@ -68,6 +68,7 @@ public function getExperimentIds() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php b/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php index 71bd9959..77d274b3 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php @@ -83,6 +83,7 @@ public function getVariationsMap() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php b/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php index 7b605064..491f4ea2 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php @@ -118,6 +118,7 @@ public function getVariablesMap() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyVariable.php b/src/Optimizely/OptimizelyConfig/OptimizelyVariable.php index 836e054f..fade386b 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyVariable.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyVariable.php @@ -82,6 +82,7 @@ public function getValue() /** * @return string JSON representation of the object. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { return get_object_vars($this); diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyVariation.php b/src/Optimizely/OptimizelyConfig/OptimizelyVariation.php index e7335eea..3e735a20 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyVariation.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyVariation.php @@ -84,6 +84,7 @@ public function getVariablesMap() * @return string JSON representation of the object. * Unsets featureEnabled property for variations of ab experiments. */ + #[\ReturnTypeWillChange] public function jsonSerialize() { $props = get_object_vars($this); From e9250ed240260ed23281596bbde700f1a6e59678 Mon Sep 17 00:00:00 2001 From: Mike Chu <104384559+mikechu-optimizely@users.noreply.github.com> Date: Mon, 4 Dec 2023 17:04:52 -0500 Subject: [PATCH 6/6] chore: prepare release 4.0.1 (#282) --- CHANGELOG.md | 9 +++++++++ src/Optimizely/Event/Builder/EventBuilder.php | 2 +- tests/EventTests/EventBuilderTest.php | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebf6dcd..34678e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Optimizely PHP SDK Changelog +## 4.0.1 +December 4, 2023 + +* Add Dev Containers and bug bash #269 +* Add GitHub Issues templates #278 +* Fix: Deprecation warning #279 +* Return Latest Experiment When Duplicate Keys in Config #280 +* Fix: Code examples by @localheinz #281 + ## 4.0.0 June 12, 2023 diff --git a/src/Optimizely/Event/Builder/EventBuilder.php b/src/Optimizely/Event/Builder/EventBuilder.php index 21a0524a..cc1e2e4c 100644 --- a/src/Optimizely/Event/Builder/EventBuilder.php +++ b/src/Optimizely/Event/Builder/EventBuilder.php @@ -39,7 +39,7 @@ class EventBuilder /** * @const string Version of the Optimizely PHP SDK. */ - const SDK_VERSION = '4.0.0'; + const SDK_VERSION = '4.0.1'; /** * @var string URL to send event to. diff --git a/tests/EventTests/EventBuilderTest.php b/tests/EventTests/EventBuilderTest.php index ae1df007..967c5893 100644 --- a/tests/EventTests/EventBuilderTest.php +++ b/tests/EventTests/EventBuilderTest.php @@ -69,7 +69,7 @@ protected function setUp() : void ]], 'revision' => '15', 'client_name' => 'php-sdk', - 'client_version' => '4.0.0', + 'client_version' => '4.0.1', 'anonymize_ip'=> false, 'enrich_decisions' => true, ]; <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy