Skip to content

Commit f6e0252

Browse files
[HttpClient] add AsyncDecoratorTrait to ease processing responses without breaking async
1 parent 2ed6a0d commit f6e0252

12 files changed

+868
-178
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 Symfony\Component\HttpClient\Response\AsyncResponse;
15+
use Symfony\Component\HttpClient\Response\ResponseStream;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
17+
use Symfony\Contracts\HttpClient\ResponseInterface;
18+
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
19+
20+
/**
21+
* Eases with processing responses while streaming them.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
trait AsyncDecoratorTrait
26+
{
27+
private $client;
28+
29+
public function __construct(HttpClientInterface $client = null)
30+
{
31+
$this->client = $client ?? HttpClient::create();
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*
37+
* @return AsyncResponse
38+
*/
39+
abstract public function request(string $method, string $url, array $options = []): ResponseInterface;
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function stream($responses, float $timeout = null): ResponseStreamInterface
45+
{
46+
if ($responses instanceof AsyncResponse) {
47+
$responses = [$responses];
48+
} elseif (!is_iterable($responses)) {
49+
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
50+
}
51+
52+
return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class));
53+
}
54+
}

src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,12 @@ public function getError(): ?string
110110
/**
111111
* @return bool Whether the wrapped error has been thrown or not
112112
*/
113-
public function didThrow(): bool
113+
public function didThrow(bool $didThrow = null): bool
114114
{
115+
if (null !== $didThrow && $this->didThrow !== $didThrow) {
116+
return !$this->didThrow = $didThrow;
117+
}
118+
115119
return $this->didThrow;
116120
}
117121

src/Symfony/Component/HttpClient/Response/AmpResponse.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*/
3535
final class AmpResponse implements ResponseInterface
3636
{
37+
use CommonResponseTrait;
3738
use ResponseTrait;
3839

3940
private $multi;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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\Response;
13+
14+
use Symfony\Component\HttpClient\Chunk\DataChunk;
15+
use Symfony\Component\HttpClient\Chunk\LastChunk;
16+
use Symfony\Contracts\HttpClient\ChunkInterface;
17+
use Symfony\Contracts\HttpClient\HttpClientInterface;
18+
use Symfony\Contracts\HttpClient\ResponseInterface;
19+
20+
/**
21+
* A DTO to work with AsyncResponse.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
final class AsyncContext
26+
{
27+
private $passthru;
28+
private $client;
29+
private $response;
30+
private $info = [];
31+
private $content;
32+
private $offset;
33+
34+
public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset)
35+
{
36+
$this->passthru = &$passthru;
37+
$this->client = $client;
38+
$this->response = &$response;
39+
$this->info = &$info;
40+
$this->content = $content;
41+
$this->offset = $offset;
42+
}
43+
44+
public function getStatusCode(): int
45+
{
46+
return $this->response->getInfo('http_code');
47+
}
48+
49+
public function getHeaders(): array
50+
{
51+
$headers = [];
52+
53+
foreach ($this->response->getInfo('response_headers') as $h) {
54+
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) {
55+
$headers = [];
56+
} elseif (2 === \count($m = explode(':', $h, 2))) {
57+
$headers[strtolower($m[0])][] = ltrim($m[1]);
58+
}
59+
}
60+
61+
return $headers;
62+
}
63+
64+
/**
65+
* @return resource|null The PHP stream resource where the content is buffered, if it is
66+
*/
67+
public function getContent()
68+
{
69+
return $this->content;
70+
}
71+
72+
public function createChunk(string $data): ChunkInterface
73+
{
74+
return new DataChunk($this->offset, $data);
75+
}
76+
77+
public function cancel(): ChunkInterface
78+
{
79+
$this->info['canceled'] = true;
80+
$this->info['error'] = 'Response has been canceled.';
81+
$this->response->cancel();
82+
83+
return new LastChunk();
84+
}
85+
86+
public function getInfo(string $type = null)
87+
{
88+
if (null !== $type) {
89+
return $this->info[$type] ?? $this->response->getInfo($type);
90+
}
91+
92+
return $this->info + $this->response->getInfo();
93+
}
94+
95+
public function setInfo(string $type, $value): self
96+
{
97+
if (null === $value) {
98+
unset($this->info[$type]);
99+
} else {
100+
$this->info[$type] = $value;
101+
}
102+
103+
return $this;
104+
}
105+
106+
public function getResponse(): ResponseInterface
107+
{
108+
return $this->response;
109+
}
110+
111+
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
112+
{
113+
$this->info['previous_info'][] = $this->response->getInfo();
114+
115+
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
116+
}
117+
118+
public function replaceResponse(ResponseInterface $response): ResponseInterface
119+
{
120+
$this->info['previous_info'][] = $this->response->getInfo();
121+
122+
return $this->response = $response;
123+
}
124+
125+
public function passthru(callable $passthru = null): self
126+
{
127+
$this->passthru = $passthru;
128+
129+
return $this;
130+
}
131+
}

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