Skip to content

Commit d9dedb4

Browse files
feature #40306 [HttpClient] Add HttpClientInterface::withOptions() (nicolas-grekas)
This PR was merged into the 5.3-dev branch. Discussion ---------- [HttpClient] Add `HttpClientInterface::withOptions()` | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - I've been thinking about this method for a few months already. We miss a way to configure an HTTP client in a generic way. This is useful when eg building an API client as this allows configuring default options once for a consumer, eg in the constructor. ```php $this->client = $client->withOptions(['base_uri' => 'https://...']); // [...] $response = $this->client->request('GET', '/relative-url'); ``` Commits ------- 439742f [HttpClient] Add `HttpClientInterface::withOptions()`
2 parents 60ce52f + 439742f commit d9dedb4

22 files changed

+161
-41
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@
189189
"url": "src/Symfony/Contracts",
190190
"options": {
191191
"versions": {
192-
"symfony/contracts": "2.3.x-dev"
192+
"symfony/contracts": "2.4.x-dev"
193193
}
194194
}
195195
}

src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,15 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
5151

5252
return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class));
5353
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function withOptions(array $options): self
59+
{
60+
$clone = clone $this;
61+
$clone->client = $this->client->withOptions($options);
62+
63+
return $clone;
64+
}
5465
}

src/Symfony/Component/HttpClient/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3
5+
---
6+
7+
* Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4
8+
49
5.2.0
510
-----
611

src/Symfony/Component/HttpClient/CurlHttpClient.php

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -341,30 +341,8 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa
341341

342342
public function reset()
343343
{
344-
if ($this->logger) {
345-
foreach ($this->multi->pushedResponses as $url => $response) {
346-
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
347-
}
348-
}
349-
350-
$this->multi->pushedResponses = [];
351-
$this->multi->dnsCache->evictions = $this->multi->dnsCache->evictions ?: $this->multi->dnsCache->removals;
352-
$this->multi->dnsCache->removals = $this->multi->dnsCache->hostnames = [];
353-
354-
if (\is_resource($this->multi->handle) || $this->multi->handle instanceof \CurlMultiHandle) {
355-
if (\defined('CURLMOPT_PUSHFUNCTION')) {
356-
curl_multi_setopt($this->multi->handle, \CURLMOPT_PUSHFUNCTION, null);
357-
}
358-
359-
$active = 0;
360-
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->multi->handle, $active));
361-
}
362-
363-
foreach ($this->multi->openHandles as [$ch]) {
364-
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
365-
curl_setopt($ch, \CURLOPT_VERBOSE, false);
366-
}
367-
}
344+
$this->multi->logger = $this->logger;
345+
$this->multi->reset();
368346
}
369347

370348
public function __sleep()
@@ -379,7 +357,7 @@ public function __wakeup()
379357

380358
public function __destruct()
381359
{
382-
$this->reset();
360+
$this->multi->logger = $this->logger;
383361
}
384362

385363
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int

src/Symfony/Component/HttpClient/EventSourceHttpClient.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
*/
2727
final class EventSourceHttpClient implements HttpClientInterface
2828
{
29-
use AsyncDecoratorTrait;
30-
use HttpClientTrait;
29+
use AsyncDecoratorTrait, HttpClientTrait {
30+
AsyncDecoratorTrait::withOptions insteadof HttpClientTrait;
31+
}
3132

3233
private $reconnectionTime;
3334

src/Symfony/Component/HttpClient/HttpClientTrait.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717
/**
1818
* Provides the common logic from writing HttpClientInterface implementations.
1919
*
20-
* All methods are static to prevent implementers from creating memory leaks via circular references.
20+
* All private methods are static to prevent implementers from creating memory leaks via circular references.
2121
*
2222
* @author Nicolas Grekas <p@tchwork.com>
2323
*/
2424
trait HttpClientTrait
2525
{
2626
private static $CHUNK_SIZE = 16372;
2727

28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function withOptions(array $options): self
32+
{
33+
$clone = clone $this;
34+
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions);
35+
36+
return $clone;
37+
}
38+
2839
/**
2940
* Validates and normalizes method, URL and options, and merges them with defaults.
3041
*

src/Symfony/Component/HttpClient/Internal/CurlClientState.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\HttpClient\Internal;
1313

14+
use Psr\Log\LoggerInterface;
15+
1416
/**
1517
* Internal representation of the cURL client's state.
1618
*
@@ -29,10 +31,55 @@ final class CurlClientState extends ClientState
2931
/** @var float[] */
3032
public $pauseExpiries = [];
3133
public $execCounter = \PHP_INT_MIN;
34+
/** @var LoggerInterface|null */
35+
public $logger;
3236

3337
public function __construct()
3438
{
3539
$this->handle = curl_multi_init();
3640
$this->dnsCache = new DnsCache();
3741
}
42+
43+
public function reset()
44+
{
45+
if ($this->logger) {
46+
foreach ($this->pushedResponses as $url => $response) {
47+
$this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
48+
}
49+
}
50+
51+
$this->pushedResponses = [];
52+
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
53+
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
54+
55+
if (\is_resource($this->handle) || $this->handle instanceof \CurlMultiHandle) {
56+
if (\defined('CURLMOPT_PUSHFUNCTION')) {
57+
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, null);
58+
}
59+
60+
$active = 0;
61+
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($this->handle, $active));
62+
}
63+
64+
foreach ($this->openHandles as [$ch]) {
65+
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
66+
curl_setopt($ch, \CURLOPT_VERBOSE, false);
67+
}
68+
}
69+
}
70+
71+
public function __sleep()
72+
{
73+
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
74+
}
75+
76+
public function __wakeup()
77+
{
78+
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
79+
}
80+
81+
public function __destruct()
82+
{
83+
$this->reset();
84+
}
3885
}

src/Symfony/Component/HttpClient/MockHttpClient.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ class MockHttpClient implements HttpClientInterface
2828
use HttpClientTrait;
2929

3030
private $responseFactory;
31-
private $baseUri;
3231
private $requestsCount = 0;
32+
private $defaultOptions = [];
3333

3434
/**
3535
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
@@ -47,15 +47,15 @@ public function __construct($responseFactory = null, string $baseUri = null)
4747
}
4848

4949
$this->responseFactory = $responseFactory;
50-
$this->baseUri = $baseUri;
50+
$this->defaultOptions['base_uri'] = $baseUri;
5151
}
5252

5353
/**
5454
* {@inheritdoc}
5555
*/
5656
public function request(string $method, string $url, array $options = []): ResponseInterface
5757
{
58-
[$url, $options] = $this->prepareRequest($method, $url, $options, ['base_uri' => $this->baseUri], true);
58+
[$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
5959
$url = implode('', $url);
6060

6161
if (null === $this->responseFactory) {
@@ -96,4 +96,15 @@ public function getRequestsCount(): int
9696
{
9797
return $this->requestsCount;
9898
}
99+
100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function withOptions(array $options): self
104+
{
105+
$clone = clone $this;
106+
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true);
107+
108+
return $clone;
109+
}
99110
}

src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void
110110
$this->client->setLogger($logger);
111111
}
112112
}
113+
114+
/**
115+
* {@inheritdoc}
116+
*/
117+
public function withOptions(array $options): self
118+
{
119+
$clone = clone $this;
120+
$clone->client = $this->client->withOptions($options);
121+
122+
return $clone;
123+
}
113124
}

src/Symfony/Component/HttpClient/ScopingHttpClient.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,15 @@ public function setLogger(LoggerInterface $logger): void
110110
$this->client->setLogger($logger);
111111
}
112112
}
113+
114+
/**
115+
* {@inheritdoc}
116+
*/
117+
public function withOptions(array $options): self
118+
{
119+
$clone = clone $this;
120+
$clone->client = $this->client->withOptions($options);
121+
122+
return $clone;
123+
}
113124
}

0 commit comments

Comments
 (0)
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