Skip to content

Commit 542a8d5

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

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

components/http_client.rst

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

0 commit comments

Comments
 (0)