From 439742ff336dfdd66fa7681c2e4e5037affc9508 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 25 Feb 2021 16:41:26 +0100 Subject: [PATCH] [HttpClient] Add `HttpClientInterface::withOptions()` --- composer.json | 2 +- .../HttpClient/AsyncDecoratorTrait.php | 11 +++++ src/Symfony/Component/HttpClient/CHANGELOG.md | 5 ++ .../Component/HttpClient/CurlHttpClient.php | 28 ++--------- .../HttpClient/EventSourceHttpClient.php | 5 +- .../Component/HttpClient/HttpClientTrait.php | 13 ++++- .../HttpClient/Internal/CurlClientState.php | 47 +++++++++++++++++++ .../Component/HttpClient/MockHttpClient.php | 17 +++++-- .../HttpClient/NoPrivateNetworkHttpClient.php | 11 +++++ .../HttpClient/ScopingHttpClient.php | 11 +++++ .../HttpClient/TraceableHttpClient.php | 11 +++++ .../Component/HttpClient/composer.json | 4 +- src/Symfony/Contracts/CHANGELOG.md | 5 ++ src/Symfony/Contracts/Cache/composer.json | 2 +- .../Contracts/Deprecation/composer.json | 2 +- .../Contracts/EventDispatcher/composer.json | 2 +- .../HttpClient/HttpClientInterface.php | 2 + .../HttpClient/Test/HttpClientTestCase.php | 16 +++++++ .../Contracts/HttpClient/composer.json | 2 +- src/Symfony/Contracts/Service/composer.json | 2 +- .../Contracts/Translation/composer.json | 2 +- src/Symfony/Contracts/composer.json | 2 +- 22 files changed, 161 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index 41e1911b6eca3..5295ee17ca760 100644 --- a/composer.json +++ b/composer.json @@ -188,7 +188,7 @@ "url": "src/Symfony/Contracts", "options": { "versions": { - "symfony/contracts": "2.3.x-dev" + "symfony/contracts": "2.4.x-dev" } } } diff --git a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php index c5d40a251d3d8..2e6267300d138 100644 --- a/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php +++ b/src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php @@ -51,4 +51,15 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class)); } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/CHANGELOG.md b/src/Symfony/Component/HttpClient/CHANGELOG.md index f25989e168396..3b97488c93fa1 100644 --- a/src/Symfony/Component/HttpClient/CHANGELOG.md +++ b/src/Symfony/Component/HttpClient/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4 + 5.2.0 ----- diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index 48ecf773d4cfd..3a75e675e25e8 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -341,30 +341,8 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa public function reset() { - if ($this->logger) { - foreach ($this->multi->pushedResponses as $url => $response) { - $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); - } - } - - $this->multi->pushedResponses = []; - $this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals; - $this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = []; - - if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) { - if (\defined('CURLMOPT_PUSHFUNCTION')) { - curl_multi_setopt($this->multi->handle, \CURLMOPT_PUSHFUNCTION, null); - } - - $active = 0; - while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active)); - } - - foreach ($this->multi->openHandles as [$ch]) { - if (\is_resource($ch) || $ch instanceof \CurlHandle) { - curl_setopt($ch, \CURLOPT_VERBOSE, false); - } - } + $this->multi->logger = $this->logger; + $this->multi->reset(); } public function __sleep() @@ -379,7 +357,7 @@ public function __wakeup() public function __destruct() { - $this->reset(); + $this->multi->logger = $this->logger; } private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int diff --git a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php index d6f27a7fae99f..81d9f4bfc8bf8 100644 --- a/src/Symfony/Component/HttpClient/EventSourceHttpClient.php +++ b/src/Symfony/Component/HttpClient/EventSourceHttpClient.php @@ -26,8 +26,9 @@ */ final class EventSourceHttpClient implements HttpClientInterface { - use AsyncDecoratorTrait; - use HttpClientTrait; + use AsyncDecoratorTrait, HttpClientTrait { + AsyncDecoratorTrait::withOptions insteadof HttpClientTrait; + } private $reconnectionTime; diff --git a/src/Symfony/Component/HttpClient/HttpClientTrait.php b/src/Symfony/Component/HttpClient/HttpClientTrait.php index 545b1aad283de..9d68f9a61f61d 100644 --- a/src/Symfony/Component/HttpClient/HttpClientTrait.php +++ b/src/Symfony/Component/HttpClient/HttpClientTrait.php @@ -17,7 +17,7 @@ /** * Provides the common logic from writing HttpClientInterface implementations. * - * All methods are static to prevent implementers from creating memory leaks via circular references. + * All private methods are static to prevent implementers from creating memory leaks via circular references. * * @author Nicolas Grekas */ @@ -25,6 +25,17 @@ trait HttpClientTrait { private static $CHUNK_SIZE = 16372; + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions); + + return $clone; + } + /** * Validates and normalizes method, URL and options, and merges them with defaults. * diff --git a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php index f381968d143c1..40ed0f1fc8c97 100644 --- a/src/Symfony/Component/HttpClient/Internal/CurlClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/CurlClientState.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpClient\Internal; +use Psr\Log\LoggerInterface; + /** * Internal representation of the cURL client's state. * @@ -29,10 +31,55 @@ final class CurlClientState extends ClientState /** @var float[] */ public $pauseExpiries = []; public $execCounter = \PHP_INT_MIN; + /** @var LoggerInterface|null */ + public $logger; public function __construct() { $this->handle = curl_multi_init(); $this->dnsCache = new DnsCache(); } + + public function reset() + { + if ($this->logger) { + foreach ($this->pushedResponses as $url => $response) { + $this->logger->debug(sprintf('Unused pushed response: "%s"', $url)); + } + } + + $this->pushedResponses = []; + $this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals; + $this->dnsCache->removals = $this->dnsCache->hostnames = []; + + if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) { + if (\defined('CURLMOPT_PUSHFUNCTION')) { + curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, null); + } + + $active = 0; + while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->handle, $active)); + } + + foreach ($this->openHandles as [$ch]) { + if (\is_resource($ch) || $ch instanceof \CurlHandle) { + curl_setopt($ch, \CURLOPT_VERBOSE, false); + } + } + } + + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + + public function __destruct() + { + $this->reset(); + } } diff --git a/src/Symfony/Component/HttpClient/MockHttpClient.php b/src/Symfony/Component/HttpClient/MockHttpClient.php index 08571f07c209b..a794faff6e75c 100644 --- a/src/Symfony/Component/HttpClient/MockHttpClient.php +++ b/src/Symfony/Component/HttpClient/MockHttpClient.php @@ -28,8 +28,8 @@ class MockHttpClient implements HttpClientInterface use HttpClientTrait; private $responseFactory; - private $baseUri; private $requestsCount = 0; + private $defaultOptions = []; /** * @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory @@ -47,7 +47,7 @@ public function __construct($responseFactory = null, string $baseUri = null) } $this->responseFactory = $responseFactory; - $this->baseUri = $baseUri; + $this->defaultOptions['base_uri'] = $baseUri; } /** @@ -55,7 +55,7 @@ public function __construct($responseFactory = null, string $baseUri = null) */ public function request(string $method, string $url, array $options = []): ResponseInterface { - [$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true); + [$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true); $url = implode('', $url); if (null === $this->responseFactory) { @@ -96,4 +96,15 @@ public function getRequestsCount(): int { return $this->requestsCount; } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php index d4c69cabcea95..b9db846992cf3 100644 --- a/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php +++ b/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php @@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/ScopingHttpClient.php b/src/Symfony/Component/HttpClient/ScopingHttpClient.php index 66dcccf0e93f2..2a6e70e15d7c3 100644 --- a/src/Symfony/Component/HttpClient/ScopingHttpClient.php +++ b/src/Symfony/Component/HttpClient/ScopingHttpClient.php @@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/TraceableHttpClient.php b/src/Symfony/Component/HttpClient/TraceableHttpClient.php index 34dc01ad2553f..bc842115900de 100644 --- a/src/Symfony/Component/HttpClient/TraceableHttpClient.php +++ b/src/Symfony/Component/HttpClient/TraceableHttpClient.php @@ -105,4 +105,15 @@ public function setLogger(LoggerInterface $logger): void $this->client->setLogger($logger); } } + + /** + * {@inheritdoc} + */ + public function withOptions(array $options): self + { + $clone = clone $this; + $clone->client = $this->client->withOptions($options); + + return $clone; + } } diff --git a/src/Symfony/Component/HttpClient/composer.json b/src/Symfony/Component/HttpClient/composer.json index 880798e83b032..be66a8f5c28e5 100644 --- a/src/Symfony/Component/HttpClient/composer.json +++ b/src/Symfony/Component/HttpClient/composer.json @@ -18,12 +18,12 @@ "php-http/async-client-implementation": "*", "php-http/client-implementation": "*", "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "2.2" + "symfony/http-client-implementation": "2.4" }, "require": { "php": ">=7.2.5", "psr/log": "^1.0", - "symfony/http-client-contracts": "^2.2", + "symfony/http-client-contracts": "^2.4", "symfony/polyfill-php73": "^1.11", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.0|^2" diff --git a/src/Symfony/Contracts/CHANGELOG.md b/src/Symfony/Contracts/CHANGELOG.md index b62029adb59d7..25efba02b929f 100644 --- a/src/Symfony/Contracts/CHANGELOG.md +++ b/src/Symfony/Contracts/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.4 +--- + + * Add `HttpClientInterface::withOptions()` + 2.3.0 ----- diff --git a/src/Symfony/Contracts/Cache/composer.json b/src/Symfony/Contracts/Cache/composer.json index 2b83ae24bc52d..0f6989201a133 100644 --- a/src/Symfony/Contracts/Cache/composer.json +++ b/src/Symfony/Contracts/Cache/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Deprecation/composer.json b/src/Symfony/Contracts/Deprecation/composer.json index 88d3055927dea..3884889637a89 100644 --- a/src/Symfony/Contracts/Deprecation/composer.json +++ b/src/Symfony/Contracts/Deprecation/composer.json @@ -25,7 +25,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/EventDispatcher/composer.json b/src/Symfony/Contracts/EventDispatcher/composer.json index 8eaf0354fd2d7..9f6dc413a00ff 100644 --- a/src/Symfony/Contracts/EventDispatcher/composer.json +++ b/src/Symfony/Contracts/EventDispatcher/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php index 4388eb84ce48b..ea793ba3943a8 100644 --- a/src/Symfony/Contracts/HttpClient/HttpClientInterface.php +++ b/src/Symfony/Contracts/HttpClient/HttpClientInterface.php @@ -19,6 +19,8 @@ * * @see HttpClientTestCase for a reference test suite * + * @method static withOptions(array $options) Returns a new instance of the client with new default options + * * @author Nicolas Grekas */ interface HttpClientInterface diff --git a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php index 74997eaafcf92..38b845438dd30 100644 --- a/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php +++ b/src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php @@ -1038,4 +1038,20 @@ public function testMaxDuration() $this->assertLessThan(10, $duration); } + + public function testWithOptions() + { + $client = $this->getHttpClient(__FUNCTION__); + if (!method_exists($client, 'withOptions')) { + $this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client))); + } + + $client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']); + + $this->assertNotSame($client, $client2); + $this->assertSame(\get_class($client), \get_class($client2)); + + $response = $client2->request('GET', '/'); + $this->assertSame(200, $response->getStatusCode()); + } } diff --git a/src/Symfony/Contracts/HttpClient/composer.json b/src/Symfony/Contracts/HttpClient/composer.json index 9ab9eaf76cc7a..2633785448429 100644 --- a/src/Symfony/Contracts/HttpClient/composer.json +++ b/src/Symfony/Contracts/HttpClient/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Service/composer.json b/src/Symfony/Contracts/Service/composer.json index 524f6a6c6c1ec..ec22b07db6e4b 100644 --- a/src/Symfony/Contracts/Service/composer.json +++ b/src/Symfony/Contracts/Service/composer.json @@ -28,7 +28,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/Translation/composer.json b/src/Symfony/Contracts/Translation/composer.json index 907d28f0a878e..00e27f836a6a8 100644 --- a/src/Symfony/Contracts/Translation/composer.json +++ b/src/Symfony/Contracts/Translation/composer.json @@ -27,7 +27,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" }, "thanks": { "name": "symfony/contracts", diff --git a/src/Symfony/Contracts/composer.json b/src/Symfony/Contracts/composer.json index 595e744af584a..b424c33fc8edd 100644 --- a/src/Symfony/Contracts/composer.json +++ b/src/Symfony/Contracts/composer.json @@ -49,7 +49,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy