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 @@ +