Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/busy-buttons-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"posthog-php": patch
---

Allow shared flag definition cache providers to load and refresh local evaluation definitions without a secret key.
30 changes: 22 additions & 8 deletions lib/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,9 @@ class Client implements FeatureFlagEvaluationsHost
* @param HttpClient|null $httpClient Custom HTTP client, primarily for tests and advanced integrations.
* @param string|null $personalAPIKey Deprecated: use $secretKey instead. Kept as a
* backwards-compatible alias for $secretKey.
* @param bool $loadFeatureFlags Whether to load local feature flag definitions during construction.
* @param string|null $secretKey Credential for local feature flag evaluation and remote config.
* @param bool $loadFeatureFlags Whether to load local feature flag definitions during construction
* from a configured cache provider or the PostHog API.
* @param string|null $secretKey Credential for fetching local feature flag definitions and remote config.
* Accepts either a Personal API Key (`phx_...`) or a Project Secret API Key (`phs_...`).
* Defaults to null. When both $secretKey and $personalAPIKey are provided, $secretKey wins.
*/
Expand Down Expand Up @@ -323,7 +324,7 @@ public function __construct(
if (
$this->enabled
&& count($this->featureFlags) == 0
&& !is_null($this->secretKey)
&& ($this->secretKey !== null || $this->flagDefinitionCacheProvider !== null)
&& $loadFeatureFlags
) {
$this->loadFlags();
Expand Down Expand Up @@ -1490,7 +1491,9 @@ private function fetchFlagsResponse(
}

/**
* Load local feature flag definitions using the configured secret key.
* Load local feature flag definitions from the configured cache provider or PostHog API.
* Without a secret key, provider-backed clients only read the shared cache.
* Direct API requests require a secret key.
*
* @return void
* @throws Exception
Expand Down Expand Up @@ -1523,17 +1526,16 @@ public function loadFlags()
} elseif ($this->hasFlagDefinitionsLoaded()) {
return;
}

$shouldFetch = !is_null($this->secretKey);
} catch (Throwable $throwable) {
$this->logFlagDefinitionCacheWarning(
'Cache provider read error: ' . $throwable->getMessage()
);
if ($this->hasFlagDefinitionsLoaded()) {
return;
}
$shouldFetch = !is_null($this->secretKey);
}

$shouldFetch = $this->secretKey !== null;
}

if ($shouldFetch) {
Expand All @@ -1552,13 +1554,18 @@ private function shouldFetchFlagDefinitionsFromApi(): bool
return true;
}

// Cache-only readers must not acquire fetch leadership through the provider.
if ($this->secretKey === null) {
return false;
}

try {
return $this->flagDefinitionCacheProvider->shouldFetchFlagDefinitions();
} catch (Throwable $throwable) {
$this->logFlagDefinitionCacheWarning(
'Cache provider fetch-decision error: ' . $throwable->getMessage()
);
return !is_null($this->secretKey);
return true;
Comment thread
dustinbyrne marked this conversation as resolved.
}
}

Expand Down Expand Up @@ -1765,6 +1772,7 @@ private function logFlagDefinitionCacheWarning(string $message): void
* Fetch local feature flag definitions from the PostHog API.
*
* @return HttpResponse Raw HTTP response, including ETag metadata when available.
* Returns a 401 response without making a request when no secret key is configured.
*/
public function localFlags(): HttpResponse
{
Expand All @@ -1779,6 +1787,12 @@ public function localFlags(): HttpResponse
);
}

if ($this->secretKey === null) {
$message = 'A secret key is required to fetch flag definitions from PostHog';
$this->logWarning($message);
return new HttpResponse($message, 401);
}

$headers = [
// Send user agent in the form of {library_name}/{library_version} as per RFC 7231.
"User-Agent: " . PostHog::LIBRARY . "/" . PostHog::VERSION,
Expand Down
12 changes: 10 additions & 2 deletions lib/FlagDefinitionCacheProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
* Implement this interface to share downloaded flag definitions across PHP workers, serverless
* invocations, or other distributed SDK instances. Provider methods are called synchronously by the
* SDK and any thrown error is logged as a warning without crashing application code.
*
* A project API key is required. Without a secret key, the SDK only reads shared definitions and
* never calls shouldFetchFlagDefinitions() or publishes definitions. With a secret key, the SDK
* consults the provider to decide whether to fetch and publish or read the cache. Definitions load
* during construction unless loadFeatureFlags is false; call Client::loadFlags() to refresh.
*/
interface FlagDefinitionCacheProvider
{
Expand All @@ -28,8 +33,11 @@ public function getFlagDefinitions(): ?array;
/**
* Decide whether this SDK instance should fetch fresh definitions from PostHog.
*
* Return false when another worker is responsible for fetching and this instance should read the
* latest definitions from getFlagDefinitions() instead.
* Called only when a secret key is configured. Return false when another worker is responsible
* for fetching and this instance should read the latest definitions from getFlagDefinitions()
* instead. Return true to fetch from the API and publish the result. Empty or unavailable caches
* preserve previously loaded definitions; with no loaded definitions, a keyed client may fetch
* directly to recover from a cache miss or read failure.
*
* @return bool
*/
Expand Down
Loading
Loading