@@ -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 happens 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 behaves 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+
9111013Symfony Framework Integration
9121014-----------------------------
9131015
0 commit comments