Skip to content

Commit

Permalink
Allow adding Guzzle Config options and middlewares
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromegamez committed Jun 3, 2023
1 parent bbd1d54 commit caffbf4
Show file tree
Hide file tree
Showing 7 changed files with 312 additions and 67 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased]

### Added

* It is now possible to add config options and middlewares to the Guzzle HTTP Client performing the HTTP Requests
to the Firebase APIs through the `HttpClientOptions` class.
([Documentation](https://firebase-php.readthedocs.io/en/latest/setup.html#http-client-options))

## [7.2.1] - 2023-04-04

### Fixed
Expand Down
49 changes: 48 additions & 1 deletion docs/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ want to access directly and suppress warnings triggered by the Google Auth Compo
HTTP Client Options
*******************

You can configure the behavior of the HTTP Client performing the API requests by passing an
You can configure the behavior of the Guzzle HTTP Client performing the API requests by passing an
instance of `Kreait\Firebase\Http\HttpClientOptions` to the factory before creating a
service.

Expand All @@ -201,6 +201,53 @@ service.
// Newly created services will now use the new HTTP options
$realtimeDatabase = $factory->createDatabase();
Setting Guzzle Config Options
=============================

In addition to the explicit settings above, you can fully customize the configuration of the Guzzle HTTP Client:

.. code-block:: php
use Kreait\Firebase\Http\HttpClientOptions;
$options = HttpClientOptions::default()
->withGuzzleConfigOption('single', 'value')
->withGuzzleConfigOptions([
'first' => 'value',
'second' => 'value,
]);
.. note::
You can find all Guzzle Config Options at
`Guzzle: Request Options <https://docs.guzzlephp.org/en/stable/request-options.html>`_

Adding Guzzle Middlewares
=========================

You can also add middlewares to the Guzzle HTTP Client:

.. code-block:: php
use Kreait\Firebase\Http\HttpClientOptions;
$options = HttpClientOptions::default();
# Adding a single middleware
$options = $options->withGuzzleMiddleware($myMiddleware, 'my_middleware'); // The name can be omitted
# Adding multiple middlewares
$options = $options->withGuzzleMiddlewares([
# Just providing the middleware
$myMiddleware,
# Alternative notation:
['middleware' => $myMiddleware]
# Providing a named middleware
['middleware' => $myMiddleware, 'name' => 'my_middleware],
]);
.. note::
You can find more information about Guzzle Middlewares at
`Guzzle: Handlers and Middleware <https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html>`_

*******
Logging
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ parameters:
-
message: '#JWT::decode.+expects.+CachedKeySet given#'
path: src/Firebase/AppCheck/AppCheckTokenVerifier.php
-
message: '#Call to method.+assertIsCallable.+will always evaluate to true#'
path: tests

checkUninitializedProperties: true
reportUnmatchedIgnoredErrors: false
Expand Down
28 changes: 8 additions & 20 deletions src/Firebase/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Psr7\HttpFactory;
use GuzzleHttp\Psr7\Utils as GuzzleUtils;
use GuzzleHttp\RequestOptions;
use Kreait\Firebase\Auth\ApiClient;
use Kreait\Firebase\Auth\CustomTokenViaGoogleCredentials;
use Kreait\Firebase\Auth\SignIn\GuzzleHandler;
Expand Down Expand Up @@ -501,22 +500,9 @@ public function getDebugInfo(): array
public function createApiClient(?array $config = null, ?array $middlewares = null): Client
{
$config ??= [];
$middlewares ??= [];

if ($proxy = $this->httpClientOptions->proxy()) {
$config[RequestOptions::PROXY] = $proxy;
}

if ($connectTimeout = $this->httpClientOptions->connectTimeout()) {
$config[RequestOptions::CONNECT_TIMEOUT] = $connectTimeout;
}

if ($readTimeout = $this->httpClientOptions->readTimeout()) {
$config[RequestOptions::READ_TIMEOUT] = $readTimeout;
}

if ($totalTimeout = $this->httpClientOptions->timeout()) {
$config[RequestOptions::TIMEOUT] = $totalTimeout;
}
$config = array_merge($this->httpClientOptions->guzzleConfig(), $config);

$handler = HandlerStack::create();

Expand All @@ -528,10 +514,12 @@ public function createApiClient(?array $config = null, ?array $middlewares = nul
$handler->push($this->httpDebugLogMiddleware, 'http_debug_logs');
}

if ($middlewares !== null) {
foreach ($middlewares as $middleware) {
$handler->push($middleware);
}
foreach ($this->httpClientOptions->guzzleMiddlewares() as $middleware) {
$handler->push($middleware['middleware'], $middleware['name']);
}

foreach ($middlewares as $middleware) {
$handler->push($middleware);
}

$credentials = $this->getGoogleAuthTokenCredentials();
Expand Down
150 changes: 104 additions & 46 deletions src/Firebase/Http/HttpClientOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,132 +4,190 @@

namespace Kreait\Firebase\Http;

use GuzzleHttp\RequestOptions;
use Kreait\Firebase\Exception\InvalidArgumentException;

use function is_callable;

final class HttpClientOptions
{
/**
* The amount of seconds to wait while connecting to a server.
*/
private ?float $connectTimeout = 15;

/**
* The amount of seconds to wait while reading a streamed body.
*
* Defaults to the value of the default_socket_timeout PHP ini setting.
*/
private ?float $readTimeout = null;

/**
* The amount of seconds to wait for a full request (connect + transfer + read) to complete.
*/
private ?float $timeout = 30;

/**
* The proxy that all requests should be passed through.
* @param array<non-empty-string, mixed> $guzzleConfig
* @param list<array{middleware: callable, name: string}> $guzzleMiddlewares
*/
private ?string $proxy = null;

private function __construct()
{
private function __construct(
private array $guzzleConfig,
private array $guzzleMiddlewares,
) {
}

public static function default(): self
{
return new self();
return new self([], []);
}

/**
* The amount of seconds to wait while connecting to a server.
*
* Defaults to indefinitely.
* @see RequestOptions::CONNECT_TIMEOUT
*/
public function connectTimeout(): ?float
{
return $this->connectTimeout;
return $this->guzzleConfig[RequestOptions::CONNECT_TIMEOUT] ?? null;
}

/**
* @param float $value the amount of seconds to wait while connecting to a server
*
* @see RequestOptions::CONNECT_TIMEOUT
*/
public function withConnectTimeout(float $value): self
{
if ($value < 0) {
throw new InvalidArgumentException('The connect timeout cannot be smaller than zero.');
}

$options = clone $this;
$options->connectTimeout = $value;

return $options;
return $this->withGuzzleConfigOption(RequestOptions::CONNECT_TIMEOUT, $value);
}

/**
* The amount of seconds to wait while reading a streamed body.
*
* Defaults to the value of the default_socket_timeout PHP ini setting.
* @see RequestOptions::READ_TIMEOUT
*/
public function readTimeout(): ?float
{
return $this->readTimeout;
return $this->guzzleConfig[RequestOptions::READ_TIMEOUT] ?? null;
}

/**
* @param float $value the amount of seconds to wait while reading a streamed body
*
* @see RequestOptions::READ_TIMEOUT
*/
public function withReadTimeout(float $value): self
{
if ($value < 0) {
throw new InvalidArgumentException('The read timeout cannot be smaller than zero.');
}

$options = clone $this;
$options->readTimeout = $value;

return $options;
return $this->withGuzzleConfigOption(RequestOptions::READ_TIMEOUT, $value);
}

/**
* The amount of seconds to wait for a full request (connect + transfer + read) to complete.
*
* Defaults to indefinitely.
* @see RequestOptions::TIMEOUT
*/
public function timeout(): ?float
{
return $this->timeout;
return $this->guzzleConfig[RequestOptions::TIMEOUT] ?? null;
}

/**
* @param float $value the amount of seconds to wait while reading a streamed body
*
* @see RequestOptions::TIMEOUT
*/
public function withTimeout(float $value): self
{
if ($value < 0) {
throw new InvalidArgumentException('The total timeout cannot be smaller than zero.');
}

$options = clone $this;
$options->timeout = $value;

return $options;
return $this->withGuzzleConfigOption(RequestOptions::TIMEOUT, $value);
}

/**
* The proxy that all requests should be passed through.
*
* @see RequestOptions::PROXY
*/
public function proxy(): ?string
{
return $this->proxy;
return $this->guzzleConfig[RequestOptions::PROXY] ?? null;
}

/**
* @param string $value the proxy that all requests should be passed through
* @param non-empty-string $value the proxy that all requests should be passed through
*
* @see RequestOptions::PROXY
*/
public function withProxy(string $value): self
{
$options = clone $this;
$options->proxy = $value;
return $this->withGuzzleConfigOption(RequestOptions::PROXY, $value);
}

/**
* @param non-empty-string $name
*/
public function withGuzzleConfigOption(string $name, mixed $option): self
{
$config = $this->guzzleConfig;
$config[$name] = $option;

return new self($config, $this->guzzleMiddlewares);
}

/**
* @param array<non-empty-string, mixed> $options
*/
public function withGuzzleConfigOptions(array $options): self
{
$config = $this->guzzleConfig;

foreach ($options as $name => $option) {
$config[$name] = $option;
}

return new self($config, $this->guzzleMiddlewares);
}

/**
* @return array<non-empty-string, mixed>
*/
public function guzzleConfig(): array
{
return $this->guzzleConfig;
}

/**
* @return list<array{middleware: callable, name: string}>
*/
public function guzzleMiddlewares(): array
{
return $this->guzzleMiddlewares;
}

/**
* @param non-empty-string|null $name
*/
public function withGuzzleMiddleware(callable $middleware, ?string $name = null): self
{
$middlewares = $this->guzzleMiddlewares;
$middlewares[] = ['middleware' => $middleware, 'name' => $name ?? ''];

return new self($this->guzzleConfig, $middlewares);
}

/**
* @param list<array{
* middleware: callable,
* name: string
* }|callable> $middlewares
*/
public function withGuzzleMiddlewares(array $middlewares): self
{
$newMiddlewares = $this->guzzleMiddlewares;

foreach ($middlewares as $middleware) {
if (is_callable($middleware)) {
$middleware = ['middleware' => $middleware, 'name' => ''];
}

$newMiddlewares[] = $middleware;
}

return $options;
return new self($this->guzzleConfig, $newMiddlewares);
}
}

0 comments on commit caffbf4

Please sign in to comment.