From a94b6a3d3edbcc7fb25662ce5553f3ef8f101ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Thu, 4 Dec 2025 00:08:52 +0100 Subject: [PATCH 1/3] Drop exception code when wrapping exceptions `Throwable::getCode()` can be non-int. --- UPGRADE-8.0.md | 5 +++++ phpstan.neon.dist | 1 + src/Firebase/AppCheck/AppCheckTokenVerifier.php | 4 ++-- src/Firebase/Auth.php | 4 ++-- .../Auth/CreateActionLink/GuzzleApiClientHandler.php | 10 ++++++++-- .../Auth/SendActionLink/GuzzleApiClientHandler.php | 5 ++++- src/Firebase/Auth/SignIn/FailedToSignIn.php | 5 ++++- src/Firebase/Database.php | 2 +- src/Firebase/Database/Query.php | 6 +++++- src/Firebase/Database/Reference.php | 2 +- .../Exception/AppCheckApiExceptionConverter.php | 7 +++++-- src/Firebase/Exception/AuthApiExceptionConverter.php | 7 +++++-- src/Firebase/Exception/Database/TransactionFailed.php | 8 +++----- src/Firebase/Exception/Database/UnsupportedQuery.php | 5 ++--- .../Exception/DatabaseApiExceptionConverter.php | 7 +++++-- .../Exception/Messaging/AuthenticationError.php | 2 +- src/Firebase/Exception/Messaging/InvalidMessage.php | 2 +- src/Firebase/Exception/Messaging/MessagingError.php | 2 +- src/Firebase/Exception/Messaging/NotFound.php | 2 +- src/Firebase/Exception/Messaging/QuotaExceeded.php | 4 ++-- src/Firebase/Exception/Messaging/ServerError.php | 2 +- src/Firebase/Exception/Messaging/ServerUnavailable.php | 4 ++-- .../Exception/MessagingApiExceptionConverter.php | 9 ++++++--- .../Exception/RemoteConfigApiExceptionConverter.php | 7 +++++-- src/Firebase/Factory.php | 5 ++++- src/Firebase/Firestore.php | 5 ++++- src/Firebase/Messaging.php | 5 ++++- 27 files changed, 85 insertions(+), 42 deletions(-) diff --git a/UPGRADE-8.0.md b/UPGRADE-8.0.md index 4613673a1..bcf107585 100644 --- a/UPGRADE-8.0.md +++ b/UPGRADE-8.0.md @@ -17,6 +17,11 @@ from 7.x to 8.0. The following list has been generated with [roave/backward-compatibility-check](https://github.com/Roave/BackwardCompatibilityCheck). ``` +[BC] CHANGED: Default parameter value for parameter $code of Kreait\Firebase\Exception\Database\TransactionFailed#__construct() changed from 0 to NULL +[BC] CHANGED: Default parameter value for parameter $code of Kreait\Firebase\Exception\Database\UnsupportedQuery#__construct() changed from 0 to NULL +[BC] CHANGED: The number of required arguments for Kreait\Firebase\Exception\Database\UnsupportedQuery#__construct() increased from 1 to 2 +[BC] CHANGED: The parameter $code of Kreait\Firebase\Exception\Database\TransactionFailed#__construct() changed from int to a non-contravariant Throwable|null +[BC] CHANGED: The parameter $code of Kreait\Firebase\Exception\Database\UnsupportedQuery#__construct() changed from int to a non-contravariant Throwable|null [BC] CHANGED: The parameter $uid of Kreait\Firebase\Request\EditUserTrait#withUid() changed from no type to Stringable|string [BC] CHANGED: The parameter $uid of Kreait\Firebase\Request\EditUserTrait#withUid() changed from no type to a non-contravariant Stringable|string [BC] CHANGED: The parameter $uid of Kreait\Firebase\Request\EditUserTrait#withUid() changed from no type to a non-contravariant Stringable|string diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bec0913fe..a712d5318 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -23,6 +23,7 @@ parameters: checkUninitializedProperties: true + checkBenevolentUnionTypes: true checkTooWideReturnTypesInProtectedAndPublicMethods: true reportAlwaysTrueInLastCondition: true reportPossiblyNonexistentConstantArrayOffset: true diff --git a/src/Firebase/AppCheck/AppCheckTokenVerifier.php b/src/Firebase/AppCheck/AppCheckTokenVerifier.php index dd696775b..aa299f59a 100644 --- a/src/Firebase/AppCheck/AppCheckTokenVerifier.php +++ b/src/Firebase/AppCheck/AppCheckTokenVerifier.php @@ -61,9 +61,9 @@ private function decodeJwt(string $token): DecodedAppCheckToken /** @var DecodedAppCheckTokenShape $payload */ $payload = (array) JWT::decode($token, $this->keySet); } catch (LogicException $e) { - throw new InvalidAppCheckToken($e->getMessage(), $e->getCode(), $e); + throw new InvalidAppCheckToken(message: $e->getMessage(), previous: $e); } catch (Throwable $e) { - throw new FailedToVerifyAppCheckToken($e->getMessage(), $e->getCode(), $e); + throw new FailedToVerifyAppCheckToken(message: $e->getMessage(), previous: $e); } return DecodedAppCheckToken::fromArray($payload); diff --git a/src/Firebase/Auth.php b/src/Firebase/Auth.php index d191e6675..ff02f70ac 100644 --- a/src/Firebase/Auth.php +++ b/src/Firebase/Auth.php @@ -312,13 +312,13 @@ public function sendEmailActionLink(string $type, Stringable|string $email, $act try { $user = $this->getUserByEmail($email); } catch (Throwable $e) { - throw new FailedToSendActionLink($e->getMessage(), $e->getCode(), $e); + throw new FailedToSendActionLink(message: $e->getMessage(), previous: $e); } try { $signInResult = $this->signInAsUser($user); } catch (Throwable $e) { - throw new FailedToSendActionLink($e->getMessage(), $e->getCode(), $e); + throw new FailedToSendActionLink(message: $e->getMessage(), previous: $e); } $idToken = $signInResult->idToken(); diff --git a/src/Firebase/Auth/CreateActionLink/GuzzleApiClientHandler.php b/src/Firebase/Auth/CreateActionLink/GuzzleApiClientHandler.php index 0bb0c2a05..630b58405 100644 --- a/src/Firebase/Auth/CreateActionLink/GuzzleApiClientHandler.php +++ b/src/Firebase/Auth/CreateActionLink/GuzzleApiClientHandler.php @@ -40,7 +40,10 @@ public function handle(CreateActionLink $action): string try { $response = $this->client->send($request, ['http_errors' => false]); } catch (ClientExceptionInterface $e) { - throw new FailedToCreateActionLink('Failed to create action link: '.$e->getMessage(), $e->getCode(), $e); + throw new FailedToCreateActionLink( + message: 'Failed to create action link: '.$e->getMessage(), + previous: $e + ); } if ($response->getStatusCode() !== 200) { @@ -50,7 +53,10 @@ public function handle(CreateActionLink $action): string try { $data = Json::decode((string) $response->getBody(), true); } catch (InvalidArgumentException $e) { - throw new FailedToCreateActionLink('Unable to parse the response data: '.$e->getMessage(), $e->getCode(), $e); + throw new FailedToCreateActionLink( + message: 'Unable to parse the response data: '.$e->getMessage(), + previous: $e + ); } $actionCode = $data['oobLink'] ?? null; diff --git a/src/Firebase/Auth/SendActionLink/GuzzleApiClientHandler.php b/src/Firebase/Auth/SendActionLink/GuzzleApiClientHandler.php index 2aa483c79..ee57a7e2e 100644 --- a/src/Firebase/Auth/SendActionLink/GuzzleApiClientHandler.php +++ b/src/Firebase/Auth/SendActionLink/GuzzleApiClientHandler.php @@ -39,7 +39,10 @@ public function handle(SendActionLink $action): void try { $response = $this->client->send($request, ['http_errors' => false]); } catch (ClientExceptionInterface $e) { - throw new FailedToSendActionLink('Failed to send action link: '.$e->getMessage(), $e->getCode(), $e); + throw new FailedToSendActionLink( + message: 'Failed to send action link: '.$e->getMessage(), + previous: $e + ); } if ($response->getStatusCode() !== 200) { diff --git a/src/Firebase/Auth/SignIn/FailedToSignIn.php b/src/Firebase/Auth/SignIn/FailedToSignIn.php index b3d1d339e..f59e142fa 100644 --- a/src/Firebase/Auth/SignIn/FailedToSignIn.php +++ b/src/Firebase/Auth/SignIn/FailedToSignIn.php @@ -37,7 +37,10 @@ public static function withActionAndResponse(SignIn $action, ResponseInterface $ public static function fromPrevious(Throwable $e): self { - return new self('Sign in failed: '.$e->getMessage(), $e->getCode(), $e); + return new self( + message: 'Sign in failed: '.$e->getMessage(), + previous: $e + ); } public function action(): ?SignIn diff --git a/src/Firebase/Database.php b/src/Firebase/Database.php index 2fc36b2ec..cc4fd36c4 100644 --- a/src/Firebase/Database.php +++ b/src/Firebase/Database.php @@ -38,7 +38,7 @@ public function getReference(?string $path = null): Reference try { return new Reference($this->uri->withPath($path), $this->client); } catch (\InvalidArgumentException $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + throw new InvalidArgumentException(message: $e->getMessage(), previous: $e); } } diff --git a/src/Firebase/Database/Query.php b/src/Firebase/Database/Query.php index cc26e66df..66a52bbe4 100644 --- a/src/Firebase/Database/Query.php +++ b/src/Firebase/Database/Query.php @@ -84,7 +84,11 @@ public function getSnapshot(): Snapshot } catch (DatabaseNotFound $e) { throw $e; } catch (DatabaseException $e) { - throw new UnsupportedQuery($this, $e->getMessage(), $e->getCode(), $e->getPrevious()); + throw new UnsupportedQuery( + query: $this, + message: $e->getMessage(), + previous: $e->getPrevious() + ); } if ($this->sorter !== null) { diff --git a/src/Firebase/Database/Reference.php b/src/Firebase/Database/Reference.php index a2f96f506..28ae6615e 100644 --- a/src/Firebase/Database/Reference.php +++ b/src/Firebase/Database/Reference.php @@ -106,7 +106,7 @@ public function getChild(string $path): self try { return new self($this->uri->withPath($childPath), $this->apiClient); } catch (\InvalidArgumentException $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + throw new InvalidArgumentException(message: $e->getMessage(), previous: $e); } } diff --git a/src/Firebase/Exception/AppCheckApiExceptionConverter.php b/src/Firebase/Exception/AppCheckApiExceptionConverter.php index c30be42bc..8861e1aa2 100644 --- a/src/Firebase/Exception/AppCheckApiExceptionConverter.php +++ b/src/Firebase/Exception/AppCheckApiExceptionConverter.php @@ -29,10 +29,13 @@ public function convertException(Throwable $exception): AppCheckException } if ($exception instanceof NetworkExceptionInterface) { - return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception); + return new ApiConnectionFailed( + message: 'Unable to connect to the API: '.$exception->getMessage(), + previous: $exception + ); } - return new AppCheckError($exception->getMessage(), $exception->getCode(), $exception); + return new AppCheckError(message: $exception->getMessage(), previous: $exception); } private function convertGuzzleRequestException(RequestException $e): AppCheckException diff --git a/src/Firebase/Exception/AuthApiExceptionConverter.php b/src/Firebase/Exception/AuthApiExceptionConverter.php index 8d41a161b..cce5ca359 100644 --- a/src/Firebase/Exception/AuthApiExceptionConverter.php +++ b/src/Firebase/Exception/AuthApiExceptionConverter.php @@ -43,10 +43,13 @@ public function convertException(Throwable $exception): AuthException } if ($exception instanceof NetworkExceptionInterface) { - return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception); + return new ApiConnectionFailed( + message: 'Unable to connect to the API: '.$exception->getMessage(), + previous: $exception + ); } - return new AuthError($exception->getMessage(), $exception->getCode(), $exception); + return new AuthError(message: $exception->getMessage(), previous: $exception); } private function convertGuzzleRequestException(RequestException $e): AuthException diff --git a/src/Firebase/Exception/Database/TransactionFailed.php b/src/Firebase/Exception/Database/TransactionFailed.php index 6131ea47b..6ce5ccb48 100644 --- a/src/Firebase/Exception/Database/TransactionFailed.php +++ b/src/Firebase/Exception/Database/TransactionFailed.php @@ -15,7 +15,7 @@ final class TransactionFailed extends RuntimeException implements DatabaseExcept { private readonly Reference $reference; - public function __construct(Reference $query, string $message = '', int $code = 0, ?Throwable $previous = null) + public function __construct(Reference $query, string $message = '', ?Throwable $previous = null) { if (trim($message) === '') { $queryPath = $query->getPath(); @@ -29,16 +29,14 @@ public function __construct(Reference $query, string $message = '', int $code = } } - parent::__construct($message, $code, $previous); + parent::__construct(message: $message, previous: $previous); $this->reference = $query; } public static function onReference(Reference $reference, ?Throwable $error = null): self { - $code = $error !== null ? $error->getCode() : 0; - - return new self($reference, '', $code, $error); + return new self($reference, $error?->getMessage() ?? '', $error); } public function getReference(): Reference diff --git a/src/Firebase/Exception/Database/UnsupportedQuery.php b/src/Firebase/Exception/Database/UnsupportedQuery.php index 6c55b10c2..7f641f255 100644 --- a/src/Firebase/Exception/Database/UnsupportedQuery.php +++ b/src/Firebase/Exception/Database/UnsupportedQuery.php @@ -13,11 +13,10 @@ final class UnsupportedQuery extends RuntimeException implements DatabaseExcepti { public function __construct( private readonly Query $query, - string $message = '', - int $code = 0, + string $message, ?Throwable $previous = null, ) { - parent::__construct($message, $code, $previous); + parent::__construct(message: $message, previous: $previous); } public function getQuery(): Query diff --git a/src/Firebase/Exception/DatabaseApiExceptionConverter.php b/src/Firebase/Exception/DatabaseApiExceptionConverter.php index 9030ac31a..b2186dad2 100644 --- a/src/Firebase/Exception/DatabaseApiExceptionConverter.php +++ b/src/Firebase/Exception/DatabaseApiExceptionConverter.php @@ -31,10 +31,13 @@ public function convertException(Throwable $exception): DatabaseException } if ($exception instanceof NetworkExceptionInterface) { - return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception); + return new ApiConnectionFailed( + message: 'Unable to connect to the API: '.$exception->getMessage(), + previous: $exception + ); } - return new DatabaseError($exception->getMessage(), $exception->getCode(), $exception); + return new DatabaseError(message: $exception->getMessage(), previous: $exception); } private function convertGuzzleRequestException(RequestException $e): DatabaseException diff --git a/src/Firebase/Exception/Messaging/AuthenticationError.php b/src/Firebase/Exception/Messaging/AuthenticationError.php index 39d1c21e0..392a29907 100644 --- a/src/Firebase/Exception/Messaging/AuthenticationError.php +++ b/src/Firebase/Exception/Messaging/AuthenticationError.php @@ -19,7 +19,7 @@ final class AuthenticationError extends RuntimeException implements MessagingExc */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; return $new; diff --git a/src/Firebase/Exception/Messaging/InvalidMessage.php b/src/Firebase/Exception/Messaging/InvalidMessage.php index 6fc747323..a99b83a58 100644 --- a/src/Firebase/Exception/Messaging/InvalidMessage.php +++ b/src/Firebase/Exception/Messaging/InvalidMessage.php @@ -19,7 +19,7 @@ final class InvalidMessage extends RuntimeException implements MessagingExceptio */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; return $new; diff --git a/src/Firebase/Exception/Messaging/MessagingError.php b/src/Firebase/Exception/Messaging/MessagingError.php index 19d308d8a..25080674b 100644 --- a/src/Firebase/Exception/Messaging/MessagingError.php +++ b/src/Firebase/Exception/Messaging/MessagingError.php @@ -19,7 +19,7 @@ final class MessagingError extends RuntimeException implements MessagingExceptio */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; return $new; diff --git a/src/Firebase/Exception/Messaging/NotFound.php b/src/Firebase/Exception/Messaging/NotFound.php index 0054705fe..7fd30d1d6 100644 --- a/src/Firebase/Exception/Messaging/NotFound.php +++ b/src/Firebase/Exception/Messaging/NotFound.php @@ -57,7 +57,7 @@ public static function becauseTokenNotFound(string $token, array $errors = []): */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; return $new; diff --git a/src/Firebase/Exception/Messaging/QuotaExceeded.php b/src/Firebase/Exception/Messaging/QuotaExceeded.php index 36c22bc1b..ed6c03adf 100644 --- a/src/Firebase/Exception/Messaging/QuotaExceeded.php +++ b/src/Firebase/Exception/Messaging/QuotaExceeded.php @@ -22,7 +22,7 @@ final class QuotaExceeded extends RuntimeException implements MessagingException */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; $new->retryAfter = $this->retryAfter; @@ -31,7 +31,7 @@ public function withErrors(array $errors): self public function withRetryAfter(DateTimeImmutable $retryAfter): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $this->errors; $new->retryAfter = $retryAfter; diff --git a/src/Firebase/Exception/Messaging/ServerError.php b/src/Firebase/Exception/Messaging/ServerError.php index a7b87838d..d656b81a0 100644 --- a/src/Firebase/Exception/Messaging/ServerError.php +++ b/src/Firebase/Exception/Messaging/ServerError.php @@ -19,7 +19,7 @@ final class ServerError extends RuntimeException implements MessagingException */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; return $new; diff --git a/src/Firebase/Exception/Messaging/ServerUnavailable.php b/src/Firebase/Exception/Messaging/ServerUnavailable.php index d6fe95c88..f48a4d7c6 100644 --- a/src/Firebase/Exception/Messaging/ServerUnavailable.php +++ b/src/Firebase/Exception/Messaging/ServerUnavailable.php @@ -22,7 +22,7 @@ final class ServerUnavailable extends RuntimeException implements MessagingExcep */ public function withErrors(array $errors): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $errors; $new->retryAfter = $this->retryAfter; @@ -31,7 +31,7 @@ public function withErrors(array $errors): self public function withRetryAfter(DateTimeImmutable $retryAfter): self { - $new = new self($this->getMessage(), $this->getCode(), $this->getPrevious()); + $new = new self(message: $this->getMessage(), previous: $this->getPrevious()); $new->errors = $this->errors; $new->retryAfter = $retryAfter; diff --git a/src/Firebase/Exception/MessagingApiExceptionConverter.php b/src/Firebase/Exception/MessagingApiExceptionConverter.php index 23aa406c7..ad5f31837 100644 --- a/src/Firebase/Exception/MessagingApiExceptionConverter.php +++ b/src/Firebase/Exception/MessagingApiExceptionConverter.php @@ -47,10 +47,13 @@ public function convertException(Throwable $exception): MessagingException } if ($exception instanceof NetworkExceptionInterface) { - return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception); + return new ApiConnectionFailed( + message: 'Unable to connect to the API: '.$exception->getMessage(), + previous: $exception + ); } - return new MessagingError($exception->getMessage(), $exception->getCode(), $exception); + return new MessagingError(message: $exception->getMessage(), previous: $exception); } public function convertResponse(ResponseInterface $response, ?Throwable $previous = null): MessagingException @@ -142,7 +145,7 @@ private function convertGuzzleRequestException(RequestException $e): MessagingEx return $this->convertResponse($response, $e); } - return new MessagingError($e->getMessage(), $e->getCode(), $e); + return new MessagingError(message: $e->getMessage(), previous: $e); } private function getRetryAfter(ResponseInterface $response): ?DateTimeImmutable diff --git a/src/Firebase/Exception/RemoteConfigApiExceptionConverter.php b/src/Firebase/Exception/RemoteConfigApiExceptionConverter.php index 7a5a4bc50..0e3d5efc0 100644 --- a/src/Firebase/Exception/RemoteConfigApiExceptionConverter.php +++ b/src/Firebase/Exception/RemoteConfigApiExceptionConverter.php @@ -33,10 +33,13 @@ public function convertException(Throwable $exception): RemoteConfigException } if ($exception instanceof ConnectException) { - return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception); + return new ApiConnectionFailed( + message: 'Unable to connect to the API: '.$exception->getMessage(), + previous: $exception + ); } - return new RemoteConfigError($exception->getMessage(), $exception->getCode(), $exception); + return new RemoteConfigError(message: $exception->getMessage(), previous: $exception); } private function convertGuzzleRequestException(RequestException $e): RemoteConfigException diff --git a/src/Firebase/Factory.php b/src/Firebase/Factory.php index b98833e75..bbc831509 100644 --- a/src/Firebase/Factory.php +++ b/src/Firebase/Factory.php @@ -499,7 +499,10 @@ public function createStorage(): Contract\Storage try { $storageClient = new StorageClient($this->googleCloudClientConfig()); } catch (Throwable $e) { - throw new RuntimeException('Unable to create a StorageClient: '.$e->getMessage(), $e->getCode(), $e); + throw new RuntimeException( + message: 'Unable to create a StorageClient: '.$e->getMessage(), + previous: $e + ); } return new Storage($storageClient, $this->getStorageBucketName()); diff --git a/src/Firebase/Firestore.php b/src/Firebase/Firestore.php index 99adb3e4d..398c407dd 100644 --- a/src/Firebase/Firestore.php +++ b/src/Firebase/Firestore.php @@ -25,7 +25,10 @@ public static function fromConfig(array $config): Contract\Firestore try { return new self(new FirestoreClient($config)); } catch (Throwable $e) { - throw new RuntimeException('Unable to create a FirestoreClient: '.$e->getMessage(), $e->getCode(), $e); + throw new RuntimeException( + message: 'Unable to create a FirestoreClient: '.$e->getMessage(), + previous: $e + ); } } diff --git a/src/Firebase/Messaging.php b/src/Firebase/Messaging.php index 08c9201dd..12c55a850 100644 --- a/src/Firebase/Messaging.php +++ b/src/Firebase/Messaging.php @@ -211,7 +211,10 @@ public function getAppInstance(RegistrationToken|string $registrationToken): App throw NotFound::becauseTokenNotFound($token->value(), $e->errors()); } catch (MessagingException $e) { // The token is invalid - throw new InvalidArgument("The registration token '{$token}' is invalid or not available", $e->getCode(), $e); + throw new InvalidArgument( + message: "The registration token '{$token}' is invalid or not available", + previous: $e + ); } } From ab4c979832a0f69a72aa380e93ed51a4d9f23027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Thu, 4 Dec 2025 00:28:30 +0100 Subject: [PATCH 2/3] Treat PHPDoc types as certain --- phpstan.neon.dist | 5 ++--- .../ActionCodeSettings/ValidatedActionCodeSettings.php | 4 +--- src/Firebase/Messaging/AppInstance.php | 2 +- src/Firebase/RemoteConfig/Template.php | 6 +++--- src/Firebase/Valinor/Source.php | 10 ++++++++-- tests/Unit/Http/HttpClientOptionsTest.php | 7 ------- tests/Unit/ServiceAccountTest.php | 6 +++--- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a712d5318..28d3b773b 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -21,13 +21,12 @@ parameters: identifier: staticMethod.dynamicCall path: tests - checkUninitializedProperties: true - checkBenevolentUnionTypes: true checkTooWideReturnTypesInProtectedAndPublicMethods: true + checkUninitializedProperties: true reportAlwaysTrueInLastCondition: true reportPossiblyNonexistentConstantArrayOffset: true - treatPhpDocTypesAsCertain: false + includes: - vendor/cuyz/valinor/qa/PHPStan/valinor-phpstan-configuration.php - vendor/phpstan/phpstan/conf/bleedingEdge.neon diff --git a/src/Firebase/Auth/ActionCodeSettings/ValidatedActionCodeSettings.php b/src/Firebase/Auth/ActionCodeSettings/ValidatedActionCodeSettings.php index e23404c6e..59063dba0 100644 --- a/src/Firebase/Auth/ActionCodeSettings/ValidatedActionCodeSettings.php +++ b/src/Firebase/Auth/ActionCodeSettings/ValidatedActionCodeSettings.php @@ -60,9 +60,7 @@ public static function fromArray(array $settings): self switch (mb_strtolower($key)) { case 'continueurl': case 'url': - $instance->continueUrl = ($value !== null) - ? Utils::uriFor(self::ensureNonEmptyString($value)) - : null; + $instance->continueUrl = Utils::uriFor(self::ensureNonEmptyString($value)); break; diff --git a/src/Firebase/Messaging/AppInstance.php b/src/Firebase/Messaging/AppInstance.php index 2f3d6194b..d1611531d 100644 --- a/src/Firebase/Messaging/AppInstance.php +++ b/src/Firebase/Messaging/AppInstance.php @@ -38,7 +38,7 @@ public static function fromRawData(RegistrationToken $registrationToken, array $ $subscriptions = []; foreach ($rawData['rel']['topics'] ?? [] as $topicName => $subscriptionInfo) { - $topic = Topic::fromValue((string) $topicName); + $topic = Topic::fromValue($topicName); $addedAt = DT::toUTCDateTimeImmutable($subscriptionInfo['addDate'] ?? null); $subscriptions[] = new TopicSubscription($topic, $registrationToken, $addedAt); } diff --git a/src/Firebase/RemoteConfig/Template.php b/src/Firebase/RemoteConfig/Template.php index f1510208b..9d1355ebc 100644 --- a/src/Firebase/RemoteConfig/Template.php +++ b/src/Firebase/RemoteConfig/Template.php @@ -244,12 +244,12 @@ private static function buildParameter(string $name, array $data): Parameter $valueType = ParameterValueType::tryFrom($data['valueType'] ?? '') ?? ParameterValueType::UNSPECIFIED; $parameter = Parameter::named($name) - ->withDescription((string) ($data['description'] ?? '')) + ->withDescription($data['description'] ?? '') ->withDefaultValue($data['defaultValue'] ?? null) ->withValueType($valueType) ; - foreach ((array) ($data['conditionalValues'] ?? []) as $key => $conditionalValueData) { + foreach (($data['conditionalValues'] ?? []) as $key => $conditionalValueData) { $parameter = $parameter->withConditionalValue(new ConditionalValue($key, ParameterValue::fromArray($conditionalValueData))); } @@ -263,7 +263,7 @@ private static function buildParameter(string $name, array $data): Parameter private static function buildParameterGroup(string $name, array $parameterGroupData): ParameterGroup { $group = ParameterGroup::named($name) - ->withDescription((string) ($parameterGroupData['description'] ?? '')) + ->withDescription($parameterGroupData['description'] ?? '') ; foreach ($parameterGroupData['parameters'] as $parameterName => $parameterData) { diff --git a/src/Firebase/Valinor/Source.php b/src/Firebase/Valinor/Source.php index 3da7a2932..ee611d14d 100644 --- a/src/Firebase/Valinor/Source.php +++ b/src/Firebase/Valinor/Source.php @@ -57,11 +57,17 @@ public static function file(string $value): self throw new InvalidArgumentException(message: $e->getMessage(), previous: $e); } - $content = $file->fread($file->getSize()); + $fileSize = $file->getSize(); $pathName = $file->getPathname(); + if (!is_int($fileSize)) { + throw new InvalidArgumentException("Unable to get filesize of `$pathName`"); + } + + $content = $file->fread($fileSize); + if ($content === false) { - throw new InvalidArgumentException("Unable to parse `$pathName`"); + throw new InvalidArgumentException("Unable to read `$pathName`"); } return self::json($content); diff --git a/tests/Unit/Http/HttpClientOptionsTest.php b/tests/Unit/Http/HttpClientOptionsTest.php index b09044898..73e990600 100644 --- a/tests/Unit/Http/HttpClientOptionsTest.php +++ b/tests/Unit/Http/HttpClientOptionsTest.php @@ -103,8 +103,6 @@ public function itAcceptsSingleCallableMiddlewares(): void $middlewares = $options->guzzleMiddlewares(); $this->assertCount(1, $middlewares); - $this->assertIsCallable($middlewares[0]['middleware']); - $this->assertSame('name', $middlewares[0]['name']); } #[Test] @@ -128,13 +126,8 @@ public static function handle(): void $this->assertCount(3, $middlewares); - $this->assertIsCallable($middlewares[0]['middleware']); $this->assertSame('', $middlewares[0]['name']); - - $this->assertIsCallable($middlewares[1]['middleware']); $this->assertSame('Foo', $middlewares[1]['name']); - - $this->assertIsCallable($middlewares[2]['middleware']); $this->assertSame('Bar', $middlewares[2]['name']); } diff --git a/tests/Unit/ServiceAccountTest.php b/tests/Unit/ServiceAccountTest.php index 8c877819e..b5fa26a68 100644 --- a/tests/Unit/ServiceAccountTest.php +++ b/tests/Unit/ServiceAccountTest.php @@ -6,6 +6,7 @@ use Kreait\Firebase\ServiceAccount; use Kreait\Firebase\Valinor\Mapper; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\TestCase; final class ServiceAccountTest extends TestCase @@ -13,6 +14,7 @@ final class ServiceAccountTest extends TestCase /** * @see https://github.com/kreait/firebase-php/pull/1034 */ + #[DoesNotPerformAssertions] public function testItCanBeMapped(): void { $mapper = (new Mapper())->allowSuperfluousKeys()->snakeToCamelCase(); @@ -24,8 +26,6 @@ public function testItCanBeMapped(): void 'private_key' => 'private-key', ]; - $serviceAccount = $mapper->map(ServiceAccount::class, $input); - - $this->assertInstanceOf(ServiceAccount::class, $serviceAccount); + $mapper->map(ServiceAccount::class, $input); } } From 571636aac68f66b70be457ca3b1052fd22953a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gamez?= Date: Thu, 4 Dec 2025 00:39:39 +0100 Subject: [PATCH 3/3] Add implemented and to-be-implemented PHPStan rules --- phpstan.neon.dist | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 28d3b773b..4f9abe215 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -21,12 +21,23 @@ parameters: identifier: staticMethod.dynamicCall path: tests + editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%' + + exceptions: + checkedExceptionRegexes: + - '#^Kreait\\Firebase\\#' + check: + # missingCheckedExceptionInThrows: true + # tooWideThrowType: true + checkBenevolentUnionTypes: true + # checkMissingCallableSignature: true checkTooWideReturnTypesInProtectedAndPublicMethods: true checkUninitializedProperties: true reportAlwaysTrueInLastCondition: true reportPossiblyNonexistentConstantArrayOffset: true - + # reportPossiblyNonexistentGeneralArrayOffset: true + reportAnyTypeWideningInVarTag: true includes: - vendor/cuyz/valinor/qa/PHPStan/valinor-phpstan-configuration.php - vendor/phpstan/phpstan/conf/bleedingEdge.neon