PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / vendor / symfony / http-client / Response / AsyncResponse.php

AsyncResponse.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at vendor/symfony/http-client/Response/AsyncResponse.php

479 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\HttpClient\Response;
13
14 use Symfony\Component\HttpClient\Chunk\ErrorChunk;
15 use Symfony\Component\HttpClient\Chunk\FirstChunk;
16 use Symfony\Component\HttpClient\Chunk\LastChunk;
17 use Symfony\Component\HttpClient\Exception\TransportException;
18 use Symfony\Contracts\HttpClient\ChunkInterface;
19 use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
20 use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
21 use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
22 use Symfony\Contracts\HttpClient\HttpClientInterface;
23 use Symfony\Contracts\HttpClient\ResponseInterface;
24
25 /**
26 * Provides a single extension point to process a response's content stream.
27 *
28 * @author Nicolas Grekas <[email protected]>
29 */
30 final class AsyncResponse implements ResponseInterface, StreamableInterface
31 {
32 use CommonResponseTrait;
33
34 private const FIRST_CHUNK_YIELDED = 1;
35 private const LAST_CHUNK_YIELDED = 2;
36
37 private $client;
38 private $response;
39 private $info = ['canceled' => false];
40 private $passthru;
41 private $stream;
42 private $yieldedState;
43
44 /**
45 * @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru
46 */
47 public function __construct(HttpClientInterface $client, string $method, string $url, array $options, callable $passthru = null)
48 {
49 $this->client = $client;
50 $this->shouldBuffer = $options['buffer'] ?? true;
51
52 if (null !== $onProgress = $options['on_progress'] ?? null) {
53 $thisInfo = &$this->info;
54 $options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
55 $onProgress($dlNow, $dlSize, $thisInfo + $info);
56 };
57 }
58 $this->response = $client->request($method, $url, ['buffer' => false] + $options);
59 $this->passthru = $passthru;
60 $this->initializer = static function (self $response, float $timeout = null) {
61 if (null === $response->shouldBuffer) {
62 return false;
63 }
64
65 while (true) {
66 foreach (self::stream([$response], $timeout) as $chunk) {
67 if ($chunk->isTimeout() && $response->passthru) {
68 foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) {
69 if ($chunk->isFirst()) {
70 return false;
71 }
72 }
73
74 continue 2;
75 }
76
77 if ($chunk->isFirst()) {
78 return false;
79 }
80 }
81
82 return false;
83 }
84 };
85 if (\array_key_exists('user_data', $options)) {
86 $this->info['user_data'] = $options['user_data'];
87 }
88 if (\array_key_exists('max_duration', $options)) {
89 $this->info['max_duration'] = $options['max_duration'];
90 }
91 }
92
93 public function getStatusCode(): int
94 {
95 if ($this->initializer) {
96 self::initialize($this);
97 }
98
99 return $this->response->getStatusCode();
100 }
101
102 public function getHeaders(bool $throw = true): array
103 {
104 if ($this->initializer) {
105 self::initialize($this);
106 }
107
108 $headers = $this->response->getHeaders(false);
109
110 if ($throw) {
111 $this->checkStatusCode();
112 }
113
114 return $headers;
115 }
116
117 public function getInfo(string $type = null)
118 {
119 if (null !== $type) {
120 return $this->info[$type] ?? $this->response->getInfo($type);
121 }
122
123 return $this->info + $this->response->getInfo();
124 }
125
126 /**
127 * {@inheritdoc}
128 */
129 public function toStream(bool $throw = true)
130 {
131 if ($throw) {
132 // Ensure headers arrived
133 $this->getHeaders(true);
134 }
135
136 $handle = function () {
137 $stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response);
138
139 return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT);
140 };
141
142 $stream = StreamWrapper::createResource($this);
143 stream_get_meta_data($stream)['wrapper_data']
144 ->bindHandles($handle, $this->content);
145
146 return $stream;
147 }
148
149 /**
150 * {@inheritdoc}
151 */
152 public function cancel(): void
153 {
154 if ($this->info['canceled']) {
155 return;
156 }
157
158 $this->info['canceled'] = true;
159 $this->info['error'] = 'Response has been canceled.';
160 $this->close();
161 $client = $this->client;
162 $this->client = null;
163
164 if (!$this->passthru) {
165 return;
166 }
167
168 try {
169 foreach (self::passthru($client, $this, new LastChunk()) as $chunk) {
170 // no-op
171 }
172
173 $this->passthru = null;
174 } catch (ExceptionInterface $e) {
175 // ignore any errors when canceling
176 }
177 }
178
179 public function __destruct()
180 {
181 $httpException = null;
182
183 if ($this->initializer && null === $this->getInfo('error')) {
184 try {
185 self::initialize($this, -0.0);
186 $this->getHeaders(true);
187 } catch (HttpExceptionInterface $httpException) {
188 // no-op
189 }
190 }
191
192 if ($this->passthru && null === $this->getInfo('error')) {
193 $this->info['canceled'] = true;
194
195 try {
196 foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) {
197 // no-op
198 }
199 } catch (ExceptionInterface $e) {
200 // ignore any errors when destructing
201 }
202 }
203
204 if (null !== $httpException) {
205 throw $httpException;
206 }
207 }
208
209 /**
210 * @internal
211 */
212 public static function stream(iterable $responses, float $timeout = null, string $class = null): \Generator
213 {
214 while ($responses) {
215 $wrappedResponses = [];
216 $asyncMap = new \SplObjectStorage();
217 $client = null;
218
219 foreach ($responses as $r) {
220 if (!$r instanceof self) {
221 throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r)));
222 }
223
224 if (null !== $e = $r->info['error'] ?? null) {
225 yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e));
226 $chunk->didThrow() ?: $chunk->getContent();
227 continue;
228 }
229
230 if (null === $client) {
231 $client = $r->client;
232 } elseif ($r->client !== $client) {
233 throw new TransportException('Cannot stream AsyncResponse objects with many clients.');
234 }
235
236 $asyncMap[$r->response] = $r;
237 $wrappedResponses[] = $r->response;
238
239 if ($r->stream) {
240 yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap);
241
242 if (!isset($asyncMap[$response])) {
243 array_pop($wrappedResponses);
244 }
245
246 if ($r->response !== $response && !isset($asyncMap[$r->response])) {
247 $asyncMap[$r->response] = $r;
248 $wrappedResponses[] = $r->response;
249 }
250 }
251 }
252
253 if (!$client || !$wrappedResponses) {
254 return;
255 }
256
257 foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
258 $r = $asyncMap[$response];
259
260 if (null === $chunk->getError()) {
261 if ($chunk->isFirst()) {
262 // Ensure no exception is thrown on destruct for the wrapped response
263 $r->response->getStatusCode();
264 } elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) {
265 $r->content = fopen('php://memory', 'w+');
266 }
267 }
268
269 if (!$r->passthru) {
270 if (null !== $chunk->getError() || $chunk->isLast()) {
271 unset($asyncMap[$response]);
272 } elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
273 $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
274 $r->info['error'] = $chunk->getError();
275 $r->response->cancel();
276 }
277
278 yield $r => $chunk;
279 continue;
280 }
281
282 if (null !== $chunk->getError()) {
283 // no-op
284 } elseif ($chunk->isFirst()) {
285 $r->yieldedState = self::FIRST_CHUNK_YIELDED;
286 } elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) {
287 throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class));
288 }
289
290 foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) {
291 yield $r => $chunk;
292 }
293
294 if ($r->response !== $response && isset($asyncMap[$response])) {
295 break;
296 }
297 }
298
299 if (null === $chunk->getError() && $chunk->isLast()) {
300 $r->yieldedState = self::LAST_CHUNK_YIELDED;
301 }
302 if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) {
303 throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.');
304 }
305
306 $responses = [];
307 foreach ($asyncMap as $response) {
308 $r = $asyncMap[$response];
309
310 if (null !== $r->client) {
311 $responses[] = $asyncMap[$response];
312 }
313 }
314 }
315 }
316
317 /**
318 * @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
319 */
320 private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, \SplObjectStorage $asyncMap = null): \Generator
321 {
322 $r->stream = null;
323 $response = $r->response;
324 $context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset);
325 if (null === $stream = ($r->passthru)($chunk, $context)) {
326 if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) {
327 throw new \LogicException('A chunk passthru cannot swallow the last chunk.');
328 }
329
330 return;
331 }
332
333 if (!$stream instanceof \Iterator) {
334 throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream)));
335 }
336 $r->stream = $stream;
337
338 yield from self::passthruStream($response, $r, null, $asyncMap);
339 }
340
341 /**
342 * @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
343 */
344 private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator
345 {
346 while (true) {
347 try {
348 if (null !== $chunk && $r->stream) {
349 $r->stream->next();
350 }
351
352 if (!$r->stream || !$r->stream->valid() || !$r->stream) {
353 $r->stream = null;
354 break;
355 }
356 } catch (\Throwable $e) {
357 unset($asyncMap[$response]);
358 $r->stream = null;
359 $r->info['error'] = $e->getMessage();
360 $r->response->cancel();
361
362 yield $r => $chunk = new ErrorChunk($r->offset, $e);
363 $chunk->didThrow() ?: $chunk->getContent();
364 break;
365 }
366
367 $chunk = $r->stream->current();
368
369 if (!$chunk instanceof ChunkInterface) {
370 throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk)));
371 }
372
373 if (null !== $chunk->getError()) {
374 // no-op
375 } elseif ($chunk->isFirst()) {
376 $e = $r->openBuffer();
377
378 yield $r => $chunk;
379
380 if ($r->initializer && null === $r->getInfo('error')) {
381 // Ensure the HTTP status code is always checked
382 $r->getHeaders(true);
383 }
384
385 if (null === $e) {
386 continue;
387 }
388
389 $r->response->cancel();
390 $chunk = new ErrorChunk($r->offset, $e);
391 } elseif ('' !== $content = $chunk->getContent()) {
392 if (null !== $r->shouldBuffer) {
393 throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.');
394 }
395
396 if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) {
397 $chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
398 $r->info['error'] = $chunk->getError();
399 $r->response->cancel();
400 }
401 }
402
403 if (null !== $chunk->getError() || $chunk->isLast()) {
404 $stream = $r->stream;
405 $r->stream = null;
406 unset($asyncMap[$response]);
407 }
408
409 if (null === $chunk->getError()) {
410 $r->offset += \strlen($content);
411
412 yield $r => $chunk;
413
414 if (!$chunk->isLast()) {
415 continue;
416 }
417
418 $stream->next();
419
420 if ($stream->valid()) {
421 throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.');
422 }
423
424 $r->passthru = null;
425 } else {
426 if ($chunk instanceof ErrorChunk) {
427 $chunk->didThrow(false);
428 } else {
429 try {
430 $chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError());
431 } catch (TransportExceptionInterface $e) {
432 $chunk = new ErrorChunk($chunk->getOffset(), $e);
433 }
434 }
435
436 yield $r => $chunk;
437 $chunk->didThrow() ?: $chunk->getContent();
438 }
439
440 break;
441 }
442 }
443
444 private function openBuffer(): ?\Throwable
445 {
446 if (null === $shouldBuffer = $this->shouldBuffer) {
447 throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.');
448 }
449
450 $e = $this->shouldBuffer = null;
451
452 if ($shouldBuffer instanceof \Closure) {
453 try {
454 $shouldBuffer = $shouldBuffer($this->getHeaders(false));
455
456 if (null !== $e = $this->response->getInfo('error')) {
457 throw new TransportException($e);
458 }
459 } catch (\Throwable $e) {
460 $this->info['error'] = $e->getMessage();
461 $this->response->cancel();
462 }
463 }
464
465 if (true === $shouldBuffer) {
466 $this->content = fopen('php://temp', 'w+');
467 } elseif (\is_resource($shouldBuffer)) {
468 $this->content = $shouldBuffer;
469 }
470
471 return $e;
472 }
473
474 private function close(): void
475 {
476 $this->response->cancel();
477 }
478 }
479