diff --git a/.changeset/is-server-property.md b/.changeset/is-server-property.md new file mode 100644 index 0000000..eb2bda9 --- /dev/null +++ b/.changeset/is-server-property.md @@ -0,0 +1,5 @@ +--- +"posthog-php": minor +--- + +Add a configurable `$is_server` event property (default `true`) so PostHog can identify server-side events. Set `is_server` to `false` when using posthog-php as a client/CLI so the device OS is attributed normally. diff --git a/lib/Client.php b/lib/Client.php index dceac83..ba2c8ab 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -121,6 +121,7 @@ class Client implements FeatureFlagEvaluationsHost * compress_request?: bool|string, * error_handler?: callable, * filename?: string, + * is_server?: bool, * error_tracking?: array{ * enabled?: bool, * capture_errors?: bool, @@ -1658,6 +1659,14 @@ private function message($msg) $msg["properties"]['$lib_version'] = PostHog::VERSION; $msg["properties"]['$lib_consumer'] = $this->consumer->getConsumer(); + // When running as a server SDK (the default), tag events as server-side so + // PostHog does not attribute the host machine's device/OS to the event. + // Set the `is_server` option to false when using posthog-php as a + // client/CLI so the device OS is attributed normally. + if (($this->options['is_server'] ?? true) === true) { + $msg["properties"]['$is_server'] = true; + } + if (isset($msg["distinctId"])) { $msg["distinct_id"] = $msg["distinctId"]; unset($msg["distinctId"]); diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 676ede7..c9dd09c 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -1453,7 +1453,7 @@ public function testSimpleFlag() ), 1 => array( "path" => "/batch/", - 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + 'payload' => '{"batch":[{"properties":{"$feature_flag":"simple-flag","$feature_flag_response":true,"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"some-distinct-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 2ad7b10..9c10ff1 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -97,7 +97,7 @@ public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditional ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"simple-test","$feature_flag_response":true,"$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":6,"$feature_flag_version":1,"$feature_flag_reason":"Matched condition set 1","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -196,7 +196,7 @@ public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMe ), 1 => array( "path" => "/batch/", - "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"properties":{"$feature_flag":"multivariate-test","$feature_flag_response":"variant-value","$feature_flag_request_id":"98487c8a-287a-4451-a085-299cd76228dd","$feature_flag_id":7,"$feature_flag_version":3,"$feature_flag_reason":"Matched condition set 2","$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true,"$groups":[]},"distinct_id":"user-id","event":"$feature_flag_called","$groups":[],"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 0b14dc8..130f5d4 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -448,6 +448,73 @@ public function testCapture(): void ); } + public function testCaptureIncludesIsServerProperty(): void + { + self::assertTrue( + PostHog::capture( + array( + "distinctId" => "john", + "event" => "Module PHP Event", + ) + ) + ); + PostHog::flush(); + + $batchCall = null; + foreach ($this->http_client->calls as $call) { + if (($call["path"] ?? null) === "/batch/") { + $batchCall = $call; + break; + } + } + self::assertNotNull($batchCall, "Expected a /batch/ call to have been made"); + + $decoded = json_decode($batchCall["payload"], true); + $properties = $decoded["batch"][0]["properties"]; + + self::assertArrayHasKey('$is_server', $properties); + self::assertTrue($properties['$is_server']); + } + + public function testCaptureOmitsIsServerPropertyWhenDisabled(): void + { + $this->http_client = new MockedHttpClient("app.posthog.com"); + $this->client = new Client( + self::FAKE_API_KEY, + [ + "debug" => true, + "is_server" => false, + ], + $this->http_client, + "test" + ); + PostHog::init(null, null, $this->client); + + self::assertTrue( + PostHog::capture( + array( + "distinctId" => "john", + "event" => "Module PHP Event", + ) + ) + ); + PostHog::flush(); + + $batchCall = null; + foreach ($this->http_client->calls as $call) { + if (($call["path"] ?? null) === "/batch/") { + $batchCall = $call; + break; + } + } + self::assertNotNull($batchCall, "Expected a /batch/ call to have been made"); + + $decoded = json_decode($batchCall["payload"], true); + $properties = $decoded["batch"][0]["properties"]; + + self::assertArrayNotHasKey('$is_server', $properties); + } + public function testCaptureWithSendFeatureFlagsOption(): void { $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { @@ -485,7 +552,7 @@ public function testCaptureWithSendFeatureFlagsOption(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -541,7 +608,7 @@ public function testCaptureWithLocalSendFlags(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":true,"properties":{"$feature\/true-flag":true,"$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -591,7 +658,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"send_feature_flags":true,"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","properties":{"$feature\/true-flag":"random-override","$active_feature_flags":["true-flag"],"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"send_feature_flags":true,"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ), @@ -893,7 +960,7 @@ public function testCaptureWithSendFeatureFlagsFalse(): void ), 1 => array ( "path" => "/batch/", - "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":false,"properties":{"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl"},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', + "payload" => '{"batch":[{"event":"Module PHP Event","send_feature_flags":false,"properties":{"$lib":"posthog-php","$lib_version":"' . PostHog::VERSION . '","$lib_consumer":"LibCurl","$is_server":true},"library":"posthog-php","library_version":"' . PostHog::VERSION . '","library_consumer":"LibCurl","distinct_id":"john","groups":[],"timestamp":"2022-05-01T00:00:00+00:00","type":"capture"}],"api_key":"random_key"}', "extraHeaders" => array(0 => 'User-Agent: posthog-php/' . PostHog::VERSION), "requestOptions" => array('shouldVerify' => true), ),