From 126d95a33606eedd52593357fc8fcf100643298d Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 20 May 2026 11:14:18 +0200 Subject: [PATCH 1/3] docs: document public SDK APIs --- lib/Client.php | 209 +++++++++++++++------- lib/Consumer.php | 26 +-- lib/Consumer/File.php | 24 ++- lib/Consumer/ForkCurl.php | 16 +- lib/Consumer/LibCurl.php | 17 +- lib/Consumer/Socket.php | 18 +- lib/EvaluatedFlagRecord.php | 16 ++ lib/ExceptionCapture.php | 47 ++++- lib/ExceptionPayloadBuilder.php | 20 ++- lib/FeatureFlag.php | 85 ++++++++- lib/FeatureFlagError.php | 5 + lib/FeatureFlagEvaluations.php | 29 ++- lib/FeatureFlagEvaluationsHost.php | 15 +- lib/FeatureFlagResult.php | 18 ++ lib/HttpClient.php | 24 ++- lib/HttpResponse.php | 17 ++ lib/InconclusiveMatchException.php | 10 ++ lib/PostHog.php | 192 ++++++++++++++------ lib/QueueConsumer.php | 32 ++-- lib/RequestContext.php | 13 ++ lib/RequiresServerEvaluationException.php | 10 ++ lib/SizeLimitedHash.php | 29 +++ lib/StringNormalizer.php | 17 ++ lib/Uuid.php | 11 ++ 24 files changed, 718 insertions(+), 182 deletions(-) diff --git a/lib/Client.php b/lib/Client.php index 768f7a4..ff3f082 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -11,6 +11,9 @@ const SIZE_LIMIT = 50_000; +/** + * PostHog PHP SDK client for event capture, user identification, feature flags, and error tracking. + */ class Client implements FeatureFlagEvaluationsHost { private const CONSUMERS = [ @@ -94,12 +97,34 @@ class Client implements FeatureFlagEvaluationsHost private array $missingDistinctIdWarnings = []; /** - * Create a new posthog object with your app's API key - * key + * Create a new PostHog client with your project's API key. * - * @param string $apiKey - * @param array $options array of consumer options [optional] - * @param HttpClient|null $httpClient + * @param string $apiKey Your project API key. + * @param array{ + * host?: string, + * ssl?: bool, + * timeout?: int, + * verify_batch_events_request?: bool, + * feature_flag_request_timeout_ms?: int, + * maximum_backoff_duration?: int, + * consumer?: 'socket'|'file'|'fork_curl'|'lib_curl', + * debug?: bool, + * max_queue_size?: int, + * batch_size?: int, + * compress_request?: bool|string, + * error_handler?: callable, + * filename?: string, + * error_tracking?: array{ + * enabled?: bool, + * capture_errors?: bool, + * excluded_exceptions?: list, + * max_frames?: int, + * context_provider?: callable + * } + * } $options Client and consumer configuration options. + * @param HttpClient|null $httpClient Custom HTTP client, primarily for tests and advanced integrations. + * @param string|null $personalAPIKey Personal API key used to load local feature flag definitions. + * @param bool $loadFeatureFlags Whether to load local feature flag definitions during construction. */ public function __construct( string $apiKey, @@ -147,16 +172,30 @@ public function __construct( } } + /** + * Flush and clean up the underlying consumer when the client is destroyed. + */ public function __destruct() { $this->consumer->__destruct(); } /** - * Captures a user action + * Captures a user action. * - * @param array $message - * @return bool whether the capture call succeeded + * @param array{ + * event: string, + * distinctId?: string, + * distinct_id?: string, + * properties?: array, + * groups?: array, + * timestamp?: mixed, + * flags?: FeatureFlagEvaluations, + * send_feature_flags?: bool, + * sendFeatureFlags?: bool + * } $message Event payload. `send_feature_flags` and `sendFeatureFlags` are deprecated; pass + * a `flags` snapshot from evaluateFlags() instead. + * @return bool Whether the capture call succeeded. */ public function capture(array $message) { @@ -271,8 +310,8 @@ public function captureException( /** * Tags properties about the user. * - * @param array $message - * @return bool whether the identify call succeeded + * @param array{distinctId?: string, distinct_id?: string, properties?: array} $message + * @return bool Whether the identify call succeeded. */ public function identify(array $message) { @@ -292,12 +331,14 @@ public function identify(array $message) * `$flags->isEnabled($key)` instead. This consolidates flag evaluation into a single * `/flags` request per incoming request. * - * @param string $key + * @param string $key Feature flag key. * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return bool + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. + * @return bool|null * @throws Exception */ public function isFeatureEnabled( @@ -341,12 +382,14 @@ public function isFeatureEnabled( * `$flags->getFlag($key)` instead. This consolidates flag evaluation into a single * `/flags` request per incoming request. * - * @param string $key + * @param string $key Feature flag key. * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return bool | string + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. + * @return bool|string|null * @throws Exception */ public function getFeatureFlag( @@ -385,13 +428,13 @@ public function getFeatureFlag( * `$flags->getFlagPayload($key)` instead. This consolidates flag evaluation into a single * `/flags` request per incoming request. * - * @param string $key + * @param string $key Feature flag key. * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @param bool $onlyEvaluateLocally - * @param bool $sendFeatureFlagEvents + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. * @return FeatureFlagResult|null * @throws Exception */ @@ -587,11 +630,11 @@ private function doGetFeatureFlagResult( * `$flags->getFlagPayload($key)` instead. This consolidates flag evaluation into a single * `/flags` request per incoming request. * - * @param string $key + * @param string $key Feature flag key. * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. * @return mixed */ public function getFeatureFlagPayload( @@ -627,10 +670,11 @@ public function getFeatureFlagPayload( * get the feature flag value for this distinct id. * * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return array + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @return array * @throws Exception */ public function getAllFlags( @@ -697,14 +741,18 @@ public function getAllFlags( * requests; access via isEnabled() or getFlag() fires a deduped $feature_flag_called event the * first time each key is touched. * - * @param array $groups - * @param array $personProperties - * @param array> $groupProperties + * @param string|null $distinctId Defaults to the current request context distinctId, when set. + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $disableGeoip Whether to disable GeoIP enrichment during remote evaluation. * @param list|null $flagKeys Optional list of flag keys. When provided, only these * flags are evaluated — the underlying /flags request asks the server for just this * subset, which makes the response smaller and the request cheaper. Use this when you * only need a handful of flags out of many. Distinct from FeatureFlagEvaluations::only(), * which scopes which already-evaluated flags get attached to a captured event. + * @return FeatureFlagEvaluations */ public function evaluateFlags( ?string $distinctId = null, @@ -893,8 +941,11 @@ public function evaluateFlags( * path. Properties are built by the caller so each call site can shape the payload to match its * available metadata. * - * @param array $properties - * @param array $groups + * @param string $distinctId The distinct ID that accessed the flag. + * @param string $key Feature flag key. + * @param array $properties Event properties for the $feature_flag_called event. + * @param array $groups Group identifiers for group-based flags. + * @return void */ public function captureFlagCalledIfNeeded( string $distinctId, @@ -915,6 +966,12 @@ public function captureFlagCalledIfNeeded( $this->distinctIdsFeatureFlagsReported->add($key, $distinctId); } + /** + * Emit a non-fatal SDK warning. + * + * @param string $message Warning message without the SDK prefix. + * @return void + */ public function logWarning(string $message): void { error_log("[PostHog][Client] " . $message); @@ -981,9 +1038,13 @@ private function computeFlagLocally( /** - * @param string $distinctId - * @param array $groups - * @return array of feature flags + * Fetch all feature flag variants for a distinct id from the remote /flags endpoint. + * + * @param string $distinctId The user's distinct ID. + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @return array Feature flag values by key. * @throws Exception */ public function fetchFeatureVariants( @@ -1012,9 +1073,11 @@ private function fetchFlagsResponse( } /** + * Load local feature flag definitions using the configured personal API key. + * + * @return void * @throws Exception */ - public function loadFlags() { $response = $this->localFlags(); @@ -1064,6 +1127,11 @@ public function loadFlags() } + /** + * Fetch local feature flag definitions from the PostHog API. + * + * @return HttpResponse Raw HTTP response, including ETag metadata when available. + */ public function localFlags(): HttpResponse { $headers = [ @@ -1158,12 +1226,14 @@ private function normalizeFeatureFlags(string $response): array /** * Fetch feature flags from the PostHog API. * - * @param string $distinctId The user's distinct ID - * @param array $groups Group identifiers - * @param array $personProperties Person properties for flag evaluation - * @param array $groupProperties Group properties for flag evaluation - * @return array The normalized feature flags response - * @throws HttpException On network errors, API errors, or quota limits + * @param string $distinctId The user's distinct ID. + * @param array $groups Group identifiers. + * @param array $personProperties Person properties for flag evaluation. + * @param array> $groupProperties Group properties for flag evaluation. + * @param bool $disableGeoip Whether to disable GeoIP enrichment during remote evaluation. + * @param list|null $flagKeys Optional list of flag keys to evaluate. + * @return array The normalized feature flags response. + * @throws HttpException On network errors, API errors, or quota limits. */ public function flags( string $distinctId, @@ -1248,10 +1318,15 @@ public function flags( } /** - * Aliases from one user id to another + * Aliases from one user id to another. * - * @param array $message - * @return boolean whether the alias call succeeded + * @param array{ + * distinctId?: string, + * distinct_id?: string, + * alias: string, + * properties?: array + * } $message + * @return bool Whether the alias call succeeded. */ public function alias(array $message) { @@ -1269,10 +1344,10 @@ public function alias(array $message) } /** - * Queue a raw (prepared) message + * Queue a raw, already-prepared message. * - * @param array $message - * @return mixed whether the identify call succeeded + * @param array $message Prepared message payload. + * @return mixed Whether the underlying consumer accepted the message. */ public function raw(array $message) { @@ -1280,8 +1355,9 @@ public function raw(array $message) } /** - * Flush any async consumers - * @return boolean true if flushed successfully + * Flush any async consumers. + * + * @return bool True if flushed successfully. */ public function flush() { @@ -1342,10 +1418,17 @@ private function formatTime($ts) /** * Run a callback with request context applied to all captures in the callback. * - * @param array $data - * @param callable $fn - * @param array $options + * @param array{ + * distinctId?: string, + * distinct_id?: string, + * sessionId?: string, + * session_id?: string, + * properties?: array + * } $data + * @param callable $fn Callback to run while the context is active. + * @param array{fresh?: bool} $options Use `fresh => true` to avoid inheriting the current context. * @return mixed + * @throws \Throwable Re-throws any exception thrown by $fn after restoring context. */ public function withContext(array $data, callable $fn, array $options = []): mixed { @@ -1353,6 +1436,8 @@ public function withContext(array $data, callable $fn, array $options = []): mix } /** + * Get the currently active request context for this client, if any. + * * @return array{distinctId?: string|null, sessionId?: string|null, properties: array}|null */ public function getContext(): ?array @@ -1361,7 +1446,9 @@ public function getContext(): ?array } /** - * @param array $headers + * Extract PostHog frontend tracing context from HTTP headers. + * + * @param array $headers HTTP headers, including $_SERVER-style HTTP_* keys. * @return array{distinctId?: string|null, sessionId?: string|null, properties: array} */ public function contextFromHeaders(array $headers): array diff --git a/lib/Consumer.php b/lib/Consumer.php index 4ebd733..330fc38 100644 --- a/lib/Consumer.php +++ b/lib/Consumer.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Base class for analytics event consumers. + * + * @internal + */ abstract class Consumer { protected $type = "Consumer"; @@ -10,9 +15,10 @@ abstract class Consumer protected $apiKey; /** - * Store our apiKey and options as part of this consumer - * @param string $apiKey - * @param array $options + * Store our apiKey and options as part of this consumer. + * + * @param string $apiKey Project API key. + * @param array $options Consumer options. */ public function __construct($apiKey, $options = array()) { @@ -21,26 +27,26 @@ public function __construct($apiKey, $options = array()) } /** - * Captures a user action + * Captures a user action. * - * @param array $message - * @return boolean whether the capture call succeeded + * @param array $message Event payload. + * @return bool Whether the capture call succeeded. */ abstract public function capture(array $message); /** * Tags properties about the user. * - * @param array $message - * @return boolean whether the identify call succeeded + * @param array $message Identify payload. + * @return bool Whether the identify call succeeded. */ abstract public function identify(array $message); /** * Aliases from one user id to another * - * @param array $message - * @return boolean whether the alias call succeeded + * @param array $message Alias payload. + * @return bool Whether the alias call succeeded. */ abstract public function alias(array $message); diff --git a/lib/Consumer/File.php b/lib/Consumer/File.php index dbe58ac..dc4f6e3 100644 --- a/lib/Consumer/File.php +++ b/lib/Consumer/File.php @@ -5,6 +5,11 @@ use Exception; use PostHog\Consumer; +/** + * Consumer that writes analytics messages to a local file. + * + * @internal + */ class File extends Consumer { protected $type = "File"; @@ -32,6 +37,9 @@ public function __construct($apiKey, $options = array()) } } + /** + * Close the file handle when the consumer is destroyed. + */ public function __destruct() { if ($this->file_handle && "Unknown" != get_resource_type($this->file_handle)) { @@ -52,8 +60,8 @@ public function getConsumer() /** * Captures a user action * - * @param array $message - * @return bool whether the capture call succeeded + * @param array $message Event payload. + * @return bool Whether the capture call succeeded. */ public function capture(array $message) { @@ -63,8 +71,8 @@ public function capture(array $message) /** * Tags properties about the user. * - * @param array $message - * @return bool whether the identify call succeeded + * @param array $message Identify payload. + * @return bool Whether the identify call succeeded. */ public function identify(array $message) { @@ -74,8 +82,8 @@ public function identify(array $message) /** * Aliases from one user id to another * - * @param array $message - * @return boolean whether the alias call succeeded + * @param array $message Alias payload. + * @return bool Whether the alias call succeeded. */ public function alias(array $message) { @@ -84,8 +92,8 @@ public function alias(array $message) /** * Writes the API call to a file as line-delimited json - * @param array $body post body content. - * @return bool whether the request succeeded + * @param array $body Post body content. + * @return bool Whether the request succeeded. */ private function write($body) { diff --git a/lib/Consumer/ForkCurl.php b/lib/Consumer/ForkCurl.php index 13edc11..23fb965 100644 --- a/lib/Consumer/ForkCurl.php +++ b/lib/Consumer/ForkCurl.php @@ -4,6 +4,11 @@ use PostHog\QueueConsumer; +/** + * Queue consumer that sends batches through a forked curl process. + * + * @internal + */ class ForkCurl extends QueueConsumer { protected $type = "ForkCurl"; @@ -11,11 +16,8 @@ class ForkCurl extends QueueConsumer /** * Creates a new queued fork consumer which queues fork and identify * calls before adding them to - * @param string $apiKey - * @param array $options - * boolean "debug" - whether to use debug output, wait for response. - * number "max_queue_size" - the max size of messages to enqueue - * number "batch_size" - how many messages to send in a single request + * @param string $apiKey Project API key. + * @param array $options Consumer options. */ public function __construct($apiKey, $options = array()) { @@ -35,8 +37,8 @@ public function getConsumer() /** * Make an async request to our API. Fork a curl process, immediately send * to the API. If debug is enabled, we wait for the response. - * @param array $messages array of all the messages to send - * @return boolean whether the request succeeded + * @param array> $messages Array of messages to send. + * @return bool Whether the request succeeded. */ public function flushBatch($messages) { diff --git a/lib/Consumer/LibCurl.php b/lib/Consumer/LibCurl.php index 89d2679..f07e22e 100644 --- a/lib/Consumer/LibCurl.php +++ b/lib/Consumer/LibCurl.php @@ -5,6 +5,11 @@ use PostHog\HttpClient; use PostHog\QueueConsumer; +/** + * Queue consumer that sends batches using libcurl. + * + * @internal + */ class LibCurl extends QueueConsumer { protected $type = "LibCurl"; @@ -15,11 +20,9 @@ class LibCurl extends QueueConsumer /** * Creates a new queued libcurl consumer - * @param string $apiKey - * @param array $options - * boolean "debug" - whether to use debug output, wait for response. - * number "max_queue_size" - the max size of messages to enqueue - * number "batch_size" - how many messages to send in a single request + * @param string $apiKey Project API key. + * @param array $options Consumer options. + * @param HttpClient|null $httpClient Custom HTTP client, primarily for tests. */ public function __construct($apiKey, $options = [], ?HttpClient $httpClient = null) { @@ -48,8 +51,8 @@ public function getConsumer() * Make a sync request to our API. If debug is * enabled, we wait for the response * and retry once to diminish impact on performance. - * @param array $messages array of all the messages to send - * @return boolean whether the request succeeded + * @param array> $messages Array of messages to send. + * @return bool Whether the request succeeded. */ public function flushBatch($messages) { diff --git a/lib/Consumer/Socket.php b/lib/Consumer/Socket.php index ba82ae7..c2845ae 100644 --- a/lib/Consumer/Socket.php +++ b/lib/Consumer/Socket.php @@ -5,6 +5,11 @@ use Exception; use PostHog\QueueConsumer; +/** + * Queue consumer that sends batches over a raw socket. + * + * @internal + */ class Socket extends QueueConsumer { protected $type = "Socket"; @@ -12,11 +17,8 @@ class Socket extends QueueConsumer /** * Creates a new socket consumer for dispatching async requests immediately - * @param string $apiKey - * @param array $options - * number "timeout" - the timeout for connecting - * function "error_handler" - function called back on errors. - * boolean "debug" - whether to use debug output, wait for response. + * @param string $apiKey Project API key. + * @param array $options Consumer options. */ public function __construct($apiKey, $options = array()) { @@ -37,6 +39,12 @@ public function getConsumer() return $this->type; } + /** + * Send a batch of queued messages. + * + * @param array> $batch Batch of queued messages. + * @return bool Whether the request succeeded. + */ public function flushBatch($batch) { $socket = $this->createSocket(); diff --git a/lib/EvaluatedFlagRecord.php b/lib/EvaluatedFlagRecord.php index f45f16a..ee8e6b0 100644 --- a/lib/EvaluatedFlagRecord.php +++ b/lib/EvaluatedFlagRecord.php @@ -4,9 +4,23 @@ /** * Immutable per-flag entry stored on a FeatureFlagEvaluations snapshot. + * + * @internal */ final class EvaluatedFlagRecord { + /** + * Create an evaluated flag record. + * + * @param string $key Feature flag key. + * @param bool $enabled Whether the flag is enabled. + * @param string|null $variant Variant key for multivariate flags. + * @param mixed $payload Decoded JSON payload associated with this flag. + * @param int|null $id Feature flag ID, when provided by the API. + * @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. + */ public function __construct( public readonly string $key, public readonly bool $enabled, @@ -21,6 +35,8 @@ public function __construct( /** * The value as $feature_flag_response would render it: variant string when set, otherwise the enabled bool. + * + * @return bool|string */ public function getValue(): bool|string { diff --git a/lib/ExceptionCapture.php b/lib/ExceptionCapture.php index d1ba197..32c18c6 100644 --- a/lib/ExceptionCapture.php +++ b/lib/ExceptionCapture.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Installs and runs automatic PHP exception/error capture handlers. + * + * @internal + */ class ExceptionCapture { private const SHUTDOWN_FATAL_ERROR_TYPES = [ @@ -45,7 +50,17 @@ class ExceptionCapture private static array $delegatedErrorExceptionIds = []; /** - * @param array $config Contents of the 'error_tracking' options subkey. + * Configure automatic error tracking for a client. + * + * @param Client $client Client used to send captured exception events. + * @param array{ + * enabled?: bool, + * capture_errors?: bool, + * excluded_exceptions?: list, + * max_frames?: int, + * context_provider?: callable + * } $config Contents of the 'error_tracking' options subkey. + * @return void */ public static function configure(Client $client, array $config): void { @@ -82,6 +97,12 @@ public static function configure(Client $client, array $config): void } } + /** + * Handle an uncaught exception from PHP's exception handler. + * + * @param \Throwable $exception Uncaught exception. + * @return void + */ public static function handleException(\Throwable $exception): void { if (self::consumeDelegatedErrorException($exception)) { @@ -104,6 +125,15 @@ public static function handleException(\Throwable $exception): void self::finishUnhandledException($exception); } + /** + * Handle a PHP error from PHP's error handler. + * + * @param int $errno PHP error severity. + * @param string $message Error message. + * @param string $file Source file path. + * @param int $line Source line number. + * @return bool Whether the error was handled. + */ public static function handleError( int $errno, string $message, @@ -186,7 +216,10 @@ public static function handleError( } /** - * @param array|null $lastError + * Handle a fatal PHP error from PHP's shutdown handler. + * + * @param array|null $lastError Last PHP error, or null to read error_get_last(). + * @return void */ public static function handleShutdown(?array $lastError = null): void { @@ -244,6 +277,11 @@ public static function handleShutdown(?array $lastError = null): void self::flushSafely(); } + /** + * Reset installed handlers and static state for tests. + * + * @return void + */ public static function resetForTests(): void { if (self::$exceptionHandlerInstalled) { @@ -266,6 +304,11 @@ public static function resetForTests(): void self::$throwOnUnhandledInTests = false; } + /** + * Re-throw unhandled exceptions instead of exiting, for tests. + * + * @return void + */ public static function enableThrowOnUnhandledForTests(): void { self::$throwOnUnhandledInTests = true; diff --git a/lib/ExceptionPayloadBuilder.php b/lib/ExceptionPayloadBuilder.php index fd57e1b..9f60ffa 100644 --- a/lib/ExceptionPayloadBuilder.php +++ b/lib/ExceptionPayloadBuilder.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Builds PostHog error tracking exception payloads. + * + * @internal + */ class ExceptionPayloadBuilder { private const CONTEXT_LINES = 5; @@ -41,6 +46,11 @@ public static function buildExceptionList( /** * Build a single exception entry from a Throwable using a custom trace. + * + * @param \Throwable $exception Exception to serialize. + * @param array> $trace Normalized stack trace frames. + * @param int $maxFrames Maximum number of frames to include. + * @return array */ public static function buildFromTrace( \Throwable $exception, @@ -57,6 +67,13 @@ public static function buildFromTrace( /** * Build a single exception entry from type, message, and file/line location. + * + * @param string $type Exception type name. + * @param string $message Exception message. + * @param string|null $file Source file path, when known. + * @param int|null $line Source line number, when known. + * @param int $maxFrames Maximum number of frames to include. + * @return array */ public static function buildFromLocation( string $type, @@ -98,7 +115,8 @@ public static function overridePrimaryMechanism(array $exceptionList, array $mec /** * Get the handled flag from the primary (first) exception. * - * @param array[] $exceptionList + * @param array[] $exceptionList Exception entries. + * @return bool */ public static function getPrimaryHandled(array $exceptionList): bool { diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index 39d2311..aba5f6a 100644 --- a/lib/FeatureFlag.php +++ b/lib/FeatureFlag.php @@ -6,8 +6,21 @@ const LONG_SCALE = 0xfffffffffffffff; +/** + * Local feature flag matching helpers. + * + * @internal + */ class FeatureFlag { + /** + * Match a single property filter against provided property values. + * + * @param array $property Feature flag property filter. + * @param array $propertyValues Available property values keyed by property name. + * @return bool Whether the property matches. + * @throws InconclusiveMatchException When the property cannot be evaluated locally. + */ public static function matchProperty($property, $propertyValues) { $key = $property["key"]; @@ -147,6 +160,19 @@ public static function matchProperty($property, $propertyValues) return false; } + /** + * Match a cohort property filter against provided property values. + * + * @param array $property Cohort property filter. + * @param array $propertyValues Available property values keyed by property name. + * @param array $cohortProperties Local cohort definitions keyed by cohort ID. + * @param array>|null $flagsByKey Local feature flag definitions keyed by key. + * @param array|null $evaluationCache Cache used for flag dependency evaluation. + * @param string|null $distinctId Distinct ID used for nested flag dependency evaluation. + * @return bool Whether the cohort matches. + * @throws RequiresServerEvaluationException When the cohort requires server-side data. + * @throws InconclusiveMatchException When the cohort cannot be evaluated locally. + */ public static function matchCohort($property, $propertyValues, $cohortProperties, $flagsByKey = null, $evaluationCache = null, $distinctId = null) { $cohortId = strval($property["value"]); @@ -161,6 +187,19 @@ public static function matchCohort($property, $propertyValues, $cohortProperties return FeatureFlag::matchPropertyGroup($propertyGroup, $propertyValues, $cohortProperties, $flagsByKey, $evaluationCache, $distinctId); } + /** + * Match a property group against provided property values. + * + * @param array|null $propertyGroup Property group definition. + * @param array $propertyValues Available property values keyed by property name. + * @param array $cohortProperties Local cohort definitions keyed by cohort ID. + * @param array>|null $flagsByKey Local feature flag definitions keyed by key. + * @param array|null $evaluationCache Cache used for flag dependency evaluation. + * @param string|null $distinctId Distinct ID used for nested flag dependency evaluation. + * @return bool Whether the property group matches. + * @throws RequiresServerEvaluationException When server-side data is required. + * @throws InconclusiveMatchException When the group cannot be evaluated locally. + */ public static function matchPropertyGroup($propertyGroup, $propertyValues, $cohortProperties, $flagsByKey = null, $evaluationCache = null, $distinctId = null) { if (!$propertyGroup) { @@ -258,6 +297,12 @@ public static function matchPropertyGroup($propertyGroup, $propertyValues, $coho } } + /** + * Parse a PostHog relative date string for feature flag matching. + * + * @param mixed $value Relative date string such as `1h`, `7d`, `2w`, `3m`, or `1y`. + * @return \DateTime|null Parsed UTC date, or null when the value is invalid. + */ public static function relativeDateParseForFeatureFlagMatching($value) { $regex = "/^-?(?[0-9]+)(?[a-z])$/"; @@ -586,6 +631,22 @@ private static function variantLookupTable($featureFlag) return $lookupTable; } + /** + * Match a full feature flag definition for a distinct id and properties. + * + * @param array $flag Feature flag definition. + * @param string $distinctId Distinct ID or group key used for bucketing. + * @param array $properties Person or group properties for evaluation. + * @param array $cohorts Local cohort definitions keyed by cohort ID. + * @param array>|null $flagsByKey Local feature flag definitions keyed by key. + * @param array|null $evaluationCache Cache used for flag dependency evaluation. + * @param array $groups Group identifiers for group-based flags. + * @param array> $groupProperties Group properties for evaluation. + * @param array $groupTypeMapping Mapping from group type index to group type name. + * @return bool|string False for disabled, true for enabled boolean flags, or variant key. + * @throws RequiresServerEvaluationException When server-side data is required. + * @throws InconclusiveMatchException When the flag cannot be evaluated locally. + */ public static function matchFeatureFlagProperties( $flag, $distinctId, @@ -732,6 +793,18 @@ private static function prepareValueForRegex($value) return $regex; } + /** + * Evaluate a feature flag dependency property. + * + * @param array $property Flag dependency property. + * @param array>|null $flagsByKey Local feature flag definitions keyed by key. + * @param array|null $evaluationCache Cache used for dependency evaluation. + * @param string $distinctId Distinct ID used for bucketing. + * @param array $properties Person or group properties for evaluation. + * @param array $cohortProperties Local cohort definitions keyed by cohort ID. + * @return bool Whether the dependency matches. + * @throws InconclusiveMatchException When the dependency cannot be evaluated locally. + */ public static function evaluateFlagDependency($property, $flagsByKey, $evaluationCache, $distinctId, $properties, $cohortProperties) { if ($flagsByKey === null || $evaluationCache === null) { @@ -841,6 +914,13 @@ public static function evaluateFlagDependency($property, $flagsByKey, $evaluatio return true; } + /** + * Compare an expected flag dependency value with an evaluated flag value. + * + * @param mixed $expectedValue Expected dependency value from the property filter. + * @param mixed $actualValue Actual evaluated flag value. + * @return bool Whether the values match dependency semantics. + */ public static function matchesDependencyValue($expectedValue, $actualValue) { // String variant case - check for exact match or boolean true @@ -854,9 +934,8 @@ public static function matchesDependencyValue($expectedValue, $actualValue) } else { return false; } - } - // Boolean case - must match expected boolean value - elseif (is_bool($actualValue) && is_bool($expectedValue)) { + } elseif (is_bool($actualValue) && is_bool($expectedValue)) { + // Boolean case - must match expected boolean value return $actualValue === $expectedValue; } diff --git a/lib/FeatureFlagError.php b/lib/FeatureFlagError.php index 19c7e9c..215912e 100644 --- a/lib/FeatureFlagError.php +++ b/lib/FeatureFlagError.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Feature flag error codes used for $feature_flag_called event properties. + * + * @internal + */ class FeatureFlagError { /** diff --git a/lib/FeatureFlagEvaluations.php b/lib/FeatureFlagEvaluations.php index 34dbcba..e2c5e3b 100644 --- a/lib/FeatureFlagEvaluations.php +++ b/lib/FeatureFlagEvaluations.php @@ -15,8 +15,17 @@ class FeatureFlagEvaluations private array $accessed; /** - * @param array $flags - * @param array $groups + * Create a feature flag evaluation snapshot. + * + * @param string $distinctId Distinct ID the snapshot was evaluated for. + * @param array $flags Evaluated flags keyed by flag key. + * @param array $groups Group identifiers used for evaluation. + * @param FeatureFlagEvaluationsHost $host Owning host used to emit access events and warnings. + * @param string|null $requestId Remote /flags request ID, when available. + * @param int|null $evaluatedAt Remote evaluation timestamp, when available. + * @param array|null $accessed Flags already accessed on this snapshot. + * @param bool $errorsWhileComputing Whether the remote response reported computation errors. + * @param bool $quotaLimited Whether the remote response was quota limited. */ public function __construct( private readonly string $distinctId, @@ -33,6 +42,8 @@ public function __construct( } /** + * Get the keys of all flags included in this snapshot. + * * @return list */ public function getKeys(): array @@ -42,6 +53,9 @@ public function getKeys(): array /** * Whether the flag is enabled for the snapshot's distinct id. Returns false for unknown keys. + * + * @param string $key Feature flag key. + * @return bool */ public function isEnabled(string $key): bool { @@ -53,6 +67,9 @@ public function isEnabled(string $key): bool /** * Returns the variant (string), enabled state (bool), or null for unknown keys. + * + * @param string $key Feature flag key. + * @return bool|string|null */ public function getFlag(string $key): bool|string|null { @@ -69,6 +86,9 @@ public function getFlag(string $key): bool|string|null /** * Returns the decoded payload for a flag without recording access or firing a $feature_flag_called event. * Returns null for unknown keys or flags without a payload. + * + * @param string $key Feature flag key. + * @return mixed */ public function getFlagPayload(string $key): mixed { @@ -79,6 +99,8 @@ public function getFlagPayload(string $key): mixed * Returns a clone of this snapshot containing only flags that were previously accessed via * isEnabled() or getFlag(). Order-dependent: if nothing has been accessed yet, the returned * snapshot is empty. The method honors its name — pre-access if you want a populated result. + * + * @return self */ public function onlyAccessed(): self { @@ -96,7 +118,8 @@ public function onlyAccessed(): self * Returns a clone of this snapshot filtered to the given keys. Unknown keys are dropped with a * warning so silent typos don't slip into captured events. * - * @param list $keys + * @param list $keys Feature flag keys to keep. + * @return self */ public function only(array $keys): self { diff --git a/lib/FeatureFlagEvaluationsHost.php b/lib/FeatureFlagEvaluationsHost.php index 949adb5..2bdc376 100644 --- a/lib/FeatureFlagEvaluationsHost.php +++ b/lib/FeatureFlagEvaluationsHost.php @@ -7,6 +7,8 @@ * * Splitting the interface out keeps the snapshot easy to unit-test with a fake host instead of a * full Client. + * + * @internal */ interface FeatureFlagEvaluationsHost { @@ -17,8 +19,11 @@ interface FeatureFlagEvaluationsHost * The properties array is built by the caller so this helper does not need to reconstruct the * shape from raw response data. * - * @param array $properties - * @param array $groups + * @param string $distinctId The distinct ID that accessed the flag. + * @param string $key Feature flag key. + * @param array $properties Event properties for the $feature_flag_called event. + * @param array $groups Group identifiers for group-based flags. + * @return void */ public function captureFlagCalledIfNeeded( string $distinctId, @@ -28,8 +33,10 @@ public function captureFlagCalledIfNeeded( ): void; /** - * Emit a non-fatal warning. Implementations may suppress these when feature_flags_log_warnings - * is disabled in the client configuration. + * Emit a non-fatal warning. + * + * @param string $message Warning message. + * @return void */ public function logWarning(string $message): void; } diff --git a/lib/FeatureFlagResult.php b/lib/FeatureFlagResult.php index bcb3224..ef27d44 100644 --- a/lib/FeatureFlagResult.php +++ b/lib/FeatureFlagResult.php @@ -12,6 +12,14 @@ class FeatureFlagResult private ?string $variant; private mixed $payload; + /** + * Create a feature flag result. + * + * @param string $key Feature flag key. + * @param bool $enabled Whether the flag is enabled. + * @param string|null $variant Variant key for multivariate flags. + * @param mixed $payload Decoded JSON payload associated with this flag. + */ public function __construct( string $key, bool $enabled, @@ -26,6 +34,8 @@ public function __construct( /** * Get the feature flag key. + * + * @return string */ public function getKey(): string { @@ -34,6 +44,8 @@ public function getKey(): string /** * Whether the flag is enabled. + * + * @return bool */ public function isEnabled(): bool { @@ -42,6 +54,8 @@ public function isEnabled(): bool /** * Get the variant value if this is a multivariate flag. + * + * @return string|null */ public function getVariant(): ?string { @@ -50,6 +64,8 @@ public function getVariant(): ?string /** * Get the decoded JSON payload associated with this flag. + * + * @return mixed */ public function getPayload(): mixed { @@ -60,6 +76,8 @@ public function getPayload(): mixed * Get the flag value in the same format as getFeatureFlag(). * Returns the variant if set, otherwise the enabled boolean. * This matches the $feature_flag_response format. + * + * @return bool|string */ public function getValue(): bool|string { diff --git a/lib/HttpClient.php b/lib/HttpClient.php index 08f450f..f016e70 100644 --- a/lib/HttpClient.php +++ b/lib/HttpClient.php @@ -4,6 +4,9 @@ use Closure; +/** + * HTTP client used by the SDK for PostHog API requests. + */ class HttpClient { /** @@ -40,6 +43,17 @@ class HttpClient */ private $curlTimeoutMilliseconds; + /** + * Create an HTTP client. + * + * @param string $host PostHog host without protocol. + * @param bool $useSsl Whether to use HTTPS. + * @param int $maximumBackoffDuration Maximum retry backoff duration in milliseconds. + * @param bool $compressRequests Whether to gzip request bodies. + * @param bool $debug Whether to emit debug logs. + * @param Closure|null $errorHandler Optional callback invoked for request errors. + * @param int $curlTimeoutMilliseconds Default cURL timeout in milliseconds. + */ public function __construct( string $host, bool $useSsl = true, @@ -59,10 +73,12 @@ public function __construct( } /** - * @param string $path - * @param string|null $payload - * @param array $extraHeaders - * @param array $requestOptions + * Send a request to the configured PostHog host. + * + * @param string $path Request path, including leading slash. + * @param string|null $payload JSON request body, or null for no body. + * @param array $extraHeaders Additional cURL header strings. + * @param array{shouldRetry?: bool, shouldVerify?: bool, includeEtag?: bool, timeout?: int} $requestOptions * @return HttpResponse */ public function sendRequest(string $path, ?string $payload, array $extraHeaders = [], array $requestOptions = []): HttpResponse diff --git a/lib/HttpResponse.php b/lib/HttpResponse.php index bd061fe..cb5408c 100644 --- a/lib/HttpResponse.php +++ b/lib/HttpResponse.php @@ -2,6 +2,9 @@ namespace PostHog; +/** + * Response wrapper returned by the SDK HTTP client. + */ class HttpResponse { private $response; @@ -9,6 +12,14 @@ class HttpResponse private $etag; private $curlErrno; + /** + * Create an HTTP response wrapper. + * + * @param mixed $response Raw response body or false on cURL failure. + * @param int $responseCode HTTP status code, or 0 when no response was received. + * @param string|null $etag ETag response header, when requested and present. + * @param int $curlErrno cURL error number, or 0 when no cURL error occurred. + */ public function __construct($response, $responseCode, ?string $etag = null, int $curlErrno = 0) { $this->response = $response; @@ -18,6 +29,8 @@ public function __construct($response, $responseCode, ?string $etag = null, int } /** + * Get the raw response body. + * * @return mixed */ public function getResponse() @@ -26,6 +39,8 @@ public function getResponse() } /** + * Get the HTTP response code. + * * @return mixed */ public function getResponseCode() @@ -34,6 +49,8 @@ public function getResponseCode() } /** + * Get the ETag response header, when captured. + * * @return string|null */ public function getEtag(): ?string diff --git a/lib/InconclusiveMatchException.php b/lib/InconclusiveMatchException.php index 8ded475..cd27029 100644 --- a/lib/InconclusiveMatchException.php +++ b/lib/InconclusiveMatchException.php @@ -4,8 +4,18 @@ use Exception; +/** + * Raised when a feature flag cannot be evaluated conclusively with local data. + * + * @internal + */ class InconclusiveMatchException extends Exception { + /** + * Format the exception as an HTML-ish error message. + * + * @return string + */ public function errorMessage() { $errorMsg = 'Error on line ' . $this->getLine() . ' in ' . $this->getFile() . ': Inconclusive Match:' . $this->getMessage() . ''; //phpcs:ignore diff --git a/lib/PostHog.php b/lib/PostHog.php index 68f9cbf..59e3ced 100644 --- a/lib/PostHog.php +++ b/lib/PostHog.php @@ -4,6 +4,9 @@ use Exception; +/** + * Static facade for the default PostHog PHP SDK client. + */ class PostHog { public const VERSION = '4.4.0'; @@ -14,10 +17,38 @@ class PostHog /** * Initializes the default client to use. Uses the libcurl consumer by default. - * @param string|null $apiKey your project's API key - * @param array|null $options passed straight to the client - * @param Client|null $client - * @throws Exception + * + * When $apiKey or the host option are omitted, POSTHOG_API_KEY and POSTHOG_HOST are used + * when present. + * + * @param string|null $apiKey Your project API key. + * @param array{ + * host?: string, + * ssl?: bool, + * timeout?: int, + * verify_batch_events_request?: bool, + * feature_flag_request_timeout_ms?: int, + * maximum_backoff_duration?: int, + * consumer?: 'socket'|'file'|'fork_curl'|'lib_curl', + * debug?: bool, + * max_queue_size?: int, + * batch_size?: int, + * compress_request?: bool|string, + * error_handler?: callable, + * filename?: string, + * error_tracking?: array{ + * enabled?: bool, + * capture_errors?: bool, + * excluded_exceptions?: list, + * max_frames?: int, + * context_provider?: callable + * } + * }|null $options Client and consumer configuration options. + * @param Client|null $client Preconfigured client instance. When provided, $apiKey, $options, + * and $personalAPIKey are ignored. + * @param string|null $personalAPIKey Personal API key used to load local feature flag definitions. + * @return void + * @throws Exception When no API key can be resolved. */ public static function init( ?string $apiKey = null, @@ -60,10 +91,10 @@ public static function init( /** * Captures an exception as a PostHog error tracking event. * - * @param \Throwable|string $exception - * @param string|null $distinctId - * @param array $additionalProperties - * @return bool + * @param \Throwable|string $exception The exception to capture or a plain string message. + * @param string|null $distinctId User ID; a random UUID is used when omitted (no person profile created). + * @param array $additionalProperties Extra properties merged into the event. + * @return bool Whether the capture call succeeded. * @throws Exception */ public static function captureException( @@ -76,10 +107,21 @@ public static function captureException( } /** - * Captures a user action + * Captures a user action. * - * @param array $message - * @return boolean whether the capture call succeeded + * @param array{ + * event: string, + * distinctId?: string, + * distinct_id?: string, + * properties?: array, + * groups?: array, + * timestamp?: mixed, + * flags?: FeatureFlagEvaluations, + * send_feature_flags?: bool, + * sendFeatureFlags?: bool + * } $message Event payload. `send_feature_flags` and `sendFeatureFlags` are deprecated; pass + * a `flags` snapshot from evaluateFlags() instead. + * @return bool Whether the capture call succeeded. * @throws Exception */ public static function capture(array $message) @@ -94,8 +136,8 @@ public static function capture(array $message) /** * Tags properties about the user. * - * @param array $message - * @return boolean whether the identify call succeeded + * @param array{distinctId?: string, distinct_id?: string, properties?: array} $message + * @return bool Whether the identify call succeeded. * @throws Exception */ public static function identify(array $message) @@ -110,9 +152,14 @@ public static function identify(array $message) /** * Adds properties to a group. * - * @param array $message Must contain keys `groupType`, `groupKey`; accepts optional `properties` - * and `distinctId`/`distinct_id` to override the default synthetic ID. - * @return boolean whether the groupIdentify call succeeded + * @param array{ + * groupType: string, + * groupKey: string, + * properties?: array, + * distinctId?: string, + * distinct_id?: string + * } $message Group identify payload. `distinctId`/`distinct_id` override the default synthetic ID. + * @return bool Whether the groupIdentify call succeeded. * @throws Exception */ public static function groupIdentify(array $message) @@ -159,10 +206,12 @@ public static function groupIdentify(array $message) * * @param string $key * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return boolean + * @param array $groups + * @param array $personProperties + * @param array> $groupProperties + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. + * @return bool|null * @throws Exception */ public static function isFeatureEnabled( @@ -193,10 +242,12 @@ public static function isFeatureEnabled( * * @param string $key * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return boolean | string + * @param array $groups + * @param array $personProperties + * @param array> $groupProperties + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. + * @return bool|string|null * @throws Exception */ public static function getFeatureFlag( @@ -225,13 +276,13 @@ public static function getFeatureFlag( * `$flags->getFlagPayload($key)` instead. This consolidates flag evaluation into a single * `/flags` request per incoming request. * - * @param string $key + * @param string $key Feature flag key. * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @param bool $onlyEvaluateLocally - * @param bool $sendFeatureFlagEvents + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $sendFeatureFlagEvents Whether to send $feature_flag_called events. * @return FeatureFlagResult|null * @throws Exception */ @@ -263,9 +314,9 @@ public static function getFeatureFlagResult( * * @param string $key * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties + * @param array $groups + * @param array $personProperties + * @param array> $groupProperties * @return mixed */ public static function getFeatureFlagPayload( @@ -290,14 +341,18 @@ public static function getFeatureFlagPayload( * Pass the snapshot to capture() via the `flags` key to attach $feature/ properties * without making another /flags request. * - * @param array $groups - * @param array $personProperties - * @param array $groupProperties + * @param string|null $distinctId Defaults to the current request context distinctId, when set. + * @param array $groups Group identifiers for group-based flags. + * @param array $personProperties Person properties to use for flag evaluation. + * @param array> $groupProperties Group properties to use for flag evaluation. + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @param bool $disableGeoip Whether to disable GeoIP enrichment during remote evaluation. * @param list|null $flagKeys Optional list of flag keys. When provided, only these * flags are evaluated — the underlying /flags request asks the server for just this * subset, which makes the response smaller and the request cheaper. Use this when you * only need a handful of flags out of many. Distinct from FeatureFlagEvaluations::only(), * which scopes which already-evaluated flags get attached to a captured event. + * @return FeatureFlagEvaluations * @throws Exception */ public static function evaluateFlags( @@ -322,13 +377,14 @@ public static function evaluateFlags( } /** - * get all enabled flags for distinct_id + * Get all enabled flags for a distinct id. * * @param string|null $distinctId Defaults to the current request context distinctId, when set. - * @param array $groups - * @param array $personProperties - * @param array $groupProperties - * @return array + * @param array $groups + * @param array $personProperties + * @param array> $groupProperties + * @param bool $onlyEvaluateLocally Whether to avoid a remote /flags fallback. + * @return array * @throws Exception */ public static function getAllFlags( @@ -350,9 +406,11 @@ public static function getAllFlags( /** + * Fetch all feature flag variants for a distinct id from the remote /flags endpoint. * - * @param string $distinctId - * @return array + * @param string $distinctId The user's distinct ID. + * @param array $groups Group identifiers for group-based flags. + * @return array * @throws Exception */ public static function fetchFeatureVariants(string $distinctId, array $groups = array()): array @@ -362,10 +420,15 @@ public static function fetchFeatureVariants(string $distinctId, array $groups = } /** - * Aliases the distinct id from a temporary id to a permanent one + * Aliases the distinct id from a temporary id to a permanent one. * - * @param array $message distinct id to alias from - * @return boolean whether the alias call succeeded + * @param array{ + * distinctId?: string, + * distinct_id?: string, + * alias: string, + * properties?: array + * } $message + * @return bool Whether the alias call succeeded. * @throws Exception */ public static function alias(array $message) @@ -381,10 +444,18 @@ public static function alias(array $message) /** * Run a callback with request context applied to all captures in the callback. * - * @param array $data - * @param callable $fn - * @param array $options + * @param array{ + * distinctId?: string, + * distinct_id?: string, + * sessionId?: string, + * session_id?: string, + * properties?: array + * } $data + * @param callable $fn Callback to run while the context is active. + * @param array{fresh?: bool} $options Use `fresh => true` to avoid inheriting the current context. * @return mixed + * @throws Exception When the client has not been initialized. + * @throws \Throwable Re-throws any exception thrown by $fn after restoring context. */ public static function withContext(array $data, callable $fn, array $options = []): mixed { @@ -393,7 +464,10 @@ public static function withContext(array $data, callable $fn, array $options = [ } /** + * Get the currently active request context for this client, if any. + * * @return array{distinctId?: string|null, sessionId?: string|null, properties: array}|null + * @throws Exception */ public static function getContext(): ?array { @@ -402,7 +476,9 @@ public static function getContext(): ?array } /** - * @param array $headers + * Extract PostHog frontend tracing context from HTTP headers. + * + * @param array $headers HTTP headers, including $_SERVER-style HTTP_* keys. * @return array{distinctId?: string|null, sessionId?: string|null, properties: array} */ public static function contextFromHeaders(array $headers): array @@ -411,10 +487,10 @@ public static function contextFromHeaders(array $headers): array } /** - * Send a raw (prepared) message + * Send a raw, already-prepared message to the underlying consumer queue. * - * @param array $message distinct id to alias from - * @return boolean whether the alias call succeeded + * @param array $message Prepared message payload. + * @return mixed Whether the underlying consumer accepted the message. */ public static function raw(array $message) { @@ -425,8 +501,10 @@ public static function raw(array $message) /** * Validate common properties. * - * @param array $msg + * @internal + * @param array $msg * @param string $type + * @return void * @throws Exception */ public static function validate($msg, $type) @@ -436,9 +514,11 @@ public static function validate($msg, $type) } /** - * Flush the client + * Flush queued events on the underlying client. + * + * @return bool True when flushing succeeded or the consumer has no flush operation. + * @throws Exception */ - public static function flush() { self::checkClient(); diff --git a/lib/QueueConsumer.php b/lib/QueueConsumer.php index 1f22ce3..9c5bc42 100644 --- a/lib/QueueConsumer.php +++ b/lib/QueueConsumer.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Base class for consumers that batch messages before delivery. + * + * @internal + */ abstract class QueueConsumer extends Consumer { protected const MAX_BATCH_PAYLOAD_SIZE = 1024 * 1024; // 1MB @@ -53,6 +58,9 @@ public function __construct($apiKey, $options = array()) $this->queue = array(); } + /** + * Flush queued messages when the consumer is destroyed. + */ public function __destruct() { // Flush our queue on destruction @@ -62,8 +70,8 @@ public function __destruct() /** * Captures a user action * - * @param array $message - * @return boolean whether the capture call succeeded + * @param array $message Event payload. + * @return bool Whether the capture call succeeded. */ public function capture(array $message) { @@ -73,8 +81,8 @@ public function capture(array $message) /** * Tags properties about the user. * - * @param array $message - * @return boolean whether the identify call succeeded + * @param array $message Identify payload. + * @return bool Whether the identify call succeeded. */ public function identify(array $message) { @@ -84,8 +92,8 @@ public function identify(array $message) /** * Aliases from one user id to another * - * @param array $message - * @return boolean whether the alias call succeeded + * @param array $message Alias payload. + * @return bool Whether the alias call succeeded. */ public function alias(array $message) { @@ -93,7 +101,9 @@ public function alias(array $message) } /** - * Flushes our queue of messages by batching them to the server + * Flushes our queue of messages by batching them to the server. + * + * @return bool True when all queued batches were sent successfully. */ public function flush() { @@ -112,8 +122,8 @@ public function flush() /** * Adds an item to our queue. - * @param mixed $item - * @return boolean whether call has succeeded + * @param mixed $item Queue item to append. + * @return bool Whether the call succeeded. */ public function enqueue($item) { @@ -136,8 +146,8 @@ public function enqueue($item) * Given a batch of messages the method returns * a valid payload. * - * @param {Array} $batch - * @return {Array} + * @param array> $batch Batch of queued messages. + * @return array{batch: array>, api_key: string} */ protected function payload($batch) { diff --git a/lib/RequestContext.php b/lib/RequestContext.php index b731a12..edd9e3c 100644 --- a/lib/RequestContext.php +++ b/lib/RequestContext.php @@ -32,6 +32,7 @@ final class RequestContext * @param array $options Use ['fresh' => true] to avoid inheriting the current context. * @param int|null $contextKey Internal Client scope key. Null keeps backwards-compatible global scope. * @return mixed + * @throws \Throwable Re-throws any exception thrown by $fn after restoring context. */ public static function withContext( array $data, @@ -50,6 +51,9 @@ public static function withContext( } /** + * Get the current context for a context scope. + * + * @param int|null $contextKey Internal Client scope key. Null keeps backwards-compatible global scope. * @return array{distinctId?: string|null, sessionId?: string|null, properties: array}|null */ public static function getContext(?int $contextKey = null): ?array @@ -65,6 +69,12 @@ public static function getContext(?int $contextKey = null): ?array return $stack[array_key_last($stack)]['context']; } + /** + * Get the current distinct ID for a context scope. + * + * @param int|null $contextKey Internal Client scope key. Null keeps backwards-compatible global scope. + * @return string|null + */ public static function getDistinctId(?int $contextKey = null): ?string { $context = self::getContext($contextKey); @@ -74,7 +84,10 @@ public static function getDistinctId(?int $contextKey = null): ?string } /** + * Clear all stored request contexts. + * * @internal Test helper for clearing leaked context between tests/processes. + * @return void */ public static function reset(): void { diff --git a/lib/RequiresServerEvaluationException.php b/lib/RequiresServerEvaluationException.php index b0ae805..3d5c3ab 100644 --- a/lib/RequiresServerEvaluationException.php +++ b/lib/RequiresServerEvaluationException.php @@ -4,8 +4,18 @@ use Exception; +/** + * Raised when feature flag evaluation requires server-side data. + * + * @internal + */ class RequiresServerEvaluationException extends Exception { + /** + * Format the exception as an HTML-ish error message. + * + * @return string + */ public function errorMessage() { $errorMsg = 'Error on line ' . $this->getLine() . ' in ' . $this->getFile() . ': Requires Server Evaluation:' . $this->getMessage() . ''; //phpcs:ignore diff --git a/lib/SizeLimitedHash.php b/lib/SizeLimitedHash.php index 9a6457c..ff81012 100644 --- a/lib/SizeLimitedHash.php +++ b/lib/SizeLimitedHash.php @@ -2,6 +2,11 @@ namespace PostHog; +/** + * Size-limited two-level set used for feature flag call deduplication. + * + * @internal + */ class SizeLimitedHash { /** @@ -14,12 +19,24 @@ class SizeLimitedHash */ private $mapping; + /** + * Create a size-limited hash. + * + * @param int $size Maximum number of top-level keys before the mapping resets. + */ public function __construct($size) { $this->size = $size; $this->mapping = []; } + /** + * Add an element under a top-level key. + * + * @param string $key Top-level key. + * @param string $element Element to mark as present. + * @return void + */ public function add($key, $element) { @@ -34,11 +51,23 @@ public function add($key, $element) } } + /** + * Check whether an element exists under a top-level key. + * + * @param string $key Top-level key. + * @param string $element Element to look up. + * @return bool + */ public function contains($key, $element) { return isset($this->mapping[$key][$element]); } + /** + * Count top-level keys in the mapping. + * + * @return int + */ public function count() { return count($this->mapping); diff --git a/lib/StringNormalizer.php b/lib/StringNormalizer.php index 34c8ac7..1a48148 100644 --- a/lib/StringNormalizer.php +++ b/lib/StringNormalizer.php @@ -2,10 +2,21 @@ namespace PostHog; +/** + * Normalizes optional strings and host defaults. + * + * @internal + */ class StringNormalizer { public const DEFAULT_HOST = 'us.i.posthog.com'; + /** + * Trim an optional string and convert empty strings to null. + * + * @param string|null $value Value to normalize. + * @return string|null Normalized string or null. + */ public static function normalizeOptional(?string $value): ?string { if ($value === null) { @@ -16,6 +27,12 @@ public static function normalizeOptional(?string $value): ?string return $normalized === '' ? null : $normalized; } + /** + * Normalize a host string, falling back to the SDK default host. + * + * @param string|null $host Host to normalize. + * @return string Non-empty host. + */ public static function normalizeHost(?string $host): string { return self::normalizeOptional($host) ?? self::DEFAULT_HOST; diff --git a/lib/Uuid.php b/lib/Uuid.php index 63c9fca..064a6a4 100644 --- a/lib/Uuid.php +++ b/lib/Uuid.php @@ -2,8 +2,19 @@ namespace PostHog; +/** + * UUID generation helpers. + * + * @internal + */ final class Uuid { + /** + * Generate a random UUID v4 string. + * + * @return string UUID v4. + * @throws \Random\RandomException When random_int() cannot gather sufficient entropy. + */ public static function v4(): string { return sprintf( From 674a4f1704f52c91c8e18c73db1f0fd62a67705e Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 20 May 2026 11:19:48 +0200 Subject: [PATCH 2/3] docs: mark HTTP transport classes internal --- lib/HttpClient.php | 2 ++ lib/HttpResponse.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/HttpClient.php b/lib/HttpClient.php index f016e70..60c570d 100644 --- a/lib/HttpClient.php +++ b/lib/HttpClient.php @@ -6,6 +6,8 @@ /** * HTTP client used by the SDK for PostHog API requests. + * + * @internal */ class HttpClient { diff --git a/lib/HttpResponse.php b/lib/HttpResponse.php index cb5408c..5e42949 100644 --- a/lib/HttpResponse.php +++ b/lib/HttpResponse.php @@ -4,6 +4,8 @@ /** * Response wrapper returned by the SDK HTTP client. + * + * @internal */ class HttpResponse { From 4380210ec20fee1219ea664361118a1814dd5e1d Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Wed, 20 May 2026 11:26:50 +0200 Subject: [PATCH 3/3] docs: simplify README --- CONTRIBUTING.md | 9 +++++++++ README.md | 47 +++-------------------------------------------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57f7cb8..cd71cbd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,15 @@ Thanks for your interest in improving the PostHog PHP SDK. composer install --prefer-dist --no-progress ``` +## Running the example app + +1. Copy `.env.example` to `.env` and add your PostHog credentials. +2. Run the interactive example script: + + ```bash + php example.php + ``` + ## CI-aligned checks Run the test command used in CI: diff --git a/README.md b/README.md index 0cfe366..e6f957b 100644 --- a/README.md +++ b/README.md @@ -10,53 +10,12 @@ Specifically, the [PHP integration](https://posthog.com/docs/integrations/php-in ## Features - ✅ Event capture and user identification -- ✅ Error tracking with manual exception capture -- ✅ Opt-in automatic PHP exception, error, and fatal shutdown capture -- ✅ Feature flag local evaluation -- ✅ **Feature flag dependencies** (new!) - Create conditional flags based on other flags -- ✅ Multivariate flags and payloads +- ✅ Feature flags, including local evaluation, multivariate flags, payloads, and flag dependencies - ✅ Group analytics +- ✅ Error tracking with manual exception capture and opt-in automatic PHP exception, error, and fatal shutdown capture +- ✅ Request context helpers for applying distinct IDs, session IDs, and common properties across a request - ✅ Comprehensive test coverage -## Quick Start - -1. Copy `.env.example` to `.env` and add your PostHog credentials -2. Run `php example.php` to see interactive examples of all features - -## Error Tracking - -Manual exception capture: - -```php -PostHog::captureException($exception, 'user-123', [ - '$current_url' => 'https://example.com/settings', -]); -``` - -Opt-in automatic capture from the core SDK: - -```php -PostHog::init('phc_xxx', [ - 'error_tracking' => [ - 'enabled' => true, - 'capture_errors' => true, - 'excluded_exceptions' => [ - \InvalidArgumentException::class, - ], - 'context_provider' => static function (array $payload): array { - return [ - 'distinctId' => $_SESSION['user_id'] ?? null, - 'properties' => [ - '$current_url' => $_SERVER['REQUEST_URI'] ?? null, - ], - ]; - }, - ], -]); -``` - -Auto error tracking is off by default. When enabled, the SDK chains existing exception and error handlers instead of replacing app behavior. - ## Questions? ### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ)