From 39c6be40327eca7a77205f8ac976cce74684ad87 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 16 Dec 2025 17:14:27 -0500 Subject: [PATCH 1/2] fix: Respect send_feature_flags=false and local eval 1. If `send_feature_flags` is not explicitly set to true, calls to `capture` will no longer add feature flag properties to an event, even when local evaluation is enabled. 2. Local evaluation is used to populate feature flag properties when local evaluation is enabled and `send_feature_flags` is true 3. As an indirect result, `$feature_flag_called` will no longer include other feature flag properties --- lib/Client.php | 37 ++++++------ test/FeatureFlagLocalEvaluationTest.php | 2 +- test/FeatureFlagTest.php | 4 +- test/PostHogTest.php | 78 ++++++++++++++++++++----- 4 files changed, 86 insertions(+), 35 deletions(-) diff --git a/lib/Client.php b/lib/Client.php index ec56ff5..4dc1599 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -149,27 +149,30 @@ public function capture(array $message) $message["properties"]['$groups'] = $message['$groups']; } - $extraProperties = []; - $flags = []; if (array_key_exists("send_feature_flags", $message) && $message["send_feature_flags"]) { - $flags = $this->fetchFeatureVariants($message["distinct_id"], $message["groups"]); - } elseif (count($this->featureFlags) != 0) { - # Local evaluation is enabled, flags are loaded, so try and get all flags we can without going to the server - $flags = $this->getAllFlags($message["distinct_id"], $message["groups"], [], [], true); - } + $extraProperties = []; + $flags = []; + + if (count($this->featureFlags) != 0) { + # Local evaluation is enabled, flags are loaded, so try and get all flags we can without going to the server + $flags = $this->getAllFlags($message["distinct_id"], $message["groups"], [], [], true); + } else { + $flags = $this->fetchFeatureVariants($message["distinct_id"], $message["groups"]); + } - // Add all feature variants to event - foreach ($flags as $flagKey => $flagValue) { - $extraProperties[sprintf('$feature/%s', $flagKey)] = $flagValue; - } + // Add all feature variants to event + foreach ($flags as $flagKey => $flagValue) { + $extraProperties[sprintf('$feature/%s', $flagKey)] = $flagValue; + } + // Add all feature flag keys that aren't false to $active_feature_flags + // decide v2 does this automatically, but we need it for when we upgrade to v3 + $extraProperties['$active_feature_flags'] = array_keys(array_filter($flags, function ($flagValue) { + return $flagValue !== false; + })); - // Add all feature flag keys that aren't false to $active_feature_flags - // decide v2 does this automatically, but we need it for when we upgrade to v3 - $extraProperties['$active_feature_flags'] = array_keys(array_filter($flags, function ($flagValue) { - return $flagValue !== false; - })); + $message["properties"] = array_merge($extraProperties, $message["properties"]); + } - $message["properties"] = array_merge($extraProperties, $message["properties"]); return $this->consumer->capture($message); } diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index fb5c294..b0cc66c 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1367,7 +1367,7 @@ public function testSimpleFlag() ), 1 => array( "path" => "/batch/", - 'payload' => '{"batch":[{"properties":{"$feature\/simple-flag":true,"$active_feature_flags":["simple-flag"],"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 1d355dc..cff58aa 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -96,7 +96,7 @@ public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditional ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$active_feature_flags":[],"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -178,7 +178,7 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$active_feature_flags":[],"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 3669b29..71206c8 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -117,14 +117,8 @@ public function testCaptureWithSendFeatureFlagsOption(): void "requestOptions" => array("includeEtag" => true), ), 1 => array ( - "path" => "/flags/?v=2", - "payload" => sprintf('{"api_key":"%s","distinct_id":"john"}', self::FAKE_API_KEY), - "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), - "requestOptions" => array("timeout" => 1234, "shouldRetry" => false), - ), - 2 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/simpleFlag":true,"$feature\/having_fun":false,"$feature\/enabled-flag":true,"$feature\/disabled-flag":false,"$feature\/multivariate-simple-test":"variant-simple-value","$feature\/simple-test":true,"$feature\/multivariate-test":"variant-value","$feature\/group-flag":"decide-fallback-value","$feature\/complex-flag":"decide-fallback-value","$feature\/beta-feature":"decide-fallback-value","$feature\/beta-feature2":"alakazam","$feature\/feature-1":"decide-fallback-value","$feature\/feature-2":"decide-fallback-value","$feature\/variant-1":"variant-1","$feature\/variant-3":"variant-3","$active_feature_flags":["simpleFlag","enabled-flag","multivariate-simple-test","simple-test","multivariate-test","group-flag","complex-flag","beta-feature","beta-feature2","feature-1","feature-2","variant-1","variant-3"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -133,12 +127,12 @@ public function testCaptureWithSendFeatureFlagsOption(): void // check true-flag is not in captured event $this->assertEquals( - strpos($this->http_client->calls[2]["payload"], 'simpleFlag'), - true + strpos($this->http_client->calls[1]["payload"], 'simpleFlag'), + false ); $this->assertEquals( - strpos($this->http_client->calls[2]["payload"], 'true-flag'), - false + strpos($this->http_client->calls[1]["payload"], 'true-flag'), + true ); }); } @@ -162,7 +156,8 @@ public function testCaptureWithLocalSendFlags(): void array ( "distinctId" => "john", "event" => "Module PHP Event", - ) + "send_feature_flags" => true + ), ) ); @@ -179,7 +174,7 @@ public function testCaptureWithLocalSendFlags(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -209,7 +204,8 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void "event" => "Module PHP Event", "properties" => array ( "\$feature/true-flag" => "random-override" - ) + ), + "send_feature_flags" => true ) ) ); @@ -228,7 +224,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"send_feature_flags":true,"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -467,4 +463,56 @@ public function testDefaultPropertiesGetAddedProperly(): void ) ); } + + public function testCaptureWithSendFeatureFlagsFalse(): void + { + $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_MULTIPLE_REQUEST); + $this->client = new Client( + self::FAKE_API_KEY, + [ + "debug" => true, + ], + $this->http_client, + "test" + ); + PostHog::init(null, null, $this->client); + + ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->assertTrue( + PostHog::capture( + array ( + "distinctId" => "john", + "event" => "Module PHP Event", + "send_feature_flags" => false + ) + ) + ); + + PostHog::flush(); + + // When send_feature_flags is explicitly false, NO feature flags should be added + $this->assertEquals( + $this->http_client->calls, + array ( + 0 => array ( + "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "payload" => null, + "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), + "requestOptions" => array("includeEtag" => true), + ), + 1 => array ( + "path" => "/batch/", + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":false,"properties":{"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), + "requestOptions" => array('shouldVerify' => true), + ), + ) + ); + + // Verify NO feature flag properties were added + $payload = $this->http_client->calls[1]["payload"]; + $this->assertStringNotContainsString('$feature/', $payload); + $this->assertStringNotContainsString('$active_feature_flags', $payload); + }); + } } From 9793b449067dc0cedd6f141fe6af9149ce095701 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 16 Dec 2025 17:28:17 -0500 Subject: [PATCH 2/2] style: Update a comment --- test/PostHogTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 71206c8..af41d7d 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -125,7 +125,7 @@ public function testCaptureWithSendFeatureFlagsOption(): void ) ); - // check true-flag is not in captured event + // Verify only locally evaluated feature flags are included $this->assertEquals( strpos($this->http_client->calls[1]["payload"], 'simpleFlag'), false