diff --git a/composer.json b/composer.json index cff876b..26d1d83 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "format": "./vendor/bin/pint" }, "require": { - "php": ">=8.0" + "php": ">=8.1", + "utopia-php/fetch": "^1.1" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/composer.lock b/composer.lock index dd64aa6..e066638 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,49 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dde525332d1da2a02fda10c2eeb97180", - "packages": [], + "content-hash": "5b7f199b10b8615a48e492132ff52481", + "packages": [ + { + "name": "utopia-php/fetch", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/fetch.git", + "reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/fetch/zipball/64f2b3a789480f1deb102ce684dac4217d8e98d5", + "reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "laravel/pint": "^1.5.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5", + "swoole/ide-helper": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Fetch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A simple library that provides an interface for making HTTP Requests.", + "support": { + "issues": "https://github.com/utopia-php/fetch/issues", + "source": "https://github.com/utopia-php/fetch/tree/1.1.2" + }, + "time": "2026-04-29T11:19:19+00:00" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", @@ -82,12 +123,12 @@ "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "232c4e699dd875c392050c84d82e8b23785857a0" + "reference": "d620575dc1d8868c3f1f8a12c07928cf50f6b447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/232c4e699dd875c392050c84d82e8b23785857a0", - "reference": "232c4e699dd875c392050c84d82e8b23785857a0", + "url": "https://api.github.com/repos/laravel/pint/zipball/d620575dc1d8868c3f1f8a12c07928cf50f6b447", + "reference": "d620575dc1d8868c3f1f8a12c07928cf50f6b447", "shasum": "" }, "require": { @@ -98,13 +139,14 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.93.1", - "illuminate/view": "^12.49.0", - "larastan/larastan": "^3.9.2", - "laravel-zero/framework": "^12.0.5", + "friendsofphp/php-cs-fixer": "^3.95.1", + "illuminate/view": "^12.56.0", + "larastan/larastan": "^3.9.6", + "laravel-zero/framework": "^12.1.0", + "laravel/agent-detector": "^2.0.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.3.3", - "pestphp/pest": "^3.8.5" + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6" }, "default-branch": true, "bin": [ @@ -142,7 +184,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-02-04T15:10:54+00:00" + "time": "2026-05-01T20:08:53+00:00" }, { "name": "myclabs/deep-copy", @@ -1873,16 +1915,16 @@ }, { "name": "utopia-php/system", - "version": "0.10.0", + "version": "0.10.1", "source": { "type": "git", "url": "https://github.com/utopia-php/system.git", - "reference": "6441a9c180958a373e5ddb330264dd638539dfdb" + "reference": "7c1669533bb9c285de19191270c8c1439161a78a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/system/zipball/6441a9c180958a373e5ddb330264dd638539dfdb", - "reference": "6441a9c180958a373e5ddb330264dd638539dfdb", + "url": "https://api.github.com/repos/utopia-php/system/zipball/7c1669533bb9c285de19191270c8c1439161a78a", + "reference": "7c1669533bb9c285de19191270c8c1439161a78a", "shasum": "" }, "require": { @@ -1923,9 +1965,9 @@ ], "support": { "issues": "https://github.com/utopia-php/system/issues", - "source": "https://github.com/utopia-php/system/tree/0.10.0" + "source": "https://github.com/utopia-php/system/tree/0.10.1" }, - "time": "2025-10-15T19:12:00+00:00" + "time": "2026-03-15T21:07:41+00:00" } ], "aliases": [], @@ -1936,8 +1978,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=8.0" + "php": ">=8.1" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/src/Analytics/Adapter.php b/src/Analytics/Adapter.php index c340408..91f05df 100644 --- a/src/Analytics/Adapter.php +++ b/src/Analytics/Adapter.php @@ -3,6 +3,8 @@ namespace Utopia\Analytics; use Exception; +use Utopia\Fetch\Client; +use Utopia\Fetch\Exception as FetchException; abstract class Adapter { @@ -106,16 +108,12 @@ public function createEvent(Event $event): bool * Make an API call * * - * @throws \Exception + * @throws Exception */ public function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''))); - $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; + $url = str_contains($path, 'http') ? $path : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''); switch ($headers['Content-Type']) { case 'application/json': @@ -131,61 +129,42 @@ public function call(string $method, string $path = '', array $headers = [], arr break; } - foreach ($headers as $i => $header) { - $headers[] = $i.':'.$header; - unset($headers[$i]); + $client = (new Client) + ->setUserAgent(php_uname('s').'-'.php_uname('r').':php-'.phpversion()) + ->setAllowRedirects(true); + + foreach ($headers as $key => $value) { + $client->addHeader($key, $value); } try { - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion()); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', strtolower($header), 2); - - if (count($header) < 2) { // ignore invalid headers - return $len; - } - - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - - return $len; - }); - - if ($method != 'GET') { - curl_setopt($ch, CURLOPT_POSTFIELDS, $query); - } - - $responseBody = curl_exec($ch); - - $responseType = $responseHeaders['Content-Type'] ?? ''; - $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $response = $client->fetch( + url: $url, + method: $method, + body: $method !== 'GET' ? $query : [], + ); + } catch (FetchException $e) { + throw new Exception($e->getMessage(), $e->getCode(), $e); + } - switch (substr($responseType, 0, strpos($responseType, ';'))) { - case 'application/json': - $responseBody = json_decode($responseBody, true); - break; - } + $responseHeaders = $response->getHeaders(); + $responseStatus = $response->getStatusCode(); + $responseBody = $response->getBody(); + $responseType = trim(explode(';', $responseHeaders['content-type'] ?? '')[0]); - if (curl_errno($ch)) { - throw new Exception(curl_error($ch), $responseStatus); - } + if ($responseType === 'application/json') { + $responseBody = json_decode($responseBody, true); + } - if ($responseStatus >= 400) { - if (is_array($responseBody)) { - throw new Exception(json_encode($responseBody), $responseStatus); - } else { - throw new Exception($responseStatus.': '.$responseBody, $responseStatus); - } + if ($responseStatus >= 400) { + if (is_array($responseBody)) { + throw new Exception(json_encode($responseBody), $responseStatus); + } else { + throw new Exception($responseStatus.': '.$responseBody, $responseStatus); } - - return $responseBody; - } finally { - curl_close($ch); } + + return $responseBody; } /** diff --git a/src/Analytics/Adapter/GoogleAnalytics.php b/src/Analytics/Adapter/GoogleAnalytics.php index 4079520..89c796a 100644 --- a/src/Analytics/Adapter/GoogleAnalytics.php +++ b/src/Analytics/Adapter/GoogleAnalytics.php @@ -91,8 +91,6 @@ public function validate(Event $event): bool ] )); - $validateResponse = json_decode($validateResponse, true); - if ($validateResponse['hitParsingResult'][0]['valid'] !== true) { throw new \Exception('Invalid event'); } @@ -148,7 +146,7 @@ public function send(Event $event): bool // Parse Debug data if ($this->endpoint == 'https://www.google-analytics.com/debug/collect') { - return json_decode($result, true)['hitParsingResult'][0]['valid']; + return $result['hitParsingResult'][0]['valid']; } return true; diff --git a/src/Analytics/Adapter/HubSpot.php b/src/Analytics/Adapter/HubSpot.php index ac69f7b..2d671e5 100644 --- a/src/Analytics/Adapter/HubSpot.php +++ b/src/Analytics/Adapter/HubSpot.php @@ -65,8 +65,6 @@ public function contactExists(string $email): bool|int throw $e; } - $result = json_decode($result, true); - if ($result && $result['id']) { return $result['id']; } else { @@ -158,8 +156,6 @@ public function accountExists(string $name): bool|int ]], ]); - $result = json_decode($result, true); - if ($result && $result['total'] > 0 && count($result['results']) > 0) { return $result['results'][0]['id']; } else { @@ -233,8 +229,6 @@ public function syncAssociation(string $accountId, string $contactId, string $ro $response = $this->call('GET', '/crm/v4/objects/contact/'.$accountId.'/associations/company'); - $response = json_decode($response, true); - $associationId = null; foreach ($response['results'] as $association) { diff --git a/src/Analytics/Adapter/Mixpanel.php b/src/Analytics/Adapter/Mixpanel.php index e20e4fd..dfaed34 100644 --- a/src/Analytics/Adapter/Mixpanel.php +++ b/src/Analytics/Adapter/Mixpanel.php @@ -147,7 +147,7 @@ public function appendProperties(string $distinctId, array $properties): bool */ public function setClientIP(string $clientIP): self { - throw new \Exception('Not implemented'); + throw new Exception('Not implemented'); } /** @@ -157,7 +157,7 @@ public function setClientIP(string $clientIP): self */ public function setUserAgent(string $userAgent): self { - throw new \Exception('Not implemented'); + throw new Exception('Not implemented'); } public function validate(Event $event): bool diff --git a/src/Analytics/Adapter/Orbit.php b/src/Analytics/Adapter/Orbit.php index a100b76..757758d 100644 --- a/src/Analytics/Adapter/Orbit.php +++ b/src/Analytics/Adapter/Orbit.php @@ -150,8 +150,6 @@ public function validate(Event $event): bool 'email' => $event->getProp('email'), ]); - $listMembers = json_decode($listMembers, true); - if (empty($listMembers['data'])) { return false; } @@ -164,8 +162,6 @@ public function validate(Event $event): bool 'activity_type' => $event->getType(), ]); - $activities = json_decode($activities, true); - if (empty($activities['data'])) { throw new \Exception('Failed to find event in Orbit'); } diff --git a/src/Analytics/Adapter/Plausible.php b/src/Analytics/Adapter/Plausible.php index f371d44..1aaa6ea 100644 --- a/src/Analytics/Adapter/Plausible.php +++ b/src/Analytics/Adapter/Plausible.php @@ -147,7 +147,6 @@ public function validate(Event $event): bool 'Content-Type' => '', 'Authorization' => 'Bearer '.$this->apiKey, ]); - $checkCreated = json_decode($checkCreated, true); if (! isset($checkCreated['results']['visitors']['value'])) { throw new Exception('Failed to validate event'); diff --git a/tests/Analytics/AnalyticsTest.php b/tests/Analytics/AnalyticsTest.php index 3729a2a..56f4a61 100644 --- a/tests/Analytics/AnalyticsTest.php +++ b/tests/Analytics/AnalyticsTest.php @@ -14,22 +14,22 @@ class AnalyticsTest extends TestCase { - /** @var \Utopia\Analytics\Adapter\GoogleAnalytics */ + /** @var GoogleAnalytics */ public $ga; - /** @var \Utopia\Analytics\Adapter\Plausible */ + /** @var Plausible */ public $pa; - /** @var \Utopia\Analytics\Adapter\Orbit */ + /** @var Orbit */ public $orbit; - /** @var \Utopia\Analytics\Adapter\Mixpanel */ + /** @var Mixpanel */ public $mp; - /** @var \Utopia\Analytics\Adapter\HubSpot */ + /** @var HubSpot */ public $hs; - /** @var \Utopia\Analytics\Adapter\ReoDev */ + /** @var ReoDev */ public $reodev; protected function setUp(): void