Viewing file: MockResponse.php (11.44 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */
namespace Symfony\Component\HttpClient\Response;
use Symfony\Component\HttpClient\Chunk\ErrorChunk; use Symfony\Component\HttpClient\Chunk\FirstChunk; use Symfony\Component\HttpClient\Exception\InvalidArgumentException; use Symfony\Component\HttpClient\Exception\TransportException; use Symfony\Component\HttpClient\Internal\ClientState; use Symfony\Contracts\HttpClient\ResponseInterface;
/** * A test-friendly response. * * @author Nicolas Grekas <p@tchwork.com> */ class MockResponse implements ResponseInterface, StreamableInterface { use CommonResponseTrait; use TransportResponseTrait { doDestruct as public __destruct; }
private string|iterable $body; private array $requestOptions = []; private string $requestUrl; private string $requestMethod;
private static ClientState $mainMulti; private static int $idSequence = 0;
/** * @param string|iterable<string|\Throwable> $body The response body as a string or an iterable of strings, * yielding an empty string simulates an idle timeout, * throwing or yielding an exception yields an ErrorChunk * * @see ResponseInterface::getInfo() for possible info, e.g. "response_headers" */ public function __construct(string|iterable $body = '', array $info = []) { $this->body = $body; $this->info = $info + ['http_code' => 200] + $this->info;
if (!isset($info['response_headers'])) { return; }
$responseHeaders = [];
foreach ($info['response_headers'] as $k => $v) { foreach ((array) $v as $v) { $responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v; } }
$this->info['response_headers'] = []; self::addResponseHeaders($responseHeaders, $this->info, $this->headers); }
/** * Returns the options used when doing the request. */ public function getRequestOptions(): array { return $this->requestOptions; }
/** * Returns the URL used when doing the request. */ public function getRequestUrl(): string { return $this->requestUrl; }
/** * Returns the method used when doing the request. */ public function getRequestMethod(): string { return $this->requestMethod; }
public function getInfo(string $type = null): mixed { return null !== $type ? $this->info[$type] ?? null : $this->info; }
public function cancel(): void { $this->info['canceled'] = true; $this->info['error'] = 'Response has been canceled.'; try { unset($this->body); } catch (TransportException $e) { // ignore errors when canceling } }
protected function close(): void { $this->inflate = null; $this->body = []; }
/** * @internal */ public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self { $response = new self([]); $response->requestOptions = $options; $response->id = ++self::$idSequence; $response->shouldBuffer = $options['buffer'] ?? true; $response->initializer = static function (self $response) { return \is_array($response->body[0] ?? null); };
$response->info['redirect_count'] = 0; $response->info['redirect_url'] = null; $response->info['start_time'] = microtime(true); $response->info['http_method'] = $method; $response->info['http_code'] = 0; $response->info['user_data'] = $options['user_data'] ?? null; $response->info['max_duration'] = $options['max_duration'] ?? null; $response->info['url'] = $url; $response->info['original_url'] = $url;
if ($mock instanceof self) { $mock->requestOptions = $response->requestOptions; $mock->requestMethod = $method; $mock->requestUrl = $url; }
self::writeRequest($response, $options, $mock); $response->body[] = [$options, $mock];
return $response; }
protected static function schedule(self $response, array &$runningResponses): void { if (!isset($response->id)) { throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.'); }
$multi = self::$mainMulti ??= new ClientState();
if (!isset($runningResponses[0])) { $runningResponses[0] = [$multi, []]; }
$runningResponses[0][1][$response->id] = $response; }
protected static function perform(ClientState $multi, array &$responses): void { foreach ($responses as $response) { $id = $response->id;
if (!isset($response->body)) { // Canceled response $response->body = []; } elseif ([] === $response->body) { // Error chunk $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null; } elseif (null === $chunk = array_shift($response->body)) { // Last chunk $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = array_shift($response->body); } elseif (\is_array($chunk)) { // First chunk try { $offset = 0; $chunk[1]->getStatusCode(); $chunk[1]->getHeaders(false); self::readResponse($response, $chunk[0], $chunk[1], $offset); $multi->handlesActivity[$id][] = new FirstChunk(); $buffer = $response->requestOptions['buffer'] ?? null;
if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) { $response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+'); } } catch (\Throwable $e) { $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = $e; } } elseif ($chunk instanceof \Throwable) { $multi->handlesActivity[$id][] = null; $multi->handlesActivity[$id][] = $chunk; } else { // Data or timeout chunk $multi->handlesActivity[$id][] = $chunk; } } }
protected static function select(ClientState $multi, float $timeout): int { return 42; }
/** * Simulates sending the request. */ private static function writeRequest(self $response, array $options, ResponseInterface $mock): void { $onProgress = $options['on_progress'] ?? static function () {}; $response->info += $mock->getInfo() ?: [];
// simulate "size_upload" if it is set if (isset($response->info['size_upload'])) { $response->info['size_upload'] = 0.0; }
// simulate "total_time" if it is not set if (!isset($response->info['total_time'])) { $response->info['total_time'] = microtime(true) - $response->info['start_time']; }
// "notify" DNS resolution $onProgress(0, 0, $response->info);
// consume the request body if (\is_resource($body = $options['body'] ?? '')) { $data = stream_get_contents($body); if (isset($response->info['size_upload'])) { $response->info['size_upload'] += \strlen($data); } } elseif ($body instanceof \Closure) { while ('' !== $data = $body(16372)) { if (!\is_string($data)) { throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data))); }
// "notify" upload progress if (isset($response->info['size_upload'])) { $response->info['size_upload'] += \strlen($data); }
$onProgress(0, 0, $response->info); } } }
/** * Simulates reading the response. */ private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset): void { $onProgress = $options['on_progress'] ?? static function () {};
// populate info related to headers $info = $mock->getInfo() ?: []; $response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200; $response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers); $dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0);
$response->info = [ 'start_time' => $response->info['start_time'], 'user_data' => $response->info['user_data'], 'max_duration' => $response->info['max_duration'], 'http_code' => $response->info['http_code'], ] + $info + $response->info;
if (null !== $response->info['error']) { throw new TransportException($response->info['error']); }
if (!isset($response->info['total_time'])) { $response->info['total_time'] = microtime(true) - $response->info['start_time']; }
// "notify" headers arrival $onProgress(0, $dlSize, $response->info);
// cast response body to activity list $body = $mock instanceof self ? $mock->body : $mock->getContent(false);
if (!\is_string($body)) { try { foreach ($body as $chunk) { if ($chunk instanceof \Throwable) { throw $chunk; }
if ('' === $chunk = (string) $chunk) { // simulate an idle timeout $response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url'])); } else { $response->body[] = $chunk; $offset += \strlen($chunk); // "notify" download progress $onProgress($offset, $dlSize, $response->info); } } } catch (\Throwable $e) { $response->body[] = $e; } } elseif ('' !== $body) { $response->body[] = $body; $offset = \strlen($body); }
if (!isset($response->info['total_time'])) { $response->info['total_time'] = microtime(true) - $response->info['start_time']; }
// "notify" completion $onProgress($offset, $dlSize, $response->info);
if ($dlSize && $offset !== $dlSize) { throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset)); } } }
|