Skip to content

Commit b703f5a

Browse files
committed
minor #13736 [HttpClient] add doc about extending and AsyncDecoratorTrait (nicolas-grekas)
This PR was merged into the 5.2 branch. Discussion ---------- [HttpClient] add doc about extending and AsyncDecoratorTrait About symfony/symfony#36779 and symfony/symfony#37136 Commits ------- f41ef12 [HttpClient] add doc about extending and AsyncDecoratorTrait
2 parents 9ea8d38 + f41ef12 commit b703f5a

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
@@ -1495,6 +1495,110 @@ This allows using them where native PHP streams are needed::
14951495
// later on if you need to, you can access the response from the stream
14961496
$response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();
14971497

1498+
Extensibility
1499+
-------------
1500+
1501+
In order to extend the behavior of a base HTTP client, decoration is the way to go::
1502+
1503+
class MyExtendedHttpClient implements HttpClientInterface
1504+
{
1505+
private $decoratedClient;
1506+
1507+
public function __construct(HttpClientInterface $decoratedClient = null)
1508+
{
1509+
$this->decoratedClient = $decoratedClient ?? HttpClient::create();
1510+
}
1511+
1512+
public function request(string $method, string $url, array $options = []): ResponseInterface
1513+
{
1514+
// do what you want here with $method, $url and/or $options
1515+
1516+
$response = $this->decoratedClient->request();
1517+
1518+
//!\ calling any method on $response here would break async, see below for a better way
1519+
1520+
return $response;
1521+
}
1522+
1523+
public function stream($responses, float $timeout = null): ResponseStreamInterface
1524+
{
1525+
return $this->decoratedClient->stream($responses, $timeout);
1526+
}
1527+
}
1528+
1529+
A decorator like this one is suited for use cases where processing the
1530+
requests' arguments is enough.
1531+
1532+
By decorating the ``on_progress`` option, one can
1533+
even implement basic monitoring of the response. But since calling responses'
1534+
methods forces synchronous operations, doing so in ``request()`` breaks async.
1535+
The solution then is to also decorate the response object itself.
1536+
:class:`Symfony\\Component\\HttpClient\\TraceableHttpClient` and
1537+
:class:`Symfony\\Component\\HttpClient\\Response\\TraceableResponse` are good
1538+
examples as a starting point.
1539+
1540+
.. versionadded:: 5.2
1541+
1542+
``AsyncDecoratorTrait`` was introduced in Symfony 5.2.
1543+
1544+
In order to help writing more advanced response processors, the component provides
1545+
an :class:`Symfony\\Component\\HttpClient\\AsyncDecoratorTrait`. This trait allows
1546+
processing the stream of chunks as they come back from the network::
1547+
1548+
class MyExtendedHttpClient implements HttpClientInterface
1549+
{
1550+
use AsyncDecoratorTrait;
1551+
1552+
public function request(string $method, string $url, array $options = []): ResponseInterface
1553+
{
1554+
// do what you want here with $method, $url and/or $options
1555+
1556+
$passthru = function (ChunkInterface $chunk, AsyncContext $context) {
1557+
1558+
// do what you want with chunks, e.g. split them
1559+
// in smaller chunks, group them, skip some, etc.
1560+
1561+
yield $chunk;
1562+
};
1563+
1564+
return new AsyncResponse($this->client, $method, $url, $options, $passthru);
1565+
}
1566+
}
1567+
1568+
Because the trait already implements a constructor and the ``stream()`` method,
1569+
you don't need to add them. The ``request()`` method should still be defined;
1570+
it shall return an
1571+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse`.
1572+
1573+
The custom processing of chunks should happen in ``$passthru``: this generator
1574+
is where you need to write your logic. It will be called for each chunk yielded by
1575+
the underlying client. A ``$passthru`` that does nothing would just ``yield $chunk;``.
1576+
Of course, you could also yield a modified chunk, split the chunk into many
1577+
ones by yielding several times, or even skip a chunk altogether by issuing a
1578+
``return;`` instead of yielding.
1579+
1580+
In order to control the stream, the chunk passthru receives an
1581+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncContext` as second
1582+
argument. This context object has methods to read the current state of the
1583+
response. It also allows altering the response stream with methods to create new
1584+
chunks of content, pause the stream, cancel the stream, change the info of the
1585+
response, replace the current request by another one or change the chunk passthru
1586+
itself.
1587+
1588+
Checking the test cases implemented in
1589+
:class:`Symfony\\Component\\HttpClient\\Response\\Tests\\AsyncDecoratorTraitTest`
1590+
might be a good start to get various working examples for a better understanding.
1591+
Here are the use cases that it simulates:
1592+
1593+
* retry a failed request;
1594+
* send a preflight request, e.g. for authentication needs;
1595+
* issue subrequests and include their content in the main response's body.
1596+
1597+
The logic in :class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse` has
1598+
many safety checks that will throw a ``LogicException`` if the chunk passthru
1599+
doesn't behave correctly; e.g. if a chunk is yielded after an ``isLast()`` one,
1600+
or if a content chunk is yielded before an ``isFirst()`` one, etc.
1601+
14981602
Testing HTTP Clients and Responses
14991603
----------------------------------
15001604

0 commit comments

Comments
 (0)