allow vendord
All checks were successful
Build, Push and Deploy / build-and-push (push) Successful in 3m3s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 16s
Build, Push and Deploy / deploy-staging (push) Successful in 32s
Build, Push and Deploy / deploy-production (push) Has been skipped
Build, Push and Deploy / cleanup (push) Successful in 1s

This commit is contained in:
2026-03-30 14:54:57 +07:00
parent 66aed7c4e8
commit b5e3a778ce
21316 changed files with 2777892 additions and 13 deletions

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\RuntimeException;
final class ApiConnectionFailed extends RuntimeException implements AppCheckException
{
use HasErrors;
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\RuntimeException;
final class AppCheckError extends RuntimeException implements AppCheckException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\RuntimeException;
final class FailedToVerifyAppCheckToken extends RuntimeException implements AppCheckException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidAppCheckToken extends RuntimeException implements AppCheckException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidAppCheckTokenOptions extends RuntimeException implements AppCheckException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\AppCheck;
use Kreait\Firebase\Exception\AppCheckException;
use Kreait\Firebase\Exception\RuntimeException;
final class PermissionDenied extends RuntimeException implements AppCheckException
{
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use Fig\Http\Message\StatusCodeInterface as StatusCode;
use GuzzleHttp\Exception\RequestException;
use Kreait\Firebase\Exception\AppCheck\ApiConnectionFailed;
use Kreait\Firebase\Exception\AppCheck\AppCheckError;
use Kreait\Firebase\Exception\AppCheck\PermissionDenied;
use Kreait\Firebase\Http\ErrorResponseParser;
use Psr\Http\Client\NetworkExceptionInterface;
use Throwable;
/**
* @internal
*/
final class AppCheckApiExceptionConverter
{
public function __construct(private readonly ErrorResponseParser $responseParser)
{
}
public function convertException(Throwable $exception): AppCheckException
{
if ($exception instanceof RequestException) {
return $this->convertGuzzleRequestException($exception);
}
if ($exception instanceof NetworkExceptionInterface) {
return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception);
}
return new AppCheckError($exception->getMessage(), $exception->getCode(), $exception);
}
private function convertGuzzleRequestException(RequestException $e): AppCheckException
{
$message = $e->getMessage();
$code = $e->getCode();
$response = $e->getResponse();
if ($response !== null) {
$message = $this->responseParser->getErrorReasonFromResponse($response);
$code = $response->getStatusCode();
}
return match ($code) {
StatusCode::STATUS_UNAUTHORIZED, StatusCode::STATUS_FORBIDDEN => new PermissionDenied($message, $code, $e),
default => new AppCheckError($message, $code, $e),
};
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
interface AppCheckException extends FirebaseException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class ApiConnectionFailed extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
use Kreait\Firebase\Factory;
final class AuthError extends RuntimeException implements AuthException
{
public static function missingProjectId(string $message): self
{
$factoryClass = Factory::class;
$fullMessage = <<<MSG
{$message}
The current Firebase project is configured without a project ID. The project
ID can be determined automatically with service account credentials, by
providing a `GOOGLE_CLOUD_PROJECT=project_id` environment variable, or
manually by using the respective method when instantiating the SDK's
factory ({$factoryClass}).
MSG;
return new self($fullMessage);
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class CredentialsMismatch extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class EmailExists extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class EmailNotFound extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class ExpiredOobCode extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class FailedToVerifySessionCookie extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class FailedToVerifyToken extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidCustomToken extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidOobCode extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidPassword extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class MissingPassword extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class OperationNotAllowed extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class PhoneNumberExists extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class ProviderLinkFailed extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
use Lcobucci\JWT\Token;
final class RevokedIdToken extends RuntimeException implements AuthException
{
public function __construct(private readonly Token $token)
{
parent::__construct('The Firebase ID token has been revoked.');
}
public function getToken(): Token
{
return $this->token;
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
use Lcobucci\JWT\Token;
final class RevokedSessionCookie extends RuntimeException implements AuthException
{
public function __construct(private readonly Token $token)
{
parent::__construct('The Firebase session cookie has been revoked.');
}
public function getToken(): Token
{
return $this->token;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class UserDisabled extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class UserNotFound extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Auth;
use Kreait\Firebase\Exception\AuthException;
use Kreait\Firebase\Exception\RuntimeException;
final class WeakPassword extends RuntimeException implements AuthException
{
}

View File

@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use GuzzleHttp\Exception\RequestException;
use Kreait\Firebase\Exception\Auth\ApiConnectionFailed;
use Kreait\Firebase\Exception\Auth\AuthError;
use Kreait\Firebase\Exception\Auth\CredentialsMismatch;
use Kreait\Firebase\Exception\Auth\EmailExists;
use Kreait\Firebase\Exception\Auth\EmailNotFound;
use Kreait\Firebase\Exception\Auth\ExpiredOobCode;
use Kreait\Firebase\Exception\Auth\InvalidCustomToken;
use Kreait\Firebase\Exception\Auth\InvalidOobCode;
use Kreait\Firebase\Exception\Auth\InvalidPassword;
use Kreait\Firebase\Exception\Auth\MissingPassword;
use Kreait\Firebase\Exception\Auth\OperationNotAllowed;
use Kreait\Firebase\Exception\Auth\PhoneNumberExists;
use Kreait\Firebase\Exception\Auth\ProviderLinkFailed;
use Kreait\Firebase\Exception\Auth\UserDisabled;
use Kreait\Firebase\Exception\Auth\UserNotFound;
use Kreait\Firebase\Exception\Auth\WeakPassword;
use Kreait\Firebase\Http\ErrorResponseParser;
use Psr\Http\Client\NetworkExceptionInterface;
use Throwable;
use function mb_stripos;
/**
* @internal
*/
class AuthApiExceptionConverter
{
public function __construct(private readonly ErrorResponseParser $responseParser)
{
}
public function convertException(Throwable $exception): AuthException
{
if ($exception instanceof RequestException) {
return $this->convertGuzzleRequestException($exception);
}
if ($exception instanceof NetworkExceptionInterface) {
return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception);
}
return new AuthError($exception->getMessage(), $exception->getCode(), $exception);
}
private function convertGuzzleRequestException(RequestException $e): AuthException
{
$message = $e->getMessage();
$code = $e->getCode();
$response = $e->getResponse();
if ($response !== null) {
$message = $this->responseParser->getErrorReasonFromResponse($response);
$code = $response->getStatusCode();
}
if (mb_stripos($message, 'credentials_mismatch') !== false) {
return new CredentialsMismatch('Invalid custom token: The custom token corresponds to a different Firebase project.', $code, $e);
}
if (mb_stripos($message, 'email_exists') !== false) {
return new EmailExists('The email address is already in use by another account.', $code, $e);
}
if (mb_stripos($message, 'email_not_found') !== false) {
return new EmailNotFound('There is no user record corresponding to this identifier. The user may have been deleted.', $code, $e);
}
if (mb_stripos($message, 'invalid_custom_token') !== false) {
return new InvalidCustomToken('Invalid custom token: The custom token format is incorrect or the token is invalid for some reason (e.g. expired, invalid signature, etc.)', $code, $e);
}
if (mb_stripos($message, 'invalid_password') !== false) {
return new InvalidPassword('The password is invalid or the user does not have a password.', $code, $e);
}
if (mb_stripos($message, 'missing_password') !== false) {
return new MissingPassword('Missing Password', $code, $e);
}
if (mb_stripos($message, 'operation_not_allowed') !== false) {
return new OperationNotAllowed('Operation not allowed.', $code, $e);
}
if (mb_stripos($message, 'user_disabled') !== false) {
return new UserDisabled('The user account has been disabled by an administrator.', $code, $e);
}
if (mb_stripos($message, 'user_not_found') !== false) {
return new UserNotFound('There is no user record corresponding to this identifier. The user may have been deleted.', $code, $e);
}
if (mb_stripos($message, 'weak_password') !== false) {
return new WeakPassword('The password must be 6 characters long or more.', $code, $e);
}
if (mb_stripos($message, 'phone_number_exists') !== false) {
return new PhoneNumberExists('The phone number is already in use by another account.', $code, $e);
}
if (mb_stripos($message, 'invalid_idp_response') !== false) {
return new ProviderLinkFailed('The supplied auth credential is malformed or has expired.', $code, $e);
}
if (mb_stripos($message, 'invalid_id_token') !== false) {
return new ProviderLinkFailed('The user\'s credential is no longer valid. The user must sign in again.', $code, $e);
}
if (mb_stripos($message, 'federated_user_id_already_linked') !== false) {
return new ProviderLinkFailed('This credential is already associated with a different user account.', $code, $e);
}
if (mb_stripos($message, 'expired_oob_code') !== false) {
return new ExpiredOobCode('The action code has expired.', $code, $e);
}
if (mb_stripos($message, 'invalid_oob_code') !== false) {
return new InvalidOobCode('The action code is invalid. This can happen if the code is malformed, expired, or has already been used.', $code, $e);
}
return new AuthError($message, $code, $e);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
interface AuthException extends FirebaseException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
final class ApiConnectionFailed extends RuntimeException implements DatabaseException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
final class DatabaseError extends RuntimeException implements DatabaseException
{
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\LogicException;
use Psr\Http\Message\UriInterface;
use function explode;
use function str_replace;
final class DatabaseNotFound extends LogicException implements DatabaseException
{
public static function fromUri(UriInterface $uri): self
{
$scheme = $uri->getScheme();
$host = $uri->getHost();
$databaseName = explode('.', $host, 2)[0];
$databaseUri = "{$scheme}://{$host}";
$suggestedDatabaseUri = str_replace($databaseName, $databaseName.'-default-rtdb', $databaseUri);
$message = <<<MESSAGE
The database at
{$databaseUri}
could not be found. You can find the correct name at
https://console.firebase.google.com/project/_/database
If you haven't configured the SDK otherwise and if you don't use multiple
Realtime Databases, the name you will find will most likely be
{$suggestedDatabaseUri}
The reason for this is that during the lifetime of the Firebase Admin SDK,
Firebase has changed the name of the default database. Previously, the default
database had the same identifier as the project. Since approximately September
2020, Realtime Databases in newly created projects have the '-default-rtdb'
suffix.
For instructions on how to set the name of the used Realtime Database, please
see https://firebase-php.readthedocs.io/en/latest/#quick-start
MESSAGE;
return new self($message);
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
final class PermissionDenied extends RuntimeException implements DatabaseException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
final class PreconditionFailed extends RuntimeException implements DatabaseException
{
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Database\Reference;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
use Throwable;
use function trim;
final class ReferenceHasNotBeenSnapshotted extends RuntimeException implements DatabaseException
{
private readonly Reference $reference;
public function __construct(Reference $query, string $message = '', int $code = 0, ?Throwable $previous = null)
{
$message = trim($message);
if ($message === '') {
$message = "The reference {$query->getPath()} has not been snapshotted.";
}
parent::__construct($message, $code, $previous);
$this->reference = $query;
}
public function getReference(): Reference
{
return $this->reference;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Database\Reference;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
use Throwable;
use function trim;
final class TransactionFailed extends RuntimeException implements DatabaseException
{
private readonly Reference $reference;
public function __construct(Reference $query, string $message = '', int $code = 0, ?Throwable $previous = null)
{
if (trim($message) === '') {
$queryPath = $query->getPath();
$message = "The transaction on {$queryPath} failed";
if ($previous instanceof PreconditionFailed) {
$message = "The reference {$queryPath} has changed remotely since the transaction has been started.";
} elseif ($previous !== null) {
$message = "The transaction on {$query->getPath()} failed: {$previous->getMessage()}";
}
}
parent::__construct($message, $code, $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);
}
public function getReference(): Reference
{
return $this->reference;
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Database;
use Kreait\Firebase\Database\Query;
use Kreait\Firebase\Exception\DatabaseException;
use Kreait\Firebase\Exception\RuntimeException;
use Throwable;
final class UnsupportedQuery extends RuntimeException implements DatabaseException
{
public function __construct(
private readonly Query $query,
string $message = '',
int $code = 0,
?Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public function getQuery(): Query
{
return $this->query;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use Fig\Http\Message\StatusCodeInterface as StatusCode;
use GuzzleHttp\Exception\RequestException;
use Kreait\Firebase\Exception\Database\ApiConnectionFailed;
use Kreait\Firebase\Exception\Database\DatabaseError;
use Kreait\Firebase\Exception\Database\DatabaseNotFound;
use Kreait\Firebase\Exception\Database\PermissionDenied;
use Kreait\Firebase\Exception\Database\PreconditionFailed;
use Kreait\Firebase\Http\ErrorResponseParser;
use Psr\Http\Client\NetworkExceptionInterface;
use Throwable;
/**
* @internal
*/
class DatabaseApiExceptionConverter
{
public function __construct(private readonly ErrorResponseParser $responseParser)
{
}
public function convertException(Throwable $exception): DatabaseException
{
if ($exception instanceof RequestException) {
return $this->convertGuzzleRequestException($exception);
}
if ($exception instanceof NetworkExceptionInterface) {
return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception);
}
return new DatabaseError($exception->getMessage(), $exception->getCode(), $exception);
}
private function convertGuzzleRequestException(RequestException $e): DatabaseException
{
$message = $e->getMessage();
$code = $e->getCode();
$response = $e->getResponse();
if ($response !== null) {
$message = $this->responseParser->getErrorReasonFromResponse($response);
$code = $response->getStatusCode();
}
return match ($code) {
StatusCode::STATUS_UNAUTHORIZED, StatusCode::STATUS_FORBIDDEN => new PermissionDenied($message, $code, $e),
StatusCode::STATUS_PRECONDITION_FAILED => new PreconditionFailed($message, $code, $e),
StatusCode::STATUS_NOT_FOUND => DatabaseNotFound::fromUri($e->getRequest()->getUri()),
default => new DatabaseError($message, $code, $e),
};
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
interface DatabaseException extends FirebaseException
{
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use Throwable;
interface FirebaseException extends Throwable
{
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
/**
* @internal
*/
trait HasErrors
{
/**
* @var array<mixed>
*/
protected array $errors = [];
/**
* @return array<mixed>
*/
public function errors(): array
{
return $this->errors;
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements FirebaseException
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
class LogicException extends \LogicException implements FirebaseException
{
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class ApiConnectionFailed extends RuntimeException implements MessagingException
{
use HasErrors;
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class AuthenticationError extends RuntimeException implements MessagingException
{
use HasErrors;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
return $new;
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\InvalidArgumentException;
use Kreait\Firebase\Exception\MessagingException;
final class InvalidArgument extends InvalidArgumentException implements MessagingException
{
use HasErrors;
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class InvalidMessage extends RuntimeException implements MessagingException
{
use HasErrors;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
return $new;
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class MessagingError extends RuntimeException implements MessagingException
{
use HasErrors;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
return $new;
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class NotFound extends RuntimeException implements MessagingException
{
use HasErrors;
/**
* @var non-empty-string|null
*/
private ?string $token = null;
/**
* @param non-empty-string $token
* @param array<mixed> $errors
*/
public static function becauseTokenNotFound(string $token, array $errors = []): self
{
$message = <<<MESSAGE
The message could not be delivered to the device identified by '{$token}'.
Although the token is syntactically correct, it is not known to the Firebase
project you are using. This could have the following reasons:
- The token has been unregistered from the project. This can happen when a user
has logged out from the application on the given client, or if they have
uninstalled or re-installed the application.
- The token has been registered to a different Firebase project than the project
you are using to send the message. A common reason for this is when you work
with different application environments and are sending a message from one
environment to a device in another environment.
MESSAGE;
$notFound = new self($message);
$notFound->errors = $errors;
$notFound->token = $token;
return $notFound;
}
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
return $new;
}
public function token(): ?string
{
return $this->token;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use DateTimeImmutable;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class QuotaExceeded extends RuntimeException implements MessagingException
{
use HasErrors;
private ?DateTimeImmutable $retryAfter = null;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
$new->retryAfter = $this->retryAfter;
return $new;
}
public function withRetryAfter(DateTimeImmutable $retryAfter): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $this->errors;
$new->retryAfter = $retryAfter;
return $new;
}
public function retryAfter(): ?DateTimeImmutable
{
return $this->retryAfter;
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class ServerError extends RuntimeException implements MessagingException
{
use HasErrors;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
return $new;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\Messaging;
use DateTimeImmutable;
use Kreait\Firebase\Exception\HasErrors;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Exception\RuntimeException;
final class ServerUnavailable extends RuntimeException implements MessagingException
{
use HasErrors;
private ?DateTimeImmutable $retryAfter = null;
/**
* @internal
*
* @param array<mixed> $errors
*/
public function withErrors(array $errors): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $errors;
$new->retryAfter = $this->retryAfter;
return $new;
}
public function withRetryAfter(DateTimeImmutable $retryAfter): self
{
$new = new self($this->getMessage(), $this->getCode(), $this->getPrevious());
$new->errors = $this->errors;
$new->retryAfter = $retryAfter;
return $new;
}
public function retryAfter(): ?DateTimeImmutable
{
return $this->retryAfter;
}
}

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use Beste\Clock\SystemClock;
use DateInterval;
use DateTimeImmutable;
use Fig\Http\Message\StatusCodeInterface as StatusCode;
use GuzzleHttp\Exception\RequestException;
use Kreait\Firebase\Exception\Messaging\ApiConnectionFailed;
use Kreait\Firebase\Exception\Messaging\AuthenticationError;
use Kreait\Firebase\Exception\Messaging\InvalidMessage;
use Kreait\Firebase\Exception\Messaging\MessagingError;
use Kreait\Firebase\Exception\Messaging\NotFound;
use Kreait\Firebase\Exception\Messaging\QuotaExceeded;
use Kreait\Firebase\Exception\Messaging\ServerError;
use Kreait\Firebase\Exception\Messaging\ServerUnavailable;
use Kreait\Firebase\Http\ErrorResponseParser;
use Psr\Clock\ClockInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
use function is_numeric;
/**
* @internal
*/
class MessagingApiExceptionConverter
{
private readonly ErrorResponseParser $responseParser;
private readonly ClockInterface $clock;
public function __construct(?ClockInterface $clock = null)
{
$this->responseParser = new ErrorResponseParser();
$this->clock = $clock ?? SystemClock::create();
}
public function convertException(Throwable $exception): MessagingException
{
if ($exception instanceof RequestException) {
return $this->convertGuzzleRequestException($exception);
}
if ($exception instanceof NetworkExceptionInterface) {
return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception);
}
return new MessagingError($exception->getMessage(), $exception->getCode(), $exception);
}
public function convertResponse(ResponseInterface $response, ?Throwable $previous = null): MessagingException
{
$code = $response->getStatusCode();
if ($code < StatusCode::STATUS_BAD_REQUEST) {
throw new InvalidArgumentException('Cannot convert a non-failed response to an exception');
}
$errors = $this->responseParser->getErrorsFromResponse($response);
$message = $this->responseParser->getErrorReasonFromResponse($response);
switch ($code) {
case StatusCode::STATUS_BAD_REQUEST:
$convertedError = new InvalidMessage($message, previous: $previous);
break;
case StatusCode::STATUS_UNAUTHORIZED:
case StatusCode::STATUS_FORBIDDEN:
$convertedError = new AuthenticationError($message, previous: $previous);
break;
case StatusCode::STATUS_NOT_FOUND:
$convertedError = new NotFound($message, previous: $previous);
break;
case StatusCode::STATUS_TOO_MANY_REQUESTS:
$convertedError = new QuotaExceeded($message, previous: $previous);
$retryAfter = $this->getRetryAfter($response);
if ($retryAfter !== null) {
$convertedError = $convertedError->withRetryAfter($retryAfter);
}
break;
case StatusCode::STATUS_INTERNAL_SERVER_ERROR:
$convertedError = new ServerError($message, previous: $previous);
break;
case StatusCode::STATUS_BAD_GATEWAY:
$contentType = mb_strtolower($response->getHeaderLine('Content-Type'));
$retryAfter = $this->getRetryAfter($response);
if (!str_contains($contentType, 'json')) {
// Adding 30 seconds as a fallback retry after because the HTML Response suggests it
// See https://github.com/kreait/firebase-php/issues/988
$retryAfter ??= ($this->clock->now()->add(new DateInterval('PT30S')));
$message = 'The server encountered a temporary error and could not complete your request.';
}
$convertedError = new ServerUnavailable($message, previous: $previous);
if ($retryAfter !== null) {
$convertedError = $convertedError->withRetryAfter($retryAfter);
}
break;
case StatusCode::STATUS_SERVICE_UNAVAILABLE:
$convertedError = new ServerUnavailable($message, previous: $previous);
$retryAfter = $this->getRetryAfter($response);
if ($retryAfter !== null) {
$convertedError = $convertedError->withRetryAfter($retryAfter);
}
break;
default:
$convertedError = new MessagingError($message, $code, $previous);
break;
}
return $convertedError->withErrors($errors);
}
private function convertGuzzleRequestException(RequestException $e): MessagingException
{
$response = $e->getResponse();
if ($response !== null) {
return $this->convertResponse($response, $e);
}
return new MessagingError($e->getMessage(), $e->getCode(), $e);
}
private function getRetryAfter(ResponseInterface $response): ?DateTimeImmutable
{
$retryAfter = $response->getHeaderLine('Retry-After');
if ($retryAfter === '') {
return null;
}
if (is_numeric($retryAfter)) {
return $this->clock->now()->modify("+{$retryAfter} seconds");
}
try {
return new DateTimeImmutable($retryAfter);
} catch (Throwable) {
// We can't afford to throw exceptions in an exception handler :)
// Here, if the Retry-After header doesn't have a numeric value
// or a date that can be handled by DateTimeImmutable, we just
// throw it away, sorry not sorry ¯\_(ツ)_/¯
return null;
}
}
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
interface MessagingException extends FirebaseException
{
/**
* @return array<mixed>
*/
public function errors(): array;
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
class OutOfRangeException extends \OutOfRangeException implements FirebaseException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class ApiConnectionFailed extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class OperationAborted extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class PermissionDenied extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class RemoteConfigError extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class ValidationFailed extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
final class VersionMismatch extends RuntimeException implements RemoteConfigException
{
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception\RemoteConfig;
use Kreait\Firebase\Exception\RemoteConfigException;
use Kreait\Firebase\Exception\RuntimeException;
use Kreait\Firebase\RemoteConfig\VersionNumber;
final class VersionNotFound extends RuntimeException implements RemoteConfigException
{
public static function withVersionNumber(VersionNumber $versionNumber): self
{
return new self('Version #'.$versionNumber.' could not be found.');
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Kreait\Firebase\Exception\RemoteConfig\ApiConnectionFailed;
use Kreait\Firebase\Exception\RemoteConfig\OperationAborted;
use Kreait\Firebase\Exception\RemoteConfig\PermissionDenied;
use Kreait\Firebase\Exception\RemoteConfig\RemoteConfigError;
use Kreait\Firebase\Exception\RemoteConfig\ValidationFailed;
use Kreait\Firebase\Exception\RemoteConfig\VersionMismatch;
use Kreait\Firebase\Http\ErrorResponseParser;
use Throwable;
use function mb_stripos;
/**
* @internal
*/
class RemoteConfigApiExceptionConverter
{
public function __construct(private readonly ErrorResponseParser $responseParser)
{
}
public function convertException(Throwable $exception): RemoteConfigException
{
if ($exception instanceof RequestException) {
return $this->convertGuzzleRequestException($exception);
}
if ($exception instanceof ConnectException) {
return new ApiConnectionFailed('Unable to connect to the API: '.$exception->getMessage(), $exception->getCode(), $exception);
}
return new RemoteConfigError($exception->getMessage(), $exception->getCode(), $exception);
}
private function convertGuzzleRequestException(RequestException $e): RemoteConfigException
{
$message = $e->getMessage();
$code = $e->getCode();
$response = $e->getResponse();
if ($response !== null) {
$message = $this->responseParser->getErrorReasonFromResponse($response);
$code = $response->getStatusCode();
}
if (mb_stripos($message, 'permission_denied') !== false) {
return new PermissionDenied($message, $code, $e);
}
if (mb_stripos($message, 'aborted') !== false) {
return new OperationAborted($message, $code, $e);
}
if (mb_stripos($message, 'version_mismatch') !== false) {
return new VersionMismatch($message, $code, $e);
}
if (mb_stripos($message, 'validation_error') !== false) {
return new ValidationFailed($message, $code, $e);
}
return new RemoteConfigError($message, $code, $e);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
interface RemoteConfigException extends FirebaseException
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Kreait\Firebase\Exception;
class RuntimeException extends \RuntimeException implements FirebaseException
{
}