From aa4ae35c761f73a336fefd6b146c0f1daad578d9 Mon Sep 17 00:00:00 2001 From: Vasco de Krijger Date: Thu, 8 Jan 2026 15:16:21 +0100 Subject: [PATCH 1/6] chore(version upgrade): Bump to PHP8.5 --- .github/workflows/php.yml | 6 +++--- .gitignore | 1 + composer.json | 9 ++++----- lib/Client.php | 3 ++- lib/FeatureFlag.php | 4 +++- lib/HttpClient.php | 3 --- phpunit.xml | 23 ++++------------------- test/FeatureFlagErrorTest.php | 20 ++++++++++---------- test/FeatureFlagLocalEvaluationTest.php | 18 +++++++++--------- test/FeatureFlagTest.php | 7 ++++--- test/PostHogTest.php | 11 ++++++----- 11 files changed, 46 insertions(+), 59 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index e0dce5b..e2e643c 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-version: [8.0, 8.1, 8.2, 8.3, 8.4] + php-version: [8.2, 8.3, 8.4, 8.5] steps: - uses: actions/checkout@v2 @@ -22,11 +22,11 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - extensions: uopz, xdebug + extensions: xdebug tools: composer, phpunit - name: Install Dependencies - run: composer install --prefer-dist --no-progress --no-suggest + run: composer install --prefer-dist --no-progress - name: Run PHPUnit Tests run: XDEBUG_MODE=coverage ./vendor/bin/phpunit --bootstrap vendor/autoload.php --configuration phpunit.xml --coverage-text diff --git a/.gitignore b/.gitignore index 59169f1..8e7eff8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ test/posthog.log .phplint-cache .idea .phpunit.result.cache +.phpunit.cache clover.xml xdebug.log .DS_Store diff --git a/composer.json b/composer.json index 127f856..3182a39 100644 --- a/composer.json +++ b/composer.json @@ -15,13 +15,12 @@ ], "require": { "ext-json": "*", - "php": ">=8.0" + "php": ">=8.2", + "symfony/clock": "^6.2|^7.0" }, "require-dev": { - "overtrue/phplint": "^3.0", - "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.7", - "slope-it/clock-mock": "^0.4.0" + "phpunit/phpunit": "^11.0", + "squizlabs/php_codesniffer": "^3.7" }, "autoload": { "psr-4": { diff --git a/lib/Client.php b/lib/Client.php index 7aa271c..e8cb099 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -7,6 +7,7 @@ use PostHog\Consumer\ForkCurl; use PostHog\Consumer\LibCurl; use PostHog\Consumer\Socket; +use Symfony\Component\Clock\Clock; const SIZE_LIMIT = 50_000; @@ -829,7 +830,7 @@ private function formatTime($ts) { // time() if (null == $ts || !$ts) { - $ts = time(); + $ts = Clock::now()->getTimestamp(); } if (false !== filter_var($ts, FILTER_VALIDATE_INT)) { return date("c", (int)$ts); diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index e501204..e608374 100644 --- a/lib/FeatureFlag.php +++ b/lib/FeatureFlag.php @@ -2,6 +2,8 @@ namespace PostHog; +use Symfony\Component\Clock\Clock; + const LONG_SCALE = 0xfffffffffffffff; class FeatureFlag @@ -213,7 +215,7 @@ public static function matchPropertyGroup($propertyGroup, $propertyValues, $coho public static function relativeDateParseForFeatureFlagMatching($value) { $regex = "/^-?(?[0-9]+)(?[a-z])$/"; - $parsedDt = new \DateTime("now", new \DateTimeZone("UTC")); + $parsedDt = \DateTime::createFromInterface(Clock::now())->setTimezone(new \DateTimeZone("UTC")); if (preg_match($regex, $value, $matches)) { $number = intval($matches["number"]); diff --git a/lib/HttpClient.php b/lib/HttpClient.php index 435f69c..08f450f 100644 --- a/lib/HttpClient.php +++ b/lib/HttpClient.php @@ -114,9 +114,6 @@ public function sendRequest(string $path, ?string $payload, array $extraHeaders $httpResponse = $this->executePost($ch, $includeEtag); $responseCode = $httpResponse->getResponseCode(); - //close connection - curl_close($ch); - // Handle 304 Not Modified - this is a success, not an error if ($responseCode === 304) { if ($this->debug) { diff --git a/phpunit.xml b/phpunit.xml index c27f9b9..76d35c6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,36 +1,21 @@ - + ./lib - - - - + test - diff --git a/test/FeatureFlagErrorTest.php b/test/FeatureFlagErrorTest.php index 118c8a0..0b4cb8c 100644 --- a/test/FeatureFlagErrorTest.php +++ b/test/FeatureFlagErrorTest.php @@ -10,10 +10,10 @@ use PostHog\FeatureFlagError; use PostHog\PostHog; use PostHog\Test\Assets\MockedResponses; -use SlopeIt\ClockMock\ClockMock; class FeatureFlagErrorTest extends TestCase { + use ClockMockTrait; public const FAKE_API_KEY = "random_key"; private $http_client; @@ -40,7 +40,7 @@ public function setUp($flagsEndpointResponse = MockedResponses::FLAGS_RESPONSE, public function testFlagMissingError() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_RESPONSE, personalApiKey: null); // Request a flag that doesn't exist in the response @@ -70,7 +70,7 @@ public function testFlagMissingError() public function testErrorsWhileComputingFlagsError() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with errorsWhileComputingFlags set to true $responseWithErrors = array_merge(MockedResponses::FLAGS_RESPONSE, [ 'errorsWhileComputingFlags' => true @@ -103,7 +103,7 @@ public function testErrorsWhileComputingFlagsError() public function testMultipleErrors() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with errorsWhileComputingFlags set to true // and request a flag that doesn't exist $responseWithErrors = array_merge(MockedResponses::FLAGS_RESPONSE, [ @@ -137,7 +137,7 @@ public function testMultipleErrors() public function testNoErrorWhenFlagEvaluatesSuccessfully() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_RESPONSE, personalApiKey: null); // Request a flag that exists in the response @@ -164,7 +164,7 @@ public function testNoErrorWhenFlagEvaluatesSuccessfully() public function testUnknownErrorWhenExceptionThrown() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that will throw an exception $this->http_client = new class ("app.posthog.com") extends MockedHttpClient { public function sendRequest( @@ -245,7 +245,7 @@ public function testApiErrorMethod() public function testTimeoutError() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a timeout (responseCode=0, curlErrno=28) $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -291,7 +291,7 @@ public function testTimeoutError() public function testConnectionError() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a connection error (responseCode=0, curlErrno=6) $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -337,7 +337,7 @@ public function testConnectionError() public function testApiError500() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a 500 error $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -382,7 +382,7 @@ public function testApiError500() public function testQuotaLimitedError() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with quotaLimited containing feature_flags $quotaLimitedResponse = array_merge(MockedResponses::FLAGS_RESPONSE, [ 'quotaLimited' => ['feature_flags'] diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index b0cc66c..70e3b06 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -7,7 +7,6 @@ use Exception; use PHPUnit\Framework\TestCase; -use SlopeIt\ClockMock\ClockMock; use PostHog\FeatureFlag; use PostHog\Client; use PostHog\PostHog; @@ -17,6 +16,7 @@ class FeatureFlagLocalEvaluationTest extends TestCase { + use ClockMockTrait; protected const FAKE_API_KEY = "random_key"; protected Client $client; @@ -604,7 +604,7 @@ public function testMatchPropertyDateOperators(): void public function testMatchPropertyRelativeDateOperators(): void { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $prop_a = [ "key" => "key", @@ -951,7 +951,7 @@ public function testRelativeDateParsingOverflow() public function testRelativeDateParsingHours() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1h'), new \DateTime('2020-01-01T11:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2h'), new \DateTime('2020-01-01T10:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('24h'), new \DateTime('2019-12-31T12:01:20Z')); @@ -965,7 +965,7 @@ public function testRelativeDateParsingHours() public function testRelativeDateParsingDays() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1d'), new \DateTime('2019-12-31T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2d'), new \DateTime('2019-12-30T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('7d'), new \DateTime('2019-12-25T12:01:20Z')); @@ -978,7 +978,7 @@ public function testRelativeDateParsingDays() public function testRelativeDateParsingWeeks() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1w'), new \DateTime('2019-12-25T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2w'), new \DateTime('2019-12-18T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('4w'), new \DateTime('2019-12-04T12:01:20Z')); @@ -991,7 +991,7 @@ public function testRelativeDateParsingWeeks() public function testRelativeDateParsingMonths() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1m'), new \DateTime('2019-12-01T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2m'), new \DateTime('2019-11-01T12:01:20Z')); @@ -1007,7 +1007,7 @@ public function testRelativeDateParsingMonths() self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('12m'), FeatureFlag::relativeDateParseForFeatureFlagMatching('1y')); }); - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-04-03T00:00:00Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-04-03T00:00:00Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1m'), new \DateTime('2020-03-03T00:00:00Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2m'), new \DateTime('2020-02-03T00:00:00Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('4m'), new \DateTime('2019-12-03T00:00:00Z')); @@ -1021,7 +1021,7 @@ public function testRelativeDateParsingMonths() public function testRelativeDateParsingYears() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1y'), new \DateTime('2019-01-01T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2y'), new \DateTime('2018-01-01T12:01:20Z')); @@ -1339,7 +1339,7 @@ public function testLoadFeatureFlagsWrongKey() public function testSimpleFlag() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_SIMPLE_REQUEST); $this->client = new Client( diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index cff58aa..5659d20 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -10,11 +10,12 @@ use PostHog\Client; use PostHog\PostHog; use PostHog\Test\Assets\MockedResponses; -use SlopeIt\ClockMock\ClockMock; class FeatureFlagTest extends TestCase { + use ClockMockTrait; + const FAKE_API_KEY = "random_key"; private $http_client; @@ -81,7 +82,7 @@ public function testIsFeatureEnabled($response) public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_V2_RESPONSE, personalApiKey: null); $this->assertTrue(PostHog::isFeatureEnabled('simple-test', 'user-id')); PostHog::flush(); @@ -163,7 +164,7 @@ public function testGetFeatureFlag($response) public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_V2_RESPONSE, personalApiKey: null); $this->assertEquals("variant-value", PostHog::getFeatureFlag('multivariate-test', 'user-id')); PostHog::flush(); diff --git a/test/PostHogTest.php b/test/PostHogTest.php index af41d7d..1315f40 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -10,11 +10,12 @@ use PostHog\Client; use PostHog\PostHog; use PostHog\Test\Assets\MockedResponses; -use SlopeIt\ClockMock\ClockMock; class PostHogTest extends TestCase { + use ClockMockTrait; + const FAKE_API_KEY = "random_key"; private $http_client; @@ -83,7 +84,7 @@ public function testCapture(): void public function testCaptureWithSendFeatureFlagsOption(): void { - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_MULTIPLE_REQUEST); $this->client = new Client( self::FAKE_API_KEY, @@ -150,7 +151,7 @@ public function testCaptureWithLocalSendFlags(): void ); PostHog::init(null, null, $this->client); - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( @@ -196,7 +197,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void ); PostHog::init(null, null, $this->client); - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( @@ -477,7 +478,7 @@ public function testCaptureWithSendFeatureFlagsFalse(): void ); PostHog::init(null, null, $this->client); - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( From bcd4c7aee552b52ec190f30f21bedd070445236a Mon Sep 17 00:00:00 2001 From: Vasco de Krijger Date: Thu, 8 Jan 2026 15:19:28 +0100 Subject: [PATCH 2/6] chore: Add missing Trait --- test/ClockMockTrait.php | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/ClockMockTrait.php diff --git a/test/ClockMockTrait.php b/test/ClockMockTrait.php new file mode 100644 index 0000000..8795303 --- /dev/null +++ b/test/ClockMockTrait.php @@ -0,0 +1,37 @@ + Date: Thu, 8 Jan 2026 15:25:01 +0100 Subject: [PATCH 3/6] chore: Fix PHPUnit issues in the newer versions --- lib/Client.php | 2 +- lib/FeatureFlag.php | 2 +- test/ClockMockTrait.php | 5 +++-- test/FeatureFlagErrorTest.php | 18 +++++++++--------- test/FeatureFlagLocalEvaluationTest.php | 16 ++++++++-------- test/FeatureFlagTest.php | 6 +++--- test/PostHogTest.php | 8 ++++---- 7 files changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/Client.php b/lib/Client.php index e8cb099..faf9133 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -830,7 +830,7 @@ private function formatTime($ts) { // time() if (null == $ts || !$ts) { - $ts = Clock::now()->getTimestamp(); + $ts = Clock::get()->now()->getTimestamp(); } if (false !== filter_var($ts, FILTER_VALIDATE_INT)) { return date("c", (int)$ts); diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index e608374..125a720 100644 --- a/lib/FeatureFlag.php +++ b/lib/FeatureFlag.php @@ -215,7 +215,7 @@ public static function matchPropertyGroup($propertyGroup, $propertyValues, $coho public static function relativeDateParseForFeatureFlagMatching($value) { $regex = "/^-?(?[0-9]+)(?[a-z])$/"; - $parsedDt = \DateTime::createFromInterface(Clock::now())->setTimezone(new \DateTimeZone("UTC")); + $parsedDt = \DateTime::createFromInterface(Clock::get()->now())->setTimezone(new \DateTimeZone("UTC")); if (preg_match($regex, $value, $matches)) { $number = intval($matches["number"]); diff --git a/test/ClockMockTrait.php b/test/ClockMockTrait.php index 8795303..0b9c30c 100644 --- a/test/ClockMockTrait.php +++ b/test/ClockMockTrait.php @@ -4,6 +4,7 @@ use Symfony\Component\Clock\Clock; use Symfony\Component\Clock\MockClock; +use Symfony\Component\Clock\NativeClock; /** * Trait providing time mocking functionality for tests using Symfony Clock. @@ -19,7 +20,7 @@ trait ClockMockTrait * @param callable $callback The callback to execute * @return mixed The return value of the callback */ - protected static function executeAtFrozenDateTime(\DateTimeInterface $dateTime, callable $callback): mixed + protected function executeAtFrozenDateTime(\DateTimeInterface $dateTime, callable $callback): mixed { $mockClock = new MockClock($dateTime instanceof \DateTimeImmutable ? $dateTime @@ -31,7 +32,7 @@ protected static function executeAtFrozenDateTime(\DateTimeInterface $dateTime, return $callback(); } finally { // Reset to real clock - Clock::set(new \Symfony\Component\Clock\NativeClock()); + Clock::set(new NativeClock()); } } } diff --git a/test/FeatureFlagErrorTest.php b/test/FeatureFlagErrorTest.php index 0b4cb8c..a1a6ef3 100644 --- a/test/FeatureFlagErrorTest.php +++ b/test/FeatureFlagErrorTest.php @@ -40,7 +40,7 @@ public function setUp($flagsEndpointResponse = MockedResponses::FLAGS_RESPONSE, public function testFlagMissingError() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_RESPONSE, personalApiKey: null); // Request a flag that doesn't exist in the response @@ -70,7 +70,7 @@ public function testFlagMissingError() public function testErrorsWhileComputingFlagsError() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with errorsWhileComputingFlags set to true $responseWithErrors = array_merge(MockedResponses::FLAGS_RESPONSE, [ 'errorsWhileComputingFlags' => true @@ -103,7 +103,7 @@ public function testErrorsWhileComputingFlagsError() public function testMultipleErrors() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with errorsWhileComputingFlags set to true // and request a flag that doesn't exist $responseWithErrors = array_merge(MockedResponses::FLAGS_RESPONSE, [ @@ -137,7 +137,7 @@ public function testMultipleErrors() public function testNoErrorWhenFlagEvaluatesSuccessfully() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_RESPONSE, personalApiKey: null); // Request a flag that exists in the response @@ -164,7 +164,7 @@ public function testNoErrorWhenFlagEvaluatesSuccessfully() public function testUnknownErrorWhenExceptionThrown() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that will throw an exception $this->http_client = new class ("app.posthog.com") extends MockedHttpClient { public function sendRequest( @@ -245,7 +245,7 @@ public function testApiErrorMethod() public function testTimeoutError() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a timeout (responseCode=0, curlErrno=28) $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -291,7 +291,7 @@ public function testTimeoutError() public function testConnectionError() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a connection error (responseCode=0, curlErrno=6) $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -337,7 +337,7 @@ public function testConnectionError() public function testApiError500() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a mocked client that simulates a 500 error $this->http_client = new MockedHttpClient( "app.posthog.com", @@ -382,7 +382,7 @@ public function testApiError500() public function testQuotaLimitedError() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { // Create a response with quotaLimited containing feature_flags $quotaLimitedResponse = array_merge(MockedResponses::FLAGS_RESPONSE, [ 'quotaLimited' => ['feature_flags'] diff --git a/test/FeatureFlagLocalEvaluationTest.php b/test/FeatureFlagLocalEvaluationTest.php index 70e3b06..e9b6adc 100644 --- a/test/FeatureFlagLocalEvaluationTest.php +++ b/test/FeatureFlagLocalEvaluationTest.php @@ -604,7 +604,7 @@ public function testMatchPropertyDateOperators(): void public function testMatchPropertyRelativeDateOperators(): void { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $prop_a = [ "key" => "key", @@ -951,7 +951,7 @@ public function testRelativeDateParsingOverflow() public function testRelativeDateParsingHours() { - self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1h'), new \DateTime('2020-01-01T11:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2h'), new \DateTime('2020-01-01T10:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('24h'), new \DateTime('2019-12-31T12:01:20Z')); @@ -965,7 +965,7 @@ public function testRelativeDateParsingHours() public function testRelativeDateParsingDays() { - self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1d'), new \DateTime('2019-12-31T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2d'), new \DateTime('2019-12-30T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('7d'), new \DateTime('2019-12-25T12:01:20Z')); @@ -978,7 +978,7 @@ public function testRelativeDateParsingDays() public function testRelativeDateParsingWeeks() { - self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1w'), new \DateTime('2019-12-25T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2w'), new \DateTime('2019-12-18T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('4w'), new \DateTime('2019-12-04T12:01:20Z')); @@ -991,7 +991,7 @@ public function testRelativeDateParsingWeeks() public function testRelativeDateParsingMonths() { - self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1m'), new \DateTime('2019-12-01T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2m'), new \DateTime('2019-11-01T12:01:20Z')); @@ -1007,7 +1007,7 @@ public function testRelativeDateParsingMonths() self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('12m'), FeatureFlag::relativeDateParseForFeatureFlagMatching('1y')); }); - self::executeAtFrozenDateTime(new \DateTime('2020-04-03T00:00:00Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-04-03T00:00:00Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1m'), new \DateTime('2020-03-03T00:00:00Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2m'), new \DateTime('2020-02-03T00:00:00Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('4m'), new \DateTime('2019-12-03T00:00:00Z')); @@ -1021,7 +1021,7 @@ public function testRelativeDateParsingMonths() public function testRelativeDateParsingYears() { - self::executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2020-01-01T12:01:20Z'), function () { self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('1y'), new \DateTime('2019-01-01T12:01:20Z')); self::assertEquals(FeatureFlag::relativeDateParseForFeatureFlagMatching('2y'), new \DateTime('2018-01-01T12:01:20Z')); @@ -1339,7 +1339,7 @@ public function testLoadFeatureFlagsWrongKey() public function testSimpleFlag() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_SIMPLE_REQUEST); $this->client = new Client( diff --git a/test/FeatureFlagTest.php b/test/FeatureFlagTest.php index 5659d20..9a8622a 100644 --- a/test/FeatureFlagTest.php +++ b/test/FeatureFlagTest.php @@ -46,7 +46,7 @@ public function checkEmptyErrorLogs(): void $this->assertTrue(empty($errorMessages), "Error logs are not empty: " . implode("\n", $errorMessages)); } - public function decideResponseCases(): array + public static function decideResponseCases(): array { return [ 'v3 response' => [MockedResponses::FLAGS_RESPONSE], @@ -82,7 +82,7 @@ public function testIsFeatureEnabled($response) public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_V2_RESPONSE, personalApiKey: null); $this->assertTrue(PostHog::isFeatureEnabled('simple-test', 'user-id')); PostHog::flush(); @@ -164,7 +164,7 @@ public function testGetFeatureFlag($response) public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->setUp(MockedResponses::FLAGS_V2_RESPONSE, personalApiKey: null); $this->assertEquals("variant-value", PostHog::getFeatureFlag('multivariate-test', 'user-id')); PostHog::flush(); diff --git a/test/PostHogTest.php b/test/PostHogTest.php index 1315f40..48997e8 100644 --- a/test/PostHogTest.php +++ b/test/PostHogTest.php @@ -84,7 +84,7 @@ public function testCapture(): void public function testCaptureWithSendFeatureFlagsOption(): void { - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->http_client = new MockedHttpClient(host: "app.posthog.com", flagEndpointResponse: MockedResponses::LOCAL_EVALUATION_MULTIPLE_REQUEST); $this->client = new Client( self::FAKE_API_KEY, @@ -151,7 +151,7 @@ public function testCaptureWithLocalSendFlags(): void ); PostHog::init(null, null, $this->client); - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( @@ -197,7 +197,7 @@ public function testCaptureWithLocalSendFlagsNoOverrides(): void ); PostHog::init(null, null, $this->client); - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( @@ -478,7 +478,7 @@ public function testCaptureWithSendFeatureFlagsFalse(): void ); PostHog::init(null, null, $this->client); - self::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array ( From 24d87ba5726652cef1f9dcc3af6a5cff9e9a0b85 Mon Sep 17 00:00:00 2001 From: Vasco de Krijger Date: Thu, 8 Jan 2026 15:42:03 +0100 Subject: [PATCH 4/6] chore: Update PHPUnit config to match the latest version --- phpunit.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 76d35c6..021aeb7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,12 +2,21 @@ + + + + + ./lib From 7a99297db34d15c6878f9e7577766ae384439a33 Mon Sep 17 00:00:00 2001 From: Vasco de Krijger Date: Thu, 8 Jan 2026 16:15:26 +0100 Subject: [PATCH 5/6] chore: Another instance where we would use the time() implicitely updated so that we can test it --- lib/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Client.php b/lib/Client.php index faf9133..589ee08 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -842,7 +842,7 @@ private function formatTime($ts) return date("c", strtotime($ts)); } - return date("c"); + return date("c", Clock::get()->now()->getTimestamp()); } // fix for floatval casting in send.php From 2708f73994b594ab0a748f93c6c317c2fd09d6b3 Mon Sep 17 00:00:00 2001 From: Vasco de Krijger Date: Thu, 8 Jan 2026 17:01:19 +0100 Subject: [PATCH 6/6] docs: Add min PHP version in the Readme.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8804475..cd79718 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # PostHog PHP +[![PHP Version](https://img.shields.io/packagist/php-v/posthog/posthog-php?logo=php)](https://packagist.org/packages/posthog/posthog-php) +[![CI](https://github.com/PostHog/posthog-php/actions/workflows/php.yml/badge.svg)](https://github.com/PostHog/posthog-php/actions/workflows/php.yml) + Please see the main [PostHog docs](https://posthog.com/docs). Specifically, the [PHP integration](https://posthog.com/docs/integrations/php-integration) details.