Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d9dedb4

Browse filesBrowse 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
Copy full SHA for d9dedb4

22 files changed

+161
-41
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/AsyncDecoratorTrait.php
+11Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CHANGELOG.md
+5Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/CurlHttpClient.php
+3-25Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/EventSourceHttpClient.php
+3-2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/HttpClientTrait.php
+12-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/Internal/CurlClientState.php
+47Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/MockHttpClient.php
+14-3Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.php
+11Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/ScopingHttpClient.php
+11Lines 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/TraceableHttpClient.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/TraceableHttpClient.php
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,15 @@ public function setLogger(LoggerInterface $logger): void
105105
$this->client->setLogger($logger);
106106
}
107107
}
108+
109+
/**
110+
* {@inheritdoc}
111+
*/
112+
public function withOptions(array $options): self
113+
{
114+
$clone = clone $this;
115+
$clone->client = $this->client->withOptions($options);
116+
117+
return $clone;
118+
}
108119
}

‎src/Symfony/Component/HttpClient/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpClient/composer.json
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"php-http/async-client-implementation": "*",
1919
"php-http/client-implementation": "*",
2020
"psr/http-client-implementation": "1.0",
21-
"symfony/http-client-implementation": "2.2"
21+
"symfony/http-client-implementation": "2.4"
2222
},
2323
"require": {
2424
"php": ">=7.2.5",
2525
"psr/log": "^1.0",
26-
"symfony/http-client-contracts": "^2.2",
26+
"symfony/http-client-contracts": "^2.4",
2727
"symfony/polyfill-php73": "^1.11",
2828
"symfony/polyfill-php80": "^1.15",
2929
"symfony/service-contracts": "^1.0|^2"

‎src/Symfony/Contracts/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/CHANGELOG.md
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.4
5+
---
6+
7+
* Add `HttpClientInterface::withOptions()`
8+
49
2.3.0
510
-----
611

‎src/Symfony/Contracts/Cache/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/Cache/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-main": "2.3-dev"
31+
"dev-main": "2.4-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

‎src/Symfony/Contracts/Deprecation/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/Deprecation/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"minimum-stability": "dev",
2626
"extra": {
2727
"branch-alias": {
28-
"dev-main": "2.3-dev"
28+
"dev-main": "2.4-dev"
2929
},
3030
"thanks": {
3131
"name": "symfony/contracts",

‎src/Symfony/Contracts/EventDispatcher/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/EventDispatcher/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-main": "2.3-dev"
31+
"dev-main": "2.4-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

‎src/Symfony/Contracts/HttpClient/HttpClientInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/HttpClientInterface.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*
2020
* @see HttpClientTestCase for a reference test suite
2121
*
22+
* @method static withOptions(array $options) Returns a new instance of the client with new default options
23+
*
2224
* @author Nicolas Grekas <p@tchwork.com>
2325
*/
2426
interface HttpClientInterface

‎src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/Test/HttpClientTestCase.php
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,4 +1038,20 @@ public function testMaxDuration()
10381038

10391039
$this->assertLessThan(10, $duration);
10401040
}
1041+
1042+
public function testWithOptions()
1043+
{
1044+
$client = $this->getHttpClient(__FUNCTION__);
1045+
if (!method_exists($client, 'withOptions')) {
1046+
$this->markTestSkipped(sprintf('Not implementing "%s::withOptions()" is deprecated.', get_debug_type($client)));
1047+
}
1048+
1049+
$client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']);
1050+
1051+
$this->assertNotSame($client, $client2);
1052+
$this->assertSame(\get_class($client), \get_class($client2));
1053+
1054+
$response = $client2->request('GET', '/');
1055+
$this->assertSame(200, $response->getStatusCode());
1056+
}
10411057
}

‎src/Symfony/Contracts/HttpClient/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/HttpClient/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"minimum-stability": "dev",
2828
"extra": {
2929
"branch-alias": {
30-
"dev-main": "2.3-dev"
30+
"dev-main": "2.4-dev"
3131
},
3232
"thanks": {
3333
"name": "symfony/contracts",

‎src/Symfony/Contracts/Service/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/Service/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"minimum-stability": "dev",
2929
"extra": {
3030
"branch-alias": {
31-
"dev-main": "2.3-dev"
31+
"dev-main": "2.4-dev"
3232
},
3333
"thanks": {
3434
"name": "symfony/contracts",

‎src/Symfony/Contracts/Translation/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/Translation/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"minimum-stability": "dev",
2828
"extra": {
2929
"branch-alias": {
30-
"dev-main": "2.3-dev"
30+
"dev-main": "2.4-dev"
3131
},
3232
"thanks": {
3333
"name": "symfony/contracts",

‎src/Symfony/Contracts/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Contracts/composer.json
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"minimum-stability": "dev",
5050
"extra": {
5151
"branch-alias": {
52-
"dev-main": "2.3-dev"
52+
"dev-main": "2.4-dev"
5353
}
5454
}
5555
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.