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