diff --git a/lib/Client.php b/lib/Client.php index b490864..443992d 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -57,6 +57,11 @@ class Client */ public $distinctIdsFeatureFlagsReported; + /** + * @var string + */ + public $decideVersion; + /** * Create a new posthog object with your app's API key * key @@ -87,6 +92,7 @@ public function __construct( $this->featureFlags = []; $this->groupTypeMapping = []; $this->distinctIdsFeatureFlagsReported = new SizeLimitedHash(SIZE_LIMIT); + $this->decideVersion = $options["decide_version"] ?? '2'; // Populate featureflags and grouptypemapping if possible if (count($this->featureFlags) == 0 && !is_null($this->personalAPIKey)) { @@ -261,6 +267,41 @@ public function getFeatureFlag( return null; } + /** + * @param string $key + * @param string $distinctId + * @param array $groups + * @param array $personProperties + * @param array $groupProperties + * @return mixed + */ + public function getFeatureFlagPayload( + string $key, + string $distinctId, + array $groups = array(), + array $personProperties = array(), + array $groupProperties = array(), + ): mixed { + $results = json_decode( + $this->decide($distinctId, $groups, $personProperties, $groupProperties), + true + ); + + if (isset($results['featureFlags'][$key]) === false || $results['featureFlags'][$key] !== true) { + return null; + } + + $payload = $results['featureFlagPayloads'][$key] ?? null; + + $json = json_decode($payload, true); + + if (is_array($json)) { + return $json; + } + + return $payload; + } + /** * get the feature flag value for this distinct id. * @@ -425,7 +466,7 @@ public function decide( } return $this->httpClient->sendRequest( - '/decide/?v=2', + '/decide/?v=' . $this->decideVersion, json_encode($payload), [ // Send user agent in the form of {library_name}/{library_version} as per RFC 7231. diff --git a/lib/PostHog.php b/lib/PostHog.php index 252d71f..8c5a363 100644 --- a/lib/PostHog.php +++ b/lib/PostHog.php @@ -170,7 +170,31 @@ public static function getFeatureFlag( ); } - /** + /** + * @param string $key + * @param string $distinctId + * @param array $groups + * @param array $personProperties + * @param array $groupProperties + * @return mixed + */ + public static function getFeatureFlagPayload( + string $key, + string $distinctId, + array $groups = array(), + array $personProperties = array(), + array $groupProperties = array(), + ): mixed { + return self::$client->getFeatureFlagPayload( + $key, + $distinctId, + $groups, + $personProperties, + $groupProperties + ); + } + + /** * get all enabled flags for distinct_id * * @param string $distinctId diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index b6ba562..f4b986b 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -16,10 +16,11 @@ class FeatureFlagTest extends TestCase { - const FAKE_API_KEY = "random_key"; + protected const FAKE_API_KEY = "random_key"; - protected $http_client; - protected $client; + protected Client $client; + + protected MockedHttpClient $http_client; public function setUp(): void { @@ -2926,4 +2927,114 @@ public function testMultivariateFlagConsistency() $this->assertEquals($testResult, $result[$number]); } } + + public function testGetFeatureFlagPayloadHandlesJson(): void + { + $this->http_client = new MockedHttpClient( + host: "app.posthog.com", + flagEndpointResponse: MockedResponses::DECIDE_REQUEST_WITH_PAYLOAD_JSON + ); + + $this->client = new Client( + apiKey: self::FAKE_API_KEY, + options: [ + "debug" => true, + "decide_version" => 3, + ], + httpClient: $this->http_client, + personalAPIKey: "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertSame(['key' => 'value'], PostHog::getFeatureFlagPayload('payload-flag', 'some-distinct')); + } + + public function testGetFeatureFlagPayloadHandlesIntegers(): void + { + $this->http_client = new MockedHttpClient( + host: "app.posthog.com", + flagEndpointResponse: MockedResponses::DECIDE_REQUEST_WITH_PAYLOAD_INTEGER + ); + + $this->client = new Client( + apiKey: self::FAKE_API_KEY, + options: [ + "debug" => true, + "decide_version" => 3, + ], + httpClient: $this->http_client, + personalAPIKey: "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertSame(2500, PostHog::getFeatureFlagPayload('payload-flag', 'some-distinct')); + } + + public function testGetFeatureFlagPayloadHandlesString(): void + { + $this->http_client = new MockedHttpClient( + host: "app.posthog.com", + flagEndpointResponse: MockedResponses::DECIDE_REQUEST_WITH_PAYLOAD_STRING + ); + + $this->client = new Client( + apiKey: self::FAKE_API_KEY, + options: [ + "debug" => true, + "decide_version" => 3, + ], + httpClient: $this->http_client, + personalAPIKey: "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertSame('A String', PostHog::getFeatureFlagPayload('payload-flag', 'some-distinct')); + } + + public function testGetFeatureFlagPayloadHandlesFlagDisabled(): void + { + $this->http_client = new MockedHttpClient( + host: "app.posthog.com", + flagEndpointResponse: MockedResponses::DECIDE_REQUEST_WITH_PAYLOAD_JSON_FLAG_DISABLED + ); + + $this->client = new Client( + apiKey: self::FAKE_API_KEY, + options: [ + "debug" => true, + "decide_version" => 3, + ], + httpClient: $this->http_client, + personalAPIKey: "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertNull(PostHog::getFeatureFlagPayload('payload-flag', 'some-distinct')); + } + + public function testGetFeatureFlagPayloadHandlesFlagNotInResults(): void + { + $this->http_client = new MockedHttpClient( + host: "app.posthog.com", + flagEndpointResponse: MockedResponses::DECIDE_REQUEST_WITH_PAYLOAD_JSON + ); + + $this->client = new Client( + apiKey: self::FAKE_API_KEY, + options: [ + "debug" => true, + "decide_version" => 3, + ], + httpClient: $this->http_client, + personalAPIKey: "test" + ); + + PostHog::init(null, null, $this->client); + + $this->assertNull(PostHog::getFeatureFlagPayload('non-existent-flag', 'some-distinct')); + } } diff --git a/test/MockedHttpClient.php b/test/MockedHttpClient.php index 7ac4ab7..e3ba9ff 100644 --- a/test/MockedHttpClient.php +++ b/test/MockedHttpClient.php @@ -42,7 +42,7 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders array_push($this->calls, array("path" => $path, "payload" => $payload)); if (str_starts_with($path, "/decide/")) { - return new HttpResponse(json_encode(MockedResponses::DECIDE_REQUEST), 200); + return new HttpResponse(json_encode($this->flagEndpointResponse ?? MockedResponses::DECIDE_REQUEST), 200); } if (str_starts_with($path, "/api/feature_flag/local_evaluation")) { diff --git a/test/assests/MockedResponses.php b/test/assests/MockedResponses.php index 7f27e1a..612a576 100644 --- a/test/assests/MockedResponses.php +++ b/test/assests/MockedResponses.php @@ -36,6 +36,102 @@ class MockedResponses 'sessionRecording' => false, ]; + public const DECIDE_REQUEST_WITH_PAYLOAD_JSON = [ + 'config' => [ + 'enable_collect_everything' => true, + ], + 'editorParams' => [ + ], + 'isAuthenticated' => false, + 'supportedCompression' => [ + 0 => 'gzip', + 1 => 'gzip-js', + 2 => 'lz64', + ], + 'featureFlags' => [ + 'payload-flag' => true, + 'having_fun' => false, + 'enabled-flag' => true, + 'disabled-flag' => false, + ], + 'sessionRecording' => false, + 'featureFlagPayloads' => [ + 'payload-flag' => '{"key":"value"}', + ] + ]; + + public const DECIDE_REQUEST_WITH_PAYLOAD_JSON_FLAG_DISABLED = [ + 'config' => [ + 'enable_collect_everything' => true, + ], + 'editorParams' => [ + ], + 'isAuthenticated' => false, + 'supportedCompression' => [ + 0 => 'gzip', + 1 => 'gzip-js', + 2 => 'lz64', + ], + 'featureFlags' => [ + 'payload-flag' => false, + 'having_fun' => false, + 'enabled-flag' => true, + 'disabled-flag' => false, + ], + 'sessionRecording' => false, + 'featureFlagPayloads' => [ + 'payload-flag' => '{"key":"value"}', + ] + ]; + + public const DECIDE_REQUEST_WITH_PAYLOAD_INTEGER = [ + 'config' => [ + 'enable_collect_everything' => true, + ], + 'editorParams' => [ + ], + 'isAuthenticated' => false, + 'supportedCompression' => [ + 0 => 'gzip', + 1 => 'gzip-js', + 2 => 'lz64', + ], + 'featureFlags' => [ + 'payload-flag' => true, + 'having_fun' => false, + 'enabled-flag' => true, + 'disabled-flag' => false, + ], + 'sessionRecording' => false, + 'featureFlagPayloads' => [ + 'payload-flag' => 2500, + ] + ]; + + public const DECIDE_REQUEST_WITH_PAYLOAD_STRING = [ + 'config' => [ + 'enable_collect_everything' => true, + ], + 'editorParams' => [ + ], + 'isAuthenticated' => false, + 'supportedCompression' => [ + 0 => 'gzip', + 1 => 'gzip-js', + 2 => 'lz64', + ], + 'featureFlags' => [ + 'payload-flag' => true, + 'having_fun' => false, + 'enabled-flag' => true, + 'disabled-flag' => false, + ], + 'sessionRecording' => false, + 'featureFlagPayloads' => [ + 'payload-flag' => 'A String', + ] + ]; + public const LOCAL_EVALUATION_REQUEST = [ 'count' => 1, 'next' => null,