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 a1b5933

Browse filesBrowse files
author
Anthony MARTIN
committed
[WIP][HttpClient] Add a ConditionalHttpClient
1 parent ac93c9e commit a1b5933
Copy full SHA for a1b5933

File tree

3 files changed

+252
-0
lines changed
Filter options

3 files changed

+252
-0
lines changed
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient;
13+
14+
use phpDocumentor\Reflection\Types\Boolean;
15+
use Symfony\Component\HttpClient\Exception\InvalidArgumentException;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
19+
20+
/**
21+
* Auto-configure the default options based on the requested URL.
22+
*
23+
* @author Anthony Martin <anthony.martin@sensiolabs.com>
24+
*
25+
* @experimental in 4.3
26+
*/
27+
class ConditionalHttpClient implements HttpClientInterface
28+
{
29+
use HttpClientTrait;
30+
31+
private $client;
32+
private $options;
33+
private $absoluteUrls;
34+
35+
/**
36+
* @param array[] $options the default options to use when the regexp provided as key matches the requested URL
37+
* @param bool $absoluteUrls indicates if at least one rtegexp url given in $options would be on absolute url, in this case we need to check the url give to request() with use of prepareRequest to parse tu url etc...
38+
*/
39+
public function __construct(HttpClientInterface $client, array $options, bool $absoluteUrls = false)
40+
{
41+
$this->client = $client;
42+
$this->options = $options;
43+
$this->absoluteUrls = $absoluteUrls;
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function request(string $method, string $url, array $options = []): ResponseInterface
50+
{
51+
foreach ($this->options as $regexp => $defaultOptions) {
52+
if ($this->absoluteUrls) {
53+
try {
54+
[$preparedUrl, $preparedOptions] = self::prepareRequest($method, $url, $options, $defaultOptions, true);
55+
$preparedUrl = implode('', $preparedUrl);
56+
} catch (InvalidArgumentException $e) {
57+
return $this->client->request($method, $url, $options);
58+
}
59+
} else {
60+
$preparedUrl = $url;
61+
$preparedOptions = self::mergeDefaultOptions($options, $this->options, true);
62+
}
63+
64+
if (preg_match($regexp, $preparedUrl)) {
65+
return $this->client->request($method, $preparedUrl, $preparedOptions);
66+
}
67+
}
68+
69+
return $this->client->request($method, $url, $options);
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function stream($responses, float $timeout = null): ResponseStreamInterface
76+
{
77+
return $this->client->stream($responses, $timeout);
78+
}
79+
}
+98Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpClient\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\ConditionalHttpClient;
16+
use Symfony\Component\HttpClient\Tests\Mocks\SimpleHttpClientMock;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
19+
class ConditionalHttpClientTest extends TestCase
20+
{
21+
public function getUrlsAndDefaultOptions()
22+
{
23+
$defaultOptions = [
24+
'#^/foo-bar$#' => [
25+
'base_uri' => 'http://example.com',
26+
'headers' => ['Content-Type' => 'application/json'],
27+
],
28+
'#^.*$#' => [
29+
'base_uri' => 'http://example.com',
30+
'headers' => ['Content-Type' => 'application/json'],
31+
],
32+
];
33+
34+
yield ['url' => '/foo-bar', 'default_options' => $defaultOptions];
35+
yield ['url' => '/bar-foo', 'default_options' => $defaultOptions];
36+
yield ['url' => '/', 'default_options' => $defaultOptions];
37+
}
38+
39+
/**
40+
* @dataProvider getUrlsAndDefaultOptions
41+
*/
42+
public function testMatchingUrls(string $url, array $options)
43+
{
44+
$client = new ConditionalHttpClient(new SimpleHttpClientMock($this->createMock(ResponseInterface::class), $options, []), $options);
45+
46+
$this->assertInstanceOf(ResponseInterface::class, $client->request('GET', $url));
47+
}
48+
49+
public function getUrlsAndDefaultOptionsAndRequestsOptions()
50+
{
51+
$defaultOptions = [
52+
'#^/foo-bar$#' => [
53+
'base_uri' => 'http://example.com',
54+
'headers' => [
55+
'X-App' => 'unit-test-foo-bar',
56+
],
57+
],
58+
'#^.*$#' => [
59+
'base_uri' => 'http://example.com',
60+
'headers' => ['Content-Type' => 'application/json'],
61+
],
62+
];
63+
64+
$requestOptionsByReg = [
65+
'#^/foo-bar$#' => ['json' => ['url' => 'http://example.com']],
66+
'#^.*$#' => ['headers' => ['X-App' => 'unit-test']],
67+
];
68+
69+
yield [
70+
'url' => '/foo-bar',
71+
'default_options' => $defaultOptions,
72+
'request_options_by_reg' => $requestOptionsByReg,
73+
'request_options' => ['json' => ['url' => 'http://example.com']],
74+
];
75+
yield [
76+
'url' => '/bar-foo',
77+
'default_options' => $defaultOptions,
78+
'request_options_by_reg' => $requestOptionsByReg,
79+
'request_options' => [],
80+
];
81+
yield [
82+
'url' => '/',
83+
'default_options' => $defaultOptions,
84+
'request_options_by_reg' => $requestOptionsByReg,
85+
'request_options' => ['headers' => ['X-App' => 'unit-test']],
86+
];
87+
}
88+
89+
/**
90+
* @dataProvider getUrlsAndDefaultOptionsAndRequestsOptions
91+
*/
92+
public function testMatchingUrlsAndOptions(string $url, array $defaultOptions, array $requestOptionsByReg, array $requestOptions)
93+
{
94+
$client = new ConditionalHttpClient(new SimpleHttpClientMock($this->createMock(ResponseInterface::class), $defaultOptions, $requestOptionsByReg), $defaultOptions);
95+
96+
$this->assertInstanceOf(ResponseInterface::class, $client->request('GET', $url, $requestOptions));
97+
}
98+
}
+75Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: anthonymartin
5+
* Date: 19/03/19
6+
* Time: 12:09.
7+
*/
8+
9+
namespace Symfony\Component\HttpClient\Tests\Mocks;
10+
11+
use Symfony\Component\HttpClient\Exception\TransportException;
12+
use Symfony\Component\HttpClient\HttpClientTrait;
13+
use Symfony\Component\HttpClient\Response\ResponseStream;
14+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
15+
use Symfony\Contracts\HttpClient\HttpClientInterface;
16+
use Symfony\Contracts\HttpClient\ResponseInterface;
17+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
18+
19+
/**
20+
* A class to mock a simple http client, to test ConditionalHttpClient class.
21+
*
22+
* @author Anthony Martin <anthony.martin@sensiolabs.com>
23+
*/
24+
class SimpleHttpClientMock implements HttpClientInterface
25+
{
26+
use HttpClientTrait;
27+
28+
private $response;
29+
private $defaultOptionsByRegexp;
30+
private $requestsOptionsByRegexp;
31+
32+
public function __construct(ResponseInterface $response, array $defaultOptionsByRegexp, array $requestsOptionsByRegexp)
33+
{
34+
$this->response = $response;
35+
$this->defaultOptionsByRegexp = $defaultOptionsByRegexp;
36+
$this->requestsOptionsByRegexp = $requestsOptionsByRegexp;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*
42+
* @throws TransportExceptionInterface When an unsupported option is passed
43+
*/
44+
public function request(string $method, string $url, array $options = []): ResponseInterface
45+
{
46+
foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) {
47+
if (preg_match($regexp, $url)) {
48+
[$preparedUrl, $preparedOptions] = self::prepareRequest($method, $url, $this->requestsOptionsByRegexp[$regexp] ?? [], $defaultOptions, true);
49+
50+
if (isset($preparedOptions['headers']['x-app']) && isset($options['headers']['x-app'])
51+
&& $preparedOptions['headers']['x-app'] === $options['headers']['x-app']) {
52+
return $this->response;
53+
}
54+
55+
throw new TransportException('options doesn\'t matches');
56+
}
57+
}
58+
59+
throw new TransportException('No url matches');
60+
}
61+
62+
/**
63+
* {@inheritdoc}
64+
*/
65+
public function stream($responses, float $timeout = null): ResponseStreamInterface
66+
{
67+
if ($responses instanceof ResponseInterface) {
68+
$responses = [$responses];
69+
} elseif (!\is_iterable($responses)) {
70+
throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of ResponseInterface objects, %s given.', __METHOD__, \is_object($responses) ? \get_class($responses) : \gettype($responses)));
71+
}
72+
73+
return new ResponseStream($responses);
74+
}
75+
}

0 commit comments

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