From b9e692b022d903e04f4230fa2cebb4ca665a938f Mon Sep 17 00:00:00 2001 From: Ahmed Abbas Date: Thu, 21 May 2026 04:06:55 +0300 Subject: [PATCH] fix(context): forward all BucketingAttributes to ExperienceManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context::runExperience and Context::runExperiences rebuilt a fresh BucketingAttributes object with only four hardcoded keys (visitorProperties, locationProperties, updateVisitorProperties, environment) before forwarding to ExperienceManager::selectVariation and selectVariations. This silently dropped every other BucketingAttributes field — enableTracking, forceVariationId, ignoreLocationProperties, typeCasting, experienceKeys — so callers opting out of tracking, forcing a variation, or ignoring location targeting were silently ignored. DataManager destructures these by name with enableTracking defaulting to true (DataManager::531), which is how the opt-out was being lost. Switch to get_object_vars-spread-then-override so all caller-supplied attributes pass through while still applying the two transforms Context must do: merging visitorProperties via getVisitorProperties() and defaulting environment to the Context's own environment. Add tests covering forwarding of enableTracking, forceVariationId, ignoreLocationProperties, and updateVisitorProperties through both runExperience and runExperiences. Mirrors convertcom/javascript-sdk#382 — same bug, same fix shape adapted to PHP. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/Php-sdk/src/Context.php | 20 +++---- packages/Php-sdk/tests/ContextTest.php | 82 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/packages/Php-sdk/src/Context.php b/packages/Php-sdk/src/Context.php index f43e17c..8a9b4fe 100644 --- a/packages/Php-sdk/src/Context.php +++ b/packages/Php-sdk/src/Context.php @@ -104,15 +104,13 @@ public function runExperience(string $experienceKey, ?BucketingAttributes $attri } $visitorProperties = $this->getVisitorProperties($attributes?->getVisitorProperties()); + $forwardedData = $attributes ? get_object_vars($attributes) : []; + $forwardedData['visitorProperties'] = $visitorProperties; + $forwardedData['environment'] = $forwardedData['environment'] ?? $this->environment; $result = $this->experienceManager->selectVariation( $this->visitorId, $experienceKey, - new BucketingAttributes([ - 'visitorProperties' => $visitorProperties, - 'locationProperties' => $attributes?->getLocationProperties(), - 'updateVisitorProperties' => $attributes?->getUpdateVisitorProperties(), - 'environment' => $attributes?->getEnvironment() ?? $this->environment, - ]) + new BucketingAttributes($forwardedData) ); if ($result === null @@ -153,15 +151,13 @@ public function runExperiences(?BucketingAttributes $attributes = null): array } $visitorProperties = $this->getVisitorProperties($attributes?->getVisitorProperties()); + $forwardedData = $attributes ? get_object_vars($attributes) : []; + $forwardedData['visitorProperties'] = $visitorProperties; + $forwardedData['environment'] = $forwardedData['environment'] ?? $this->environment; $bucketedVariations = $this->experienceManager->selectVariations( $this->visitorId, - new BucketingAttributes([ - 'visitorProperties' => $visitorProperties, - 'locationProperties' => $attributes?->getLocationProperties(), - 'updateVisitorProperties' => $attributes?->getUpdateVisitorProperties(), - 'environment' => $attributes?->getEnvironment() ?? $this->environment, - ]) + new BucketingAttributes($forwardedData) ); $dtos = []; diff --git a/packages/Php-sdk/tests/ContextTest.php b/packages/Php-sdk/tests/ContextTest.php index 8f555cf..d73168e 100644 --- a/packages/Php-sdk/tests/ContextTest.php +++ b/packages/Php-sdk/tests/ContextTest.php @@ -133,6 +133,88 @@ public function testGetVariationsAcrossAllExperiences(): void } } + public function testRunExperienceForwardsAllBucketingAttributes(): void + { + $realManager = $this->experienceManager; + $captured = null; + + $spy = $this->createMock(\ConvertSdk\Interfaces\ExperienceManagerInterface::class); + $spy->method('selectVariation')->willReturnCallback( + function (string $visitorId, string $experienceKey, BucketingAttributes $attributes) use ($realManager, &$captured) { + $captured = $attributes; + return $realManager->selectVariation($visitorId, $experienceKey, $attributes); + } + ); + + $context = new Context( + $this->config, + $this->visitorId, + $this->eventManager, + $spy, + $this->featureManager, + $this->dataManager, + $this->segmentsManager, + $this->apiManager, + ); + + $context->runExperience('test-experience-ab-fullstack-2', new BucketingAttributes([ + 'locationProperties' => ['url' => 'https://convert.com/'], + 'visitorProperties' => ['varName3' => 'something'], + 'enableTracking' => false, + 'forceVariationId' => '100299461', + 'ignoreLocationProperties' => true, + 'updateVisitorProperties' => true, + ])); + + $this->assertNotNull($captured); + $this->assertFalse($captured->enableTracking); + $this->assertSame('100299461', $captured->forceVariationId); + $this->assertTrue($captured->ignoreLocationProperties); + $this->assertTrue($captured->updateVisitorProperties); + $this->assertSame(['url' => 'https://convert.com/'], $captured->locationProperties); + } + + public function testRunExperiencesForwardsAllBucketingAttributes(): void + { + $realManager = $this->experienceManager; + $captured = null; + + $spy = $this->createMock(\ConvertSdk\Interfaces\ExperienceManagerInterface::class); + $spy->method('selectVariations')->willReturnCallback( + function (string $visitorId, BucketingAttributes $attributes) use ($realManager, &$captured) { + $captured = $attributes; + return $realManager->selectVariations($visitorId, $attributes); + } + ); + + $context = new Context( + $this->config, + $this->visitorId, + $this->eventManager, + $spy, + $this->featureManager, + $this->dataManager, + $this->segmentsManager, + $this->apiManager, + ); + + $context->runExperiences(new BucketingAttributes([ + 'locationProperties' => ['url' => 'https://convert.com/'], + 'visitorProperties' => ['varName3' => 'something'], + 'enableTracking' => false, + 'forceVariationId' => '100299461', + 'ignoreLocationProperties' => true, + 'updateVisitorProperties' => true, + ])); + + $this->assertNotNull($captured); + $this->assertFalse($captured->enableTracking); + $this->assertSame('100299461', $captured->forceVariationId); + $this->assertTrue($captured->ignoreLocationProperties); + $this->assertTrue($captured->updateVisitorProperties); + $this->assertSame(['url' => 'https://convert.com/'], $captured->locationProperties); + } + public function testGetSingleFeatureWithStatus(): void { $featureKey = 'feature-2';