Skip to content

Commit cd298a8

Browse files
[HttpClient] add doc about extending and AsyncDecoratorTrait
1 parent 096b320 commit cd298a8

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

components/http_client.rst

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,110 @@ This allows using them where native PHP streams are needed::
908908
// later on if you need to, you can access the response from the stream
909909
$response = stream_get_meta_data($streamResource)['wrapper_data']->getResponse();
910910

911+
Extensibility
912+
-------------
913+
914+
In order to extend the behavior of a base HTTP client, decoration is the way to go::
915+
916+
class MyExtendedHttpClient implements HttpClientInterface
917+
{
918+
private $decoratedClient;
919+
920+
public function __construct(HttpClientInterface decoratedClient = null)
921+
{
922+
$this->decoratedClient = $decoratedClient ?? HttpClient::create();
923+
}
924+
925+
public function request(string $method, string $url, array $options = []): ResponseInterface
926+
{
927+
// do what you want here with $method, $url and/or $options
928+
929+
$response = $this->decoratedClient->request();
930+
931+
//!\ calling any method on $response here would break async
932+
933+
return $response;
934+
}
935+
936+
public function stream($responses, float $timeout = null): ResponseStreamInterface
937+
{
938+
return $this->decoratedClient->stream($responses, $timeout);
939+
}
940+
}
941+
942+
A decorator like this one is suited for use cases where processing the
943+
requests' arguments is enough.
944+
945+
By decorating the ``on_progress`` option, one can
946+
even implement basic monitoring of the response. But since calling responses'
947+
methods forces synchronous operations, doing so in ``request()`` breaks async.
948+
The solution then is to also decorate the response object itself.
949+
:class:`Symfony\\Component\\HttpClient\\TraceableHttpClient` and
950+
:class:`Symfony\\Component\\HttpClient\\Response\\TraceableHResponse` are good
951+
examples as a starting point.
952+
953+
.. versionadded:: 5.2
954+
955+
``AsyncDecoratorTrait`` was introduced in Symfony 5.2.
956+
957+
In order to help writing more advanced response processors, the component provides
958+
an :class:`Symfony\\Component\\HttpClient\\AsyncDecoratorTrait`. This trait allows
959+
processing the stream of chunks as they come back from the network::
960+
961+
class MyExtendedHttpClient implements HttpClientInterface
962+
{
963+
use AsyncDecoratorTrait;
964+
965+
public function request(string $method, string $url, array $options = []): ResponseInterface
966+
{
967+
// do what you want here with $method, $url and/or $options
968+
969+
$passthru = function (ChunkInterface $chunk, AsyncContext $context) {
970+
971+
// do what you want with chunks, e.g. split them
972+
// in smaller chunks, group them, skip some, etc.
973+
974+
yield $chunk;
975+
};
976+
977+
return new AsyncResponse($method, $url, $options, $chunk, $passthru);
978+
}
979+
}
980+
981+
Because the trait already implements a constructor and the ``stream()`` method,
982+
you don't need to add them. The ``request()`` method should still be defined;
983+
it shall return an
984+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse`.
985+
986+
The custom processing of chunks should happen in ``$passthru``: this generator
987+
is where you need to write your logic. It will be called for each chunk yielded by
988+
the underlying client. A ``$passthru`` that does nothing would just ``yield $chunk;``.
989+
Of course, you could also yield a modified chunk, split the chunk into many
990+
ones by yielding several times, or even skip a chunk altogether by issuing a
991+
``return;`` instead of yielding.
992+
993+
In order to control the stream, the chunk passthru receives an
994+
:class:`Symfony\\Component\\HttpClient\\Response\\AsyncContext` as second
995+
argument. This context object has methods to read the current state of the
996+
response. It also allows altering the response stream with methods to create new
997+
chunks of content, pause the stream, cancel the stream, change the info of the
998+
response, replace the current request by another one or change the chunk passthru
999+
itself.
1000+
1001+
Checking the test cases implemented in
1002+
:class:`Symfony\\Component\\HttpClient\\Response\\Tests\\AsyncDecoratorTraitTest`
1003+
might be a good start to get various working examples for a better understanding.
1004+
Here are the use cases that it simulates:
1005+
1006+
* retry a failed request;
1007+
* send a preflight request, e.g. for authentication needs;
1008+
* issue subrequests and include their content in the main response's body.
1009+
1010+
The logic in :class:`Symfony\\Component\\HttpClient\\Response\\AsyncResponse` has
1011+
many safety checks that will throw a ``LogicException`` if the chunk passthru
1012+
doesn't behave correclty; e.g. if a chunk is yielded after an ``isLast()`` one,
1013+
or if a content chunk is yielded before an ``isFirst()`` one, etc.
1014+
9111015
Symfony Framework Integration
9121016
-----------------------------
9131017

0 commit comments

Comments
 (0)