From 2f764e7b3e6b9b4eb5c6cb69d1411f5ec90d0801 Mon Sep 17 00:00:00 2001 From: Manoel Aranda Neto Date: Sun, 5 Jul 2026 12:26:57 +0200 Subject: [PATCH] feat: add before send callback --- .changeset/bright-owls-filter.md | 5 +++ lib/Client.php | 43 ++++++++++++++++++++++++ lib/PostHog.php | 1 + test/PostHogTest.php | 57 ++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 .changeset/bright-owls-filter.md diff --git a/.changeset/bright-owls-filter.md b/.changeset/bright-owls-filter.md new file mode 100644 index 0000000..46491cb --- /dev/null +++ b/.changeset/bright-owls-filter.md @@ -0,0 +1,5 @@ +--- +"posthog-php": minor +--- + +Add a before_send callback for modifying or dropping fully enriched events. diff --git a/lib/Client.php b/lib/Client.php index 2701ac0..839979a 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -172,6 +172,7 @@ class Client implements FeatureFlagEvaluationsHost * flush_interval_seconds?: int|float, * compress_request?: bool|string, * error_handler?: callable, + * before_send?: callable(array): (array|null), * filename?: string, * is_server?: bool, * flag_definition_cache_provider?: FlagDefinitionCacheProvider, @@ -394,9 +395,51 @@ public function capture(array $message) unset($message["send_feature_flags"]); + $message = $this->applyBeforeSend($message); + if ($message === null) { + return false; + } + return $this->consumer->capture($message); } + /** + * Run the configured before_send callback for a fully enriched capture event. + * + * @param array $message + * @return array|null + */ + private function applyBeforeSend(array $message): ?array + { + $beforeSend = $this->options['before_send'] ?? null; + if ($beforeSend === null) { + return $message; + } + + if (!is_callable($beforeSend)) { + error_log('[PostHog][Client] before_send is not callable; dropping event'); + return null; + } + + try { + $result = $beforeSend($message); + } catch (Throwable $e) { + error_log('[PostHog][Client] before_send callback threw; dropping event: ' . $e->getMessage()); + return null; + } + + if ($result === null) { + return null; + } + + if (!is_array($result)) { + error_log('[PostHog][Client] before_send must return an array or null; dropping event'); + return null; + } + + return $result; + } + /** * Captures an exception as a PostHog error tracking event. * diff --git a/lib/PostHog.php b/lib/PostHog.php index f961ec4..1b90b4b 100644 --- a/lib/PostHog.php +++ b/lib/PostHog.php @@ -49,6 +49,7 @@ class PostHog * flush_interval_seconds?: int|float, * compress_request?: bool|string, * error_handler?: callable, + * before_send?: callable(array): (array|null), * filename?: string, * flag_definition_cache_provider?: FlagDefinitionCacheProvider, * error_tracking?: array{ diff --git a/test/PostHogTest.php b/test/PostHogTest.php index fcb0e07..edf8cd7 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -487,6 +487,63 @@ public function testBatchSizeOneFlushesImmediately(): void $this->assertSame('/batch/', $httpClient->calls[0]['path']); } + public function testBeforeSendCanModifyFullyEnrichedEvent(): void + { + $httpClient = new MockedHttpClient("app.posthog.com"); + $sawFullyEnrichedEvent = false; + $client = new Client( + self::FAKE_API_KEY, + [ + "batch_size" => 1, + "before_send" => function (array $event) use (&$sawFullyEnrichedEvent): array { + $sawFullyEnrichedEvent = isset( + $event['properties']['$lib'], + $event['properties']['$lib_version'], + $event['properties']['$lib_consumer'], + $event['properties']['$is_server'] + ); + unset($event['properties']['secret']); + $event['properties']['before_send'] = true; + + return $event; + }, + ], + $httpClient, + null, + false + ); + + $this->assertTrue($client->capture([ + "distinctId" => "john", + "event" => "Module PHP Event", + "properties" => ["secret" => "remove"], + ])); + + $this->assertTrue($sawFullyEnrichedEvent); + $payload = json_decode($httpClient->calls[0]['payload'], true); + $properties = $payload['batch'][0]['properties']; + $this->assertTrue($properties['before_send']); + $this->assertArrayNotHasKey('secret', $properties); + } + + public function testBeforeSendCanDropEvent(): void + { + $httpClient = new MockedHttpClient("app.posthog.com"); + $client = new Client( + self::FAKE_API_KEY, + ["batch_size" => 1, "before_send" => static fn(array $event): ?array => null], + $httpClient, + null, + false + ); + + $this->assertFalse($client->capture([ + "distinctId" => "john", + "event" => "Module PHP Event", + ])); + $this->assertSame([], $httpClient->calls ?? []); + } + /** * @dataProvider facadeNoOpBeforeInitCases