@@ -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\\ TraceableResponse ` 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($this->client, $method, $url, $options, $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 correctly; 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+
9111015Symfony Framework Integration
9121016-----------------------------
9131017
0 commit comments