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/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. 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..589ee08 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::get()->now()->getTimestamp(); } if (false !== filter_var($ts, FILTER_VALIDATE_INT)) { return date("c", (int)$ts); @@ -841,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 diff --git a/lib/FeatureFlag.php b/lib/FeatureFlag.php index e501204..125a720 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::get()->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..021aeb7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,36 +1,30 @@ - - - ./lib - + + + + ./lib + + test - diff --git a/test/ClockMockTrait.php b/test/ClockMockTrait.php new file mode 100644 index 0000000..0b9c30c --- /dev/null +++ b/test/ClockMockTrait.php @@ -0,0 +1,38 @@ +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 () { + $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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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 b0cc66c..e9b6adc 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 () { + $this->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 () { + $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() { - ClockMock::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() { - ClockMock::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() { - ClockMock::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')); }); - ClockMock::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() { - ClockMock::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() { - ClockMock::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 cff58aa..9a8622a 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; @@ -45,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], @@ -81,7 +82,7 @@ public function testIsFeatureEnabled($response) public function testIsFeatureEnabledCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - ClockMock::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(); @@ -163,7 +164,7 @@ public function testGetFeatureFlag($response) public function testGetFeatureFlagCapturesFeatureFlagCalledEventWithAdditionalMetadata() { - ClockMock::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 af41d7d..48997e8 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 () { + $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, @@ -150,7 +151,7 @@ public function testCaptureWithLocalSendFlags(): void ); PostHog::init(null, null, $this->client); - ClockMock::executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { + $this->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 () { + $this->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 () { + $this->executeAtFrozenDateTime(new \DateTime('2022-05-01'), function () { $this->assertTrue( PostHog::capture( array (