diff --git a/lib/Client.php b/lib/Client.php index 4f41443..0f332a5 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -688,6 +688,12 @@ public function loadFlags() return; } + $responseCode = $response->getResponseCode(); + if ($responseCode !== 200) { + error_log("[PostHog][Client] Failed to load feature flags (HTTP $responseCode): " . $response->getResponse()); + return; + } + $payload = json_decode($response->getResponse(), true); if ($payload && array_key_exists("detail", $payload)) { @@ -724,7 +730,7 @@ public function localFlags(): HttpResponse } return $this->httpClient->sendRequest( - '/api/feature_flag/local_evaluation?send_cohorts&token=' . $this->apiKey, + '/flags/definitions?send_cohorts&token=' . $this->apiKey, null, $headers, [ diff --git a/test/EtagSupportTest.php b/test/EtagSupportTest.php index eeb3aec..b9878ab 100644 --- a/test/EtagSupportTest.php +++ b/test/EtagSupportTest.php @@ -285,9 +285,8 @@ public function testUpdatesEtagWhen304ResponseIncludesNewEtag(): void public function testProcessesErrorResponseWithoutFlagsKey(): void { - // This test verifies current behavior: error responses without a 'flags' key - // will result in empty flags (due to $payload['flags'] ?? []) - // This is pre-existing behavior that's consistent with or without ETag support + // Server error responses (non-200) log and return gracefully, + // preserving previously loaded flags. $this->http_client = new MockedHttpClient( host: "app.posthog.com", @@ -311,15 +310,9 @@ public function testProcessesErrorResponseWithoutFlagsKey(): void ['response' => ['error' => 'Internal Server Error'], 'etag' => null, 'responseCode' => 500] ]); - // loadFlags will parse the response and set featureFlags to [] - // This is pre-existing behavior: error responses clear flags + // loadFlags logs the error and returns — flags are preserved from the first load $this->client->loadFlags(); - - // Flags are cleared because response doesn't have 'flags' key - // ($payload['flags'] ?? [] evaluates to []) - $this->assertCount(0, $this->client->featureFlags); - - // ETag is set to null (from the response) - $this->assertNull($this->client->getFlagsEtag()); + $this->assertCount(1, $this->client->featureFlags); + $this->assertEquals('person-flag', $this->client->featureFlags[0]['key']); } } diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 66580fc..13cc28a 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1081,7 +1081,7 @@ public function testFlagPersonBooleanProperties() $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -1325,20 +1325,25 @@ public function testLoadFeatureFlags() public function testLoadFeatureFlagsWrongKey() { - self::expectException(Exception::class); + // Non-200 responses log and return gracefully (no exception). + // Flags remain empty. $this->http_client = new MockedHttpClient( host: "app.posthog.com", - flagEndpointResponse: ["detail" => "Invalid personal API key."] + flagEndpointResponse: ["detail" => "Invalid personal API key."], + flagEndpointResponseCode: 401 ); $this->client = new Client( self::FAKE_API_KEY, [ "debug" => true, + "host" => "localhost:39876", ], $this->http_client, self::FAKE_API_KEY ); PostHog::init(null, null, $this->client); + + $this->assertEmpty($this->client->featureFlags); } public function testSimpleFlag() @@ -1364,7 +1369,7 @@ public function testSimpleFlag() $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -1462,7 +1467,7 @@ public function testComputingInactiveFlagLocally() $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -1497,7 +1502,7 @@ public function testFeatureFlagsLocalEvaluationForCohorts() $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -1568,7 +1573,7 @@ public function testFeatureFlagsLocalEvaluationForNegatedCohorts() $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index e8e433a..2ad7b10 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -65,7 +65,7 @@ public function testIsFeatureEnabled($response) $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -135,7 +135,7 @@ public function testIsFeatureEnabledGroups($response) $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -164,7 +164,7 @@ public function testGetFeatureFlag($response) $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -231,7 +231,7 @@ public function testGetFeatureFlagGroups($response) $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -493,9 +493,9 @@ public function testGetFeatureFlagResultWithLocalEvaluationOnly(): void $this->assertEquals('simple-flag', $result->getKey()); $this->assertTrue($result->isEnabled()); - // Verify no /flags/ network call was made + // Verify no decide (/flags/?v=2) call was made foreach ($this->http_client->calls as $call) { - $this->assertStringNotContainsString('/flags/', $call['path'], 'Expected no /flags/ call for local evaluation'); + $this->assertFalse(str_starts_with($call['path'], '/flags/?'), 'Expected no decide call for local evaluation'); } } @@ -527,9 +527,9 @@ public function testGetFeatureFlagResultReturnsNullForLocalEvaluationWhenFlagCan $this->assertNull($result); - // Verify no /flags/ network call was made + // Verify no decide (/flags/?v=2) call was made foreach ($this->http_client->calls as $call) { - $this->assertStringNotContainsString('/flags/', $call['path'], 'Expected no /flags/ call for local evaluation only'); + $this->assertFalse(str_starts_with($call['path'], '/flags/?'), 'Expected no decide call for local evaluation only'); } } @@ -554,7 +554,7 @@ public function testGetFeatureFlagResultWithGroups($response): void // Verify that groups were passed in the /flags/ request $flagsCall = null; foreach ($this->http_client->calls as $call) { - if (str_contains($call['path'], '/flags/')) { + if (str_starts_with($call['path'], '/flags/?')) { $flagsCall = $call; break; } diff --git a/test/MockedHttpClient.php b/test/MockedHttpClient.php index 28b5b7b..1d2c11f 100644 --- a/test/MockedHttpClient.php +++ b/test/MockedHttpClient.php @@ -75,16 +75,8 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders } array_push($this->calls, array("path" => $path, "payload" => $payload, "extraHeaders" => $extraHeaders, "requestOptions" => $requestOptions)); - if (str_starts_with($path, "/flags/")) { - return new HttpResponse( - json_encode($this->flagsEndpointResponse), - $this->flagsEndpointResponseCode, - null, - $this->flagsEndpointCurlErrno - ); - } - - if (str_starts_with($path, "/api/feature_flag/local_evaluation")) { + // Local evaluation endpoint: /flags/definitions?... + if (str_starts_with($path, "/flags/definitions")) { // Check if we have a response queue if ($this->flagEndpointResponseQueue !== null && !empty($this->flagEndpointResponseQueue)) { $nextResponse = array_shift($this->flagEndpointResponseQueue); @@ -112,6 +104,16 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders ); } + // Decide endpoint: /flags/?v=2 + if (str_starts_with($path, "/flags/?")) { + return new HttpResponse( + json_encode($this->flagsEndpointResponse), + $this->flagsEndpointResponseCode, + null, + $this->flagsEndpointCurlErrno + ); + } + return parent::sendRequest($path, $payload, $extraHeaders, $requestOptions); } } diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 44d5ce8..f2fc875 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -253,7 +253,7 @@ public function testCaptureWithSendFeatureFlagsOption(): void $this->http_client->calls, array ( 0 => array ( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -309,7 +309,7 @@ public function testCaptureWithLocalSendFlags(): void $this->http_client->calls, array ( 0 => array ( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -358,7 +358,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void $this->http_client->calls, array ( 0 => array ( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -537,7 +537,7 @@ public function testDefaultPropertiesGetAddedProperly(): void $this->http_client->calls, array( 0 => array( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true), @@ -637,7 +637,7 @@ public function testCaptureWithSendFeatureFlagsFalse(): void $this->http_client->calls, array ( 0 => array ( - "path" => "/api/feature_flag/local_evaluation?send_cohorts&token=random_key", + "path" => "/flags/definitions?send_cohorts&token=random_key", "payload" => null, "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION, 1 => 'Authorization: Bearer test'), "requestOptions" => array("includeEtag" => true),