Skip to content
Merged
8 changes: 7 additions & 1 deletion lib/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Comment thread
marandaneto marked this conversation as resolved.

$payload = json_decode($response->getResponse(), true);

if ($payload && array_key_exists("detail", $payload)) {
Expand Down Expand Up @@ -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,
[
Expand Down
17 changes: 5 additions & 12 deletions test/EtagSupportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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']);
}
}
19 changes: 12 additions & 7 deletions test/FeatureFlagLocalEvaluationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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()
Expand All @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
18 changes: 9 additions & 9 deletions test/FeatureFlagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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');
}
}

Expand Down Expand Up @@ -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');
}
}

Expand All @@ -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;
}
Expand Down
22 changes: 12 additions & 10 deletions test/MockedHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
10 changes: 5 additions & 5 deletions test/PostHogTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down
Loading