Skip to content

Commit f41ef12

Browse files
[HttpClient] add doc about extending and AsyncDecoratorTrait
1 parent fcf2eba commit f41ef12

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

http_client.rst

+104
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,110 @@ This allows using them where native PHP streams are needed::
13221322
// later on if you need to, you can access the response from the stream
13231323
$response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();
13241324

1325+
Extensibility
1326+
-------------
1327+
1328+
In order to extend the behavior of a base HTTP client, decoration is the way to go::
1329+
1330+
class MyExtendedHttpClient implements HttpClientInterface
1331+
{
1332+
private $decoratedClient;
1333+
1334+
public function __construct(HttpClientInterface $decoratedClient = null)
1335+
{
1336+
$this->decoratedClient = $decoratedClient ?? HttpClient::create();
1337+
}
1338+
1339+
public function request(string $method, string $url, array $options = []): ResponseInterface
1340+
{
1341+
// do what you want here with $method, $url and/or $options
1342+
1343+
$response = $this->decoratedClient->request();
1344+
1345+
//!\ calling any method on $response here would break async, see below for a better way
1346+
1347+
return $response;
1348+
}
1349+
1350+
public function stream($responses, float $timeout = null): ResponseStreamInterface
1351+
{
1352+
return $this->decoratedClient->stream($responses, $timeout);
1353+
}
1354+
}
1355+
1356+
A decorator like this one is suited for use cases where processing the
1357+
requests' arguments is enough.
1358+
1359+
By decorating the ``on_progress`` option, one can
1360+
even implement basic monitoring of the response. But since calling responses'
1361+
methods forces synchronous operations, doing so in ``request()`` breaks async.
1362+
The solution then is to also decorate the response object itself.
1363+
:class:`Symfony\\Component\\HttpClient\\TraceableHttpClient` and
1364+
:class:`Symfony\\Component\\HttpClient\\Response\\TraceableResponse` are good
1365+
examples as a starting point.
1366+
1367+
.. versionadded:: 5.2
1368+
1369+
``AsyncDecoratorTrait`` was introduced in Symfony 5.2.
1370+
1371+
In order to help writing more advanced response processors, the component provides
1372+
an :class:`Symfony\\Component\\HttpClient\\AsyncDecoratorTrait`. This trait allows
1373+
processing the stream of chunks as they come back from the network::
1374+
1375+
class MyExtendedHttpClient implements HttpClientInterface
1376+
{
1377+
use AsyncDecoratorTrait;
1378+
1379+
public function request(string $method, string $url, array $options = []): ResponseInterface
1380+
{
1381+
// do what you want here with $method, $url and/or $options
1382+
1383+
$passthru = function (ChunkInterface $chunk, AsyncContext $context) {
1384+
1385+
// do what you want with chunks, e.g. split them
1386+
// in smaller chunks, group them, skip some, etc.
1387+
1388+
yield $chunk;
1389+
};
1390+
1391+
return new AsyncResponse($this->client, $method, $url, $options, $passthru);
1392+
}
1393+
}
1394+
1395+
Because the trait already implements a constructor and the ``stream()`` method,
1396+
you don't need to add them. The ``request()`` method should still be defined;
1397+
it shall return an
1398+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse`.
1399+
1400+
The custom processing of chunks should happen in ``$passthru``: this generator
1401+
is where you need to write your logic. It will be called for each chunk yielded by
1402+
the underlying client. A ``$passthru`` that does nothing would just ``yield $chunk;``.
1403+
Of course, you could also yield a modified chunk, split the chunk into many
1404+
ones by yielding several times, or even skip a chunk altogether by issuing a
1405+
``return;`` instead of yielding.
1406+
1407+
In order to control the stream, the chunk passthru receives an
1408+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncContext` as second
1409+
argument. This context object has methods to read the current state of the
1410+
response. It also allows altering the response stream with methods to create new
1411+
chunks of content, pause the stream, cancel the stream, change the info of the
1412+
response, replace the current request by another one or change the chunk passthru
1413+
itself.
1414+
1415+
Checking the test cases implemented in
1416+
:class:`Symfony\\Component\\HttpClient\\Response\\Tests\\AsyncDecoratorTraitTest`
1417+
might be a good start to get various working examples for a better understanding.
1418+
Here are the use cases that it simulates:
1419+
1420+
* retry a failed request;
1421+
* send a preflight request, e.g. for authentication needs;
1422+
* issue subrequests and include their content in the main response's body.
1423+
1424+
The logic in :class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse` has
1425+
many safety checks that will throw a ``LogicException`` if the chunk passthru
1426+
doesn't behave correctly; e.g. if a chunk is yielded after an ``isLast()`` one,
1427+
or if a content chunk is yielded before an ``isFirst()`` one, etc.
1428+
13251429
Testing HTTP Clients and Responses
13261430
----------------------------------
13271431

0 commit comments

Comments
 (0)