From d7dc96c7b50890620ef57266c8bbdb4ce5ec2d9f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Sun, 12 Jul 2026 02:52:00 +0100 Subject: [PATCH] Add support for Guzzle 8 --- CHANGELOG.md | 2 + composer.json | 6 +-- phpstan.neon.dist | 40 +++++++++++++++++++ .../AppCheckApiExceptionConverter.php | 2 +- src/Exception/AuthApiExceptionConverter.php | 2 +- .../DatabaseApiExceptionConverter.php | 2 +- .../MessagingApiExceptionConverter.php | 2 +- .../RemoteConfigApiExceptionConverter.php | 6 +-- tests/CreatesRequestExceptions.php | 27 +++++++++++++ .../AppCheckApiExceptionConverterTest.php | 5 ++- .../AuthApiExceptionConverterTest.php | 8 ++-- .../DatabaseApiExceptionConverterTest.php | 6 ++- .../MessagingApiExceptionConverterTest.php | 5 ++- 13 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 tests/CreatesRequestExceptions.php diff --git a/CHANGELOG.md b/CHANGELOG.md index cae53570e..ad52654df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Please update your remote URL if you have forked or cloned the repository. ## Unreleased +* Added support for `guzzlehttp/guzzle:^8.0`, `guzzlehttp/psr7:^3.0` and `guzzlehttp/promises:^3.0`. + ## 8.3.0 - 2026-07-18 ### Security improvements diff --git a/composer.json b/composer.json index 9c91fc6c1..91446d26d 100644 --- a/composer.json +++ b/composer.json @@ -42,9 +42,9 @@ "firebase/php-jwt": "^6.10.2 || ^7.0.2", "google/auth": "^1.45", "google/cloud-storage": "^1.50.0 || ^2.0.0", - "guzzlehttp/guzzle": "^7.9.2", - "guzzlehttp/promises": "^2.0.4", - "guzzlehttp/psr7": "^2.7", + "guzzlehttp/guzzle": "^7.9.2 || ^8.0", + "guzzlehttp/promises": "^2.0.4 || ^3.0", + "guzzlehttp/psr7": "^2.7 || ^3.0", "kreait/firebase-tokens": "^5.2", "lcobucci/jwt": "^5.3", "mtdowling/jmespath.php": "^2.9.2", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c0dc2dfad..8c740cab3 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -23,6 +23,46 @@ parameters: identifier: staticMethod.dynamicCall path: tests + # The following errors depend on which Guzzle, Promises and PSR-7 majors are installed + - + message: '#^Call to function method_exists\(\) with GuzzleHttp\\Exception\\RequestException and ''getResponse'' will always evaluate to true\.$#' + path: src/Exception + reportUnmatched: false + - + message: '#^Parameter \#1 \$config of class GuzzleHttp\\Client constructor expects array#' + path: src/Factory.php + reportUnmatched: false + - + message: '#^Parameter \#3 \$config of class GuzzleHttp\\Pool constructor expects array#' + path: src/Messaging/ApiClient.php + reportUnmatched: false + - + message: '#^Casting to string something that''s already string\.$#' + paths: + - src/Messaging.php + - src/Messaging/AppInstanceApiClient.php + reportUnmatched: false + - + message: '#^Offset ''value'' might not exist on array#' + path: src/Messaging.php + reportUnmatched: false + - + message: '#^Method Kreait\\Firebase\\Messaging::unsubscribeFromAllTopics\(\) should return array> but returns array\|string>\.$#' + path: src/Messaging.php + reportUnmatched: false + - + message: '#^Provide more specific return type "GuzzleHttp\\Promise\\PromiseInterface" over abstract one$#' + paths: + - src/Messaging/ApiClient.php + - src/Messaging/AppInstanceApiClient.php + reportUnmatched: false + + # Only reported with Guzzle 7; Guzzle 8's ResponseException extends RequestException + - + message: '#should return GuzzleHttp\\Exception\\RequestException but returns GuzzleHttp\\Exception\\ResponseException#' + path: tests/CreatesRequestExceptions.php + reportUnmatched: false + editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' exceptions: diff --git a/src/Exception/AppCheckApiExceptionConverter.php b/src/Exception/AppCheckApiExceptionConverter.php index 0716d2de7..38bf7acc2 100644 --- a/src/Exception/AppCheckApiExceptionConverter.php +++ b/src/Exception/AppCheckApiExceptionConverter.php @@ -43,7 +43,7 @@ private function convertGuzzleRequestException(RequestException $e): AppCheckExc { $message = $e->getMessage(); $code = $e->getCode(); - $response = $e->getResponse(); + $response = method_exists($e, 'getResponse') ? $e->getResponse() : null; if ($response instanceof ResponseInterface) { $message = $this->responseParser->getErrorReasonFromResponse($response); diff --git a/src/Exception/AuthApiExceptionConverter.php b/src/Exception/AuthApiExceptionConverter.php index becd82d47..30aed671b 100644 --- a/src/Exception/AuthApiExceptionConverter.php +++ b/src/Exception/AuthApiExceptionConverter.php @@ -57,7 +57,7 @@ private function convertGuzzleRequestException(RequestException $e): AuthExcepti { $message = $e->getMessage(); $code = $e->getCode(); - $response = $e->getResponse(); + $response = method_exists($e, 'getResponse') ? $e->getResponse() : null; if ($response instanceof ResponseInterface) { $message = $this->responseParser->getErrorReasonFromResponse($response); diff --git a/src/Exception/DatabaseApiExceptionConverter.php b/src/Exception/DatabaseApiExceptionConverter.php index c7200d766..7e0b4e787 100644 --- a/src/Exception/DatabaseApiExceptionConverter.php +++ b/src/Exception/DatabaseApiExceptionConverter.php @@ -45,7 +45,7 @@ private function convertGuzzleRequestException(RequestException $e): DatabaseExc { $message = $e->getMessage(); $code = $e->getCode(); - $response = $e->getResponse(); + $response = method_exists($e, 'getResponse') ? $e->getResponse() : null; if ($response instanceof ResponseInterface) { $message = $this->responseParser->getErrorReasonFromResponse($response); diff --git a/src/Exception/MessagingApiExceptionConverter.php b/src/Exception/MessagingApiExceptionConverter.php index 7f368af46..e512626e5 100644 --- a/src/Exception/MessagingApiExceptionConverter.php +++ b/src/Exception/MessagingApiExceptionConverter.php @@ -139,7 +139,7 @@ public function convertResponse(ResponseInterface $response, ?Throwable $previou private function convertGuzzleRequestException(RequestException $e): MessagingException { - $response = $e->getResponse(); + $response = method_exists($e, 'getResponse') ? $e->getResponse() : null; if ($response instanceof ResponseInterface) { return $this->convertResponse($response, $e); diff --git a/src/Exception/RemoteConfigApiExceptionConverter.php b/src/Exception/RemoteConfigApiExceptionConverter.php index e9163b1e4..887b899a6 100644 --- a/src/Exception/RemoteConfigApiExceptionConverter.php +++ b/src/Exception/RemoteConfigApiExceptionConverter.php @@ -4,7 +4,6 @@ namespace Kreait\Firebase\Exception; -use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use Kreait\Firebase\Exception\RemoteConfig\ApiConnectionFailed; use Kreait\Firebase\Exception\RemoteConfig\OperationAborted; @@ -13,6 +12,7 @@ use Kreait\Firebase\Exception\RemoteConfig\ValidationFailed; use Kreait\Firebase\Exception\RemoteConfig\VersionMismatch; use Kreait\Firebase\Http\ErrorResponseParser; +use Psr\Http\Client\NetworkExceptionInterface; use Psr\Http\Message\ResponseInterface; use Throwable; @@ -33,7 +33,7 @@ public function convertException(Throwable $exception): RemoteConfigException return $this->convertGuzzleRequestException($exception); } - if ($exception instanceof ConnectException) { + if ($exception instanceof NetworkExceptionInterface) { return new ApiConnectionFailed( message: 'Unable to connect to the API: '.$exception->getMessage(), previous: $exception @@ -47,7 +47,7 @@ private function convertGuzzleRequestException(RequestException $e): RemoteConfi { $message = $e->getMessage(); $code = $e->getCode(); - $response = $e->getResponse(); + $response = method_exists($e, 'getResponse') ? $e->getResponse() : null; if ($response instanceof ResponseInterface) { $message = $this->responseParser->getErrorReasonFromResponse($response); diff --git a/tests/CreatesRequestExceptions.php b/tests/CreatesRequestExceptions.php new file mode 100644 index 000000000..f9ea525b9 --- /dev/null +++ b/tests/CreatesRequestExceptions.php @@ -0,0 +1,27 @@ +request, new Response(400, [], $responseBody = '{"what is this"'), diff --git a/tests/Unit/Exception/MessagingApiExceptionConverterTest.php b/tests/Unit/Exception/MessagingApiExceptionConverterTest.php index 47a9fa572..7c10b6eae 100644 --- a/tests/Unit/Exception/MessagingApiExceptionConverterTest.php +++ b/tests/Unit/Exception/MessagingApiExceptionConverterTest.php @@ -21,6 +21,7 @@ use Kreait\Firebase\Exception\Messaging\ServerError; use Kreait\Firebase\Exception\Messaging\ServerUnavailable; use Kreait\Firebase\Exception\MessagingApiExceptionConverter; +use Kreait\Firebase\Tests\CreatesRequestExceptions; use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; @@ -35,6 +36,8 @@ */ final class MessagingApiExceptionConverterTest extends TestCase { + use CreatesRequestExceptions; + private MessagingApiExceptionConverter $converter; private FrozenClock $clock; @@ -87,7 +90,7 @@ public static function exceptions(): Iterator public static function createRequestException(int $code, string $identifier): RequestException { - return new RequestException( + return self::createGuzzleRequestException( 'Firebase Error Test', new Request('GET', 'https://example.com'), new Response($code, [], Json::encode([