From b21ece6b3c4f1dab1a555b8e4751c1cdd3f0ad15 Mon Sep 17 00:00:00 2001 From: Phil Haack Date: Wed, 15 Jul 2026 09:15:31 -0700 Subject: [PATCH 1/2] Add $feature_flag_has_experiment to $feature_flag_called events --- .changeset/flag-called-has-experiment.md | 5 ++ api/public-api.json | 17 +++++++ lib/Client.php | 14 ++++++ lib/EvaluatedFlagRecord.php | 3 ++ lib/FeatureFlagEvaluations.php | 3 ++ test/FeatureFlagEvaluationsTest.php | 58 ++++++++++++++++++++++++ test/FeatureFlagLocalEvaluationTest.php | 27 ++++++++++- test/FeatureFlagTest.php | 35 +++++++++++++- 8 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 .changeset/flag-called-has-experiment.md diff --git a/.changeset/flag-called-has-experiment.md b/.changeset/flag-called-has-experiment.md new file mode 100644 index 0000000..bd09be8 --- /dev/null +++ b/.changeset/flag-called-has-experiment.md @@ -0,0 +1,5 @@ +--- +"posthog-php": minor +--- + +Add a $feature_flag_has_experiment boolean property to every $feature_flag_called event, sourced from the server's has_experiment field and defaulting to false when the server does not report it. diff --git a/api/public-api.json b/api/public-api.json index 979073c..a605f46 100644 --- a/api/public-api.json +++ b/api/public-api.json @@ -1576,6 +1576,13 @@ "default": null, "hasDefault": false }, + "hasExperiment": { + "static": false, + "readonly": true, + "type": "bool", + "default": null, + "hasDefault": false + }, "id": { "static": false, "readonly": true, @@ -1712,6 +1719,16 @@ "default": null, "defaultConstant": null, "hasDefault": false + }, + { + "name": "hasExperiment", + "type": "bool", + "byReference": false, + "variadic": false, + "optional": true, + "default": false, + "defaultConstant": null, + "hasDefault": true } ] }, diff --git a/lib/Client.php b/lib/Client.php index 273aff9..c6311a9 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -699,9 +699,11 @@ private function doGetFeatureFlagResult( $result = null; $payload = null; $featureFlagError = null; + $localFlagDefinition = null; foreach ($this->featureFlags as $flag) { if ($flag["key"] == $key) { + $localFlagDefinition = $flag; try { $result = $this->computeFlagLocally( $flag, @@ -785,9 +787,19 @@ private function doGetFeatureFlagResult( } if ($sendFeatureFlagEvents) { + // Locally-evaluated flags carry has_experiment in the stored definition; + // remotely-evaluated flags carry it in the response metadata. Defaults to + // false when the server (older deployment) does not report it. + if ($flagWasEvaluatedLocally) { + $hasExperiment = (bool) ($localFlagDefinition['has_experiment'] ?? false); + } else { + $hasExperiment = (bool) ($flagDetail['metadata']['has_experiment'] ?? false); + } + $properties = [ '$feature_flag' => $key, '$feature_flag_response' => $result, + '$feature_flag_has_experiment' => $hasExperiment, ]; if (!is_null($requestId)) { @@ -1034,6 +1046,7 @@ public function evaluateFlags( version: null, reason: 'Evaluated locally', locallyEvaluated: true, + hasExperiment: (bool) ($flag['has_experiment'] ?? false), ); } @@ -1106,6 +1119,7 @@ public function evaluateFlags( : null, reason: $flagDetail['reason']['description'] ?? null, locallyEvaluated: false, + hasExperiment: (bool) ($flagDetail['metadata']['has_experiment'] ?? false), ); } } catch (HttpException $e) { diff --git a/lib/EvaluatedFlagRecord.php b/lib/EvaluatedFlagRecord.php index ee8e6b0..9bac570 100644 --- a/lib/EvaluatedFlagRecord.php +++ b/lib/EvaluatedFlagRecord.php @@ -20,6 +20,8 @@ final class EvaluatedFlagRecord * @param int|null $version Feature flag version, when provided by the API. * @param string|null $reason Evaluation reason, when provided by the API. * @param bool $locallyEvaluated Whether the value was computed locally. + * @param bool $hasExperiment Server-reported signal for whether the flag is linked to an + * experiment. Defaults to false when the server does not report it (older deployments). */ public function __construct( public readonly string $key, @@ -30,6 +32,7 @@ public function __construct( public readonly ?int $version, public readonly ?string $reason, public readonly bool $locallyEvaluated, + public readonly bool $hasExperiment = false, ) { } diff --git a/lib/FeatureFlagEvaluations.php b/lib/FeatureFlagEvaluations.php index 7a6a293..4b579ba 100644 --- a/lib/FeatureFlagEvaluations.php +++ b/lib/FeatureFlagEvaluations.php @@ -178,6 +178,9 @@ private function recordAccess(string $key, ?EvaluatedFlagRecord $record): void // Missing flags get a null response (not false), matching the legacy single-flag path // so consumers can distinguish "flag exists and is disabled" from "flag not found". '$feature_flag_response' => $response, + // Server-reported signal for whether the flag is linked to an experiment. + // Defaults to false when the server does not report it (older deployments). + '$feature_flag_has_experiment' => $record?->hasExperiment ?? false, // Always set explicitly so consumers don't have to infer "missing key means remote". 'locally_evaluated' => $record?->locallyEvaluated ?? false, ]; diff --git a/test/FeatureFlagEvaluationsTest.php b/test/FeatureFlagEvaluationsTest.php index 2dc882c..e073b80 100644 --- a/test/FeatureFlagEvaluationsTest.php +++ b/test/FeatureFlagEvaluationsTest.php @@ -364,6 +364,63 @@ public function testLocallyEvaluatedFlagTagsLocallyEvaluatedAndReason(): void $this->assertArrayNotHasKey('$feature_flag_version', $properties); $this->assertArrayNotHasKey('$feature_flag_request_id', $properties); $this->assertArrayNotHasKey('$feature_flag_evaluated_at', $properties); + // Local definition doesn't report has_experiment, so the property defaults to false. + $this->assertFalse($properties['$feature_flag_has_experiment']); + } + + public function testHasExperimentFromRemoteMetadataPropagatesToEvent(): void + { + $response = MockedResponses::FLAGS_V2_RESPONSE; + $response['flags']['simple-test']['metadata']['has_experiment'] = true; + $response['flags']['multivariate-test']['metadata']['has_experiment'] = false; + // having_fun's metadata omits has_experiment, covering older deployments. + $this->makeClient(flagsEndpointResponse: $response); + + $snapshot = PostHog::evaluateFlags('user-1'); + $snapshot->isEnabled('simple-test'); + $snapshot->getFlag('multivariate-test'); + $snapshot->isEnabled('having_fun'); + PostHog::flush(); + + $batches = $this->batchRequests(); + $this->assertCount(1, $batches); + $hasExperimentByFlag = []; + foreach ($batches[0]['batch'] as $event) { + $properties = $event['properties']; + $hasExperimentByFlag[$properties['$feature_flag']] = $properties['$feature_flag_has_experiment']; + } + + $this->assertSame( + [ + 'simple-test' => true, + 'multivariate-test' => false, + 'having_fun' => false, + ], + $hasExperimentByFlag + ); + } + + public function testHasExperimentFromLocalDefinitionPropagatesToEvent(): void + { + $localResponse = MockedResponses::LOCAL_EVALUATION_REQUEST; + $localResponse['flags'][0]['has_experiment'] = true; + $this->makeClient( + personalApiKey: 'test-personal-key', + localEvaluationResponse: $localResponse, + ); + $snapshot = PostHog::evaluateFlags( + 'user-1', + personProperties: ['region' => 'USA'] + ); + + $this->assertTrue($snapshot->isEnabled('person-flag')); + PostHog::flush(); + + $batches = $this->batchRequests(); + $this->assertCount(1, $batches); + $properties = $batches[0]['batch'][0]['properties']; + $this->assertTrue($properties['locally_evaluated']); + $this->assertTrue($properties['$feature_flag_has_experiment']); } public function testLocalEvaluationSkipsRemoteFlagsRequestWhenAllResolved(): void @@ -409,6 +466,7 @@ public function testMissingFlagResponseIsNullNotFalse(): void $properties = $batches[0]['batch'][0]['properties']; $this->assertNull($properties['$feature_flag_response']); $this->assertFalse($properties['locally_evaluated']); + $this->assertFalse($properties['$feature_flag_has_experiment']); } public function testRemotePayloadHandlesPreDecodedValue(): void diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 5f4d773..7c4a5e8 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1569,7 +1569,7 @@ public function testSimpleFlag() ), 1 => array( "path" => "/batch/", - 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', + 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$feature_flag_has_experiment":false,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -1578,6 +1578,31 @@ public function testSimpleFlag() }); } + public function testFeatureFlagCalledEventIncludesHasExperimentFromLocalDefinition() + { + $localResponse = MockedResponses::LOCAL_EVALUATION_SIMPLE_REQUEST; + $localResponse['flags'][0]['has_experiment'] = true; + + $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: $localResponse); + $this->client = new Client( + self::FAKE_API_KEY, + [ + "debug" => true, + ], + $this->http_client, + "test" + ); + PostHog::init(null, null, $this->client); + + $this->assertTrue(PostHog::getFeatureFlag('simple-flag', 'some-distinct-id')); + PostHog::flush(); + + $payload = json_decode($this->http_client->calls[1]['payload'], true); + $properties = $payload['batch'][0]['properties']; + $this->assertSame('simple-flag', $properties['$feature_flag']); + $this->assertTrue($properties['$feature_flag_has_experiment']); + } + public function testFeatureFlagsDontFallbackToDecideWhenOnlyLocalEvaluationIsTrue() { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::FALLBACK_TO_FLAGS_REQUEST); diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 47fc236..d513a79 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -379,7 +379,7 @@ public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditional ), 1 => array( "path" => "/batch/", - "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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_has_experiment":false,"$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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -479,7 +479,7 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe ), 1 => array( "path" => "/batch/", - "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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_has_experiment":false,"$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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -488,6 +488,37 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe }); } + public static function hasExperimentCases(): array + { + return [ + 'reported true' => [true, true], + 'reported false' => [false, false], + 'absent' => [null, false], + ]; + } + + /** + * @dataProvider hasExperimentCases + */ + public function testFeatureFlagCalledEventIncludesHasExperimentFromRemoteMetadata( + ?bool $reported, + bool $expected + ) { + $response = MockedResponses::FLAGS_V2_RESPONSE; + if ($reported !== null) { + $response['flags']['simple-test']['metadata']['has_experiment'] = $reported; + } + $this->setUp($response, personalApiKey: null); + + $this->assertTrue(PostHog::isFeatureEnabled('simple-test', 'user-id')); + PostHog::flush(); + + $payload = json_decode($this->http_client->calls[1]['payload'], true); + $properties = $payload['batch'][0]['properties']; + $this->assertSame('simple-test', $properties['$feature_flag']); + $this->assertSame($expected, $properties['$feature_flag_has_experiment']); + } + /** * @dataProvider decideResponseCases */ From fce707aad4a720045f727f445cdadbc19996cd9f Mon Sep 17 00:00:00 2001 From: Phil Haack Date: Wed, 15 Jul 2026 12:37:34 -0700 Subject: [PATCH 2/2] Omit $feature_flag_has_experiment when the server does not report it --- .changeset/flag-called-has-experiment.md | 2 +- api/public-api.json | 6 +++--- lib/Client.php | 22 +++++++++++++++------- lib/EvaluatedFlagRecord.php | 6 +++--- lib/FeatureFlagEvaluations.php | 10 +++++++--- test/FeatureFlagEvaluationsTest.php | 24 ++++++++++-------------- test/FeatureFlagLocalEvaluationTest.php | 2 +- test/FeatureFlagTest.php | 24 ++++++++++++++---------- 8 files changed, 54 insertions(+), 42 deletions(-) diff --git a/.changeset/flag-called-has-experiment.md b/.changeset/flag-called-has-experiment.md index bd09be8..07ad8fb 100644 --- a/.changeset/flag-called-has-experiment.md +++ b/.changeset/flag-called-has-experiment.md @@ -2,4 +2,4 @@ "posthog-php": minor --- -Add a $feature_flag_has_experiment boolean property to every $feature_flag_called event, sourced from the server's has_experiment field and defaulting to false when the server does not report it. +Add a $feature_flag_has_experiment boolean property to $feature_flag_called events, mirroring the server's has_experiment field. The property is only sent when the server explicitly reports has_experiment and is omitted when unknown (older deployments, missing metadata, missing flags). diff --git a/api/public-api.json b/api/public-api.json index a605f46..d2c9f7d 100644 --- a/api/public-api.json +++ b/api/public-api.json @@ -1579,7 +1579,7 @@ "hasExperiment": { "static": false, "readonly": true, - "type": "bool", + "type": "?bool", "default": null, "hasDefault": false }, @@ -1722,11 +1722,11 @@ }, { "name": "hasExperiment", - "type": "bool", + "type": "?bool", "byReference": false, "variadic": false, "optional": true, - "default": false, + "default": null, "defaultConstant": null, "hasDefault": true } diff --git a/lib/Client.php b/lib/Client.php index c6311a9..9826050 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -788,20 +788,24 @@ private function doGetFeatureFlagResult( if ($sendFeatureFlagEvents) { // Locally-evaluated flags carry has_experiment in the stored definition; - // remotely-evaluated flags carry it in the response metadata. Defaults to - // false when the server (older deployment) does not report it. + // remotely-evaluated flags carry it in the response metadata. Null when the + // server (older deployment) does not report it, in which case the property + // is omitted from the event. if ($flagWasEvaluatedLocally) { - $hasExperiment = (bool) ($localFlagDefinition['has_experiment'] ?? false); + $hasExperiment = $localFlagDefinition['has_experiment'] ?? null; } else { - $hasExperiment = (bool) ($flagDetail['metadata']['has_experiment'] ?? false); + $hasExperiment = $flagDetail['metadata']['has_experiment'] ?? null; } $properties = [ '$feature_flag' => $key, '$feature_flag_response' => $result, - '$feature_flag_has_experiment' => $hasExperiment, ]; + if (!is_null($hasExperiment)) { + $properties['$feature_flag_has_experiment'] = (bool) $hasExperiment; + } + if (!is_null($requestId)) { $properties['$feature_flag_request_id'] = $requestId; } @@ -1046,7 +1050,9 @@ public function evaluateFlags( version: null, reason: 'Evaluated locally', locallyEvaluated: true, - hasExperiment: (bool) ($flag['has_experiment'] ?? false), + hasExperiment: isset($flag['has_experiment']) + ? (bool) $flag['has_experiment'] + : null, ); } @@ -1119,7 +1125,9 @@ public function evaluateFlags( : null, reason: $flagDetail['reason']['description'] ?? null, locallyEvaluated: false, - hasExperiment: (bool) ($flagDetail['metadata']['has_experiment'] ?? false), + hasExperiment: isset($flagDetail['metadata']['has_experiment']) + ? (bool) $flagDetail['metadata']['has_experiment'] + : null, ); } } catch (HttpException $e) { diff --git a/lib/EvaluatedFlagRecord.php b/lib/EvaluatedFlagRecord.php index 9bac570..f4b381e 100644 --- a/lib/EvaluatedFlagRecord.php +++ b/lib/EvaluatedFlagRecord.php @@ -20,8 +20,8 @@ final class EvaluatedFlagRecord * @param int|null $version Feature flag version, when provided by the API. * @param string|null $reason Evaluation reason, when provided by the API. * @param bool $locallyEvaluated Whether the value was computed locally. - * @param bool $hasExperiment Server-reported signal for whether the flag is linked to an - * experiment. Defaults to false when the server does not report it (older deployments). + * @param bool|null $hasExperiment Server-reported signal for whether the flag is linked to an + * experiment. Null when the server does not report it (older deployments). */ public function __construct( public readonly string $key, @@ -32,7 +32,7 @@ public function __construct( public readonly ?int $version, public readonly ?string $reason, public readonly bool $locallyEvaluated, - public readonly bool $hasExperiment = false, + public readonly ?bool $hasExperiment = null, ) { } diff --git a/lib/FeatureFlagEvaluations.php b/lib/FeatureFlagEvaluations.php index 4b579ba..137d6f6 100644 --- a/lib/FeatureFlagEvaluations.php +++ b/lib/FeatureFlagEvaluations.php @@ -178,13 +178,17 @@ private function recordAccess(string $key, ?EvaluatedFlagRecord $record): void // Missing flags get a null response (not false), matching the legacy single-flag path // so consumers can distinguish "flag exists and is disabled" from "flag not found". '$feature_flag_response' => $response, - // Server-reported signal for whether the flag is linked to an experiment. - // Defaults to false when the server does not report it (older deployments). - '$feature_flag_has_experiment' => $record?->hasExperiment ?? false, // Always set explicitly so consumers don't have to infer "missing key means remote". 'locally_evaluated' => $record?->locallyEvaluated ?? false, ]; + // Server-reported signal for whether the flag is linked to an experiment. Only sent + // when the server explicitly reported it; omitted when unknown (older deployments, + // missing metadata, missing flags). + if ($record?->hasExperiment !== null) { + $properties['$feature_flag_has_experiment'] = $record->hasExperiment; + } + if ($record !== null) { if ($record->id !== null) { $properties['$feature_flag_id'] = $record->id; diff --git a/test/FeatureFlagEvaluationsTest.php b/test/FeatureFlagEvaluationsTest.php index e073b80..57c4eae 100644 --- a/test/FeatureFlagEvaluationsTest.php +++ b/test/FeatureFlagEvaluationsTest.php @@ -364,8 +364,8 @@ public function testLocallyEvaluatedFlagTagsLocallyEvaluatedAndReason(): void $this->assertArrayNotHasKey('$feature_flag_version', $properties); $this->assertArrayNotHasKey('$feature_flag_request_id', $properties); $this->assertArrayNotHasKey('$feature_flag_evaluated_at', $properties); - // Local definition doesn't report has_experiment, so the property defaults to false. - $this->assertFalse($properties['$feature_flag_has_experiment']); + // Local definition doesn't report has_experiment, so the property is omitted. + $this->assertArrayNotHasKey('$feature_flag_has_experiment', $properties); } public function testHasExperimentFromRemoteMetadataPropagatesToEvent(): void @@ -384,20 +384,15 @@ public function testHasExperimentFromRemoteMetadataPropagatesToEvent(): void $batches = $this->batchRequests(); $this->assertCount(1, $batches); - $hasExperimentByFlag = []; + $propertiesByFlag = []; foreach ($batches[0]['batch'] as $event) { - $properties = $event['properties']; - $hasExperimentByFlag[$properties['$feature_flag']] = $properties['$feature_flag_has_experiment']; + $propertiesByFlag[$event['properties']['$feature_flag']] = $event['properties']; } - $this->assertSame( - [ - 'simple-test' => true, - 'multivariate-test' => false, - 'having_fun' => false, - ], - $hasExperimentByFlag - ); + $this->assertTrue($propertiesByFlag['simple-test']['$feature_flag_has_experiment']); + $this->assertFalse($propertiesByFlag['multivariate-test']['$feature_flag_has_experiment']); + // Unreported has_experiment omits the property rather than defaulting it. + $this->assertArrayNotHasKey('$feature_flag_has_experiment', $propertiesByFlag['having_fun']); } public function testHasExperimentFromLocalDefinitionPropagatesToEvent(): void @@ -466,7 +461,8 @@ public function testMissingFlagResponseIsNullNotFalse(): void $properties = $batches[0]['batch'][0]['properties']; $this->assertNull($properties['$feature_flag_response']); $this->assertFalse($properties['locally_evaluated']); - $this->assertFalse($properties['$feature_flag_has_experiment']); + // Missing flags have no server-reported has_experiment, so the property is omitted. + $this->assertArrayNotHasKey('$feature_flag_has_experiment', $properties); } public function testRemotePayloadHandlesPreDecodedValue(): void diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 7c4a5e8..789bb02 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1569,7 +1569,7 @@ public function testSimpleFlag() ), 1 => array( "path" => "/batch/", - 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$feature_flag_has_experiment":false,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"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","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"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 d513a79..a1ae12c 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -379,7 +379,7 @@ public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditional ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_has_experiment":false,"$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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -479,7 +479,7 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_has_experiment":false,"$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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"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","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"groups":[],"timestamp":"2022-05-01T00:00:00+00:00"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -490,20 +490,20 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe public static function hasExperimentCases(): array { + // The event property mirrors the server's has_experiment field exactly and is + // omitted when the server does not report it (older deployments). return [ - 'reported true' => [true, true], - 'reported false' => [false, false], - 'absent' => [null, false], + 'reported true' => [true], + 'reported false' => [false], + 'absent' => [null], ]; } /** * @dataProvider hasExperimentCases */ - public function testFeatureFlagCalledEventIncludesHasExperimentFromRemoteMetadata( - ?bool $reported, - bool $expected - ) { + public function testFeatureFlagCalledEventIncludesHasExperimentFromRemoteMetadata(?bool $reported) + { $response = MockedResponses::FLAGS_V2_RESPONSE; if ($reported !== null) { $response['flags']['simple-test']['metadata']['has_experiment'] = $reported; @@ -516,7 +516,11 @@ public function testFeatureFlagCalledEventIncludesHasExperimentFromRemoteMetadat $payload = json_decode($this->http_client->calls[1]['payload'], true); $properties = $payload['batch'][0]['properties']; $this->assertSame('simple-test', $properties['$feature_flag']); - $this->assertSame($expected, $properties['$feature_flag_has_experiment']); + if ($reported === null) { + $this->assertArrayNotHasKey('$feature_flag_has_experiment', $properties); + } else { + $this->assertSame($reported, $properties['$feature_flag_has_experiment']); + } } /**