CommonResponseTrait.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpClient\Response;
  11. use Symfony\Component\HttpClient\Exception\ClientException;
  12. use Symfony\Component\HttpClient\Exception\JsonException;
  13. use Symfony\Component\HttpClient\Exception\RedirectionException;
  14. use Symfony\Component\HttpClient\Exception\ServerException;
  15. use Symfony\Component\HttpClient\Exception\TransportException;
  16. /**
  17. * Implements common logic for response classes.
  18. *
  19. * @author Nicolas Grekas <p@tchwork.com>
  20. *
  21. * @internal
  22. */
  23. trait CommonResponseTrait
  24. {
  25. /**
  26. * @var callable|null A callback that tells whether we're waiting for response headers
  27. */
  28. private $initializer;
  29. private $shouldBuffer;
  30. private $content;
  31. private int $offset = 0;
  32. private ?array $jsonData = null;
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function getContent(bool $throw = true): string
  37. {
  38. if ($this->initializer) {
  39. self::initialize($this);
  40. }
  41. if ($throw) {
  42. $this->checkStatusCode();
  43. }
  44. if (null === $this->content) {
  45. $content = null;
  46. foreach (self::stream([$this]) as $chunk) {
  47. if (!$chunk->isLast()) {
  48. $content .= $chunk->getContent();
  49. }
  50. }
  51. if (null !== $content) {
  52. return $content;
  53. }
  54. if (null === $this->content) {
  55. throw new TransportException('Cannot get the content of the response twice: buffering is disabled.');
  56. }
  57. } else {
  58. foreach (self::stream([$this]) as $chunk) {
  59. // Chunks are buffered in $this->content already
  60. }
  61. }
  62. rewind($this->content);
  63. return stream_get_contents($this->content);
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. public function toArray(bool $throw = true): array
  69. {
  70. if ('' === $content = $this->getContent($throw)) {
  71. throw new JsonException('Response body is empty.');
  72. }
  73. if (null !== $this->jsonData) {
  74. return $this->jsonData;
  75. }
  76. try {
  77. $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | \JSON_THROW_ON_ERROR);
  78. } catch (\JsonException $e) {
  79. throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode());
  80. }
  81. if (!\is_array($content)) {
  82. throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url')));
  83. }
  84. if (null !== $this->content) {
  85. // Option "buffer" is true
  86. return $this->jsonData = $content;
  87. }
  88. return $content;
  89. }
  90. /**
  91. * {@inheritdoc}
  92. */
  93. public function toStream(bool $throw = true)
  94. {
  95. if ($throw) {
  96. // Ensure headers arrived
  97. $this->getHeaders($throw);
  98. }
  99. $stream = StreamWrapper::createResource($this);
  100. stream_get_meta_data($stream)['wrapper_data']
  101. ->bindHandles($this->handle, $this->content);
  102. return $stream;
  103. }
  104. public function __sleep(): array
  105. {
  106. throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  107. }
  108. public function __wakeup()
  109. {
  110. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  111. }
  112. /**
  113. * Closes the response and all its network handles.
  114. */
  115. abstract protected function close(): void;
  116. private static function initialize(self $response): void
  117. {
  118. if (null !== $response->getInfo('error')) {
  119. throw new TransportException($response->getInfo('error'));
  120. }
  121. try {
  122. if (($response->initializer)($response, -0.0)) {
  123. foreach (self::stream([$response], -0.0) as $chunk) {
  124. if ($chunk->isFirst()) {
  125. break;
  126. }
  127. }
  128. }
  129. } catch (\Throwable $e) {
  130. // Persist timeouts thrown during initialization
  131. $response->info['error'] = $e->getMessage();
  132. $response->close();
  133. throw $e;
  134. }
  135. $response->initializer = null;
  136. }
  137. private function checkStatusCode()
  138. {
  139. $code = $this->getInfo('http_code');
  140. if (500 <= $code) {
  141. throw new ServerException($this);
  142. }
  143. if (400 <= $code) {
  144. throw new ClientException($this);
  145. }
  146. if (300 <= $code) {
  147. throw new RedirectionException($this);
  148. }
  149. }
  150. }