CommonResponseTrait.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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. /**
  32. * @var int
  33. */
  34. private $offset = 0;
  35. /**
  36. * @var mixed[]|null
  37. */
  38. private $jsonData;
  39. /**
  40. * @param bool $throw
  41. */
  42. public function getContent($throw = true)
  43. {
  44. if ($this->initializer) {
  45. self::initialize($this);
  46. }
  47. if ($throw) {
  48. $this->checkStatusCode();
  49. }
  50. if (null === $this->content) {
  51. $content = null;
  52. foreach (self::stream([$this]) as $chunk) {
  53. if (!$chunk->isLast()) {
  54. $content .= $chunk->getContent();
  55. }
  56. }
  57. if (null !== $content) {
  58. return $content;
  59. }
  60. if (null === $this->content) {
  61. throw new TransportException('Cannot get the content of the response twice: buffering is disabled.');
  62. }
  63. } else {
  64. foreach (self::stream([$this]) as $chunk) {
  65. // Chunks are buffered in $this->content already
  66. }
  67. }
  68. rewind($this->content);
  69. return stream_get_contents($this->content);
  70. }
  71. /**
  72. * @param bool $throw
  73. */
  74. public function toArray($throw = true)
  75. {
  76. if ('' === $content = $this->getContent($throw)) {
  77. throw new JsonException('Response body is empty.');
  78. }
  79. if (null !== $this->jsonData) {
  80. return $this->jsonData;
  81. }
  82. try {
  83. $content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING);
  84. } catch (\JsonException $e) {
  85. throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode());
  86. }
  87. if (!\is_array($content)) {
  88. throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url')));
  89. }
  90. if (null !== $this->content) {
  91. // Option "buffer" is true
  92. return $this->jsonData = $content;
  93. }
  94. return $content;
  95. }
  96. /**
  97. * @return resource
  98. * @param bool $throw
  99. */
  100. public function toStream($throw = true)
  101. {
  102. if ($throw) {
  103. // Ensure headers arrived
  104. $this->getHeaders($throw);
  105. }
  106. $stream = StreamWrapper::createResource($this);
  107. stream_get_meta_data($stream)['wrapper_data']
  108. ->bindHandles($this->handle, $this->content);
  109. return $stream;
  110. }
  111. public function __sleep()
  112. {
  113. throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  114. }
  115. public function __wakeup()
  116. {
  117. throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
  118. }
  119. /**
  120. * Closes the response and all its network handles.
  121. */
  122. abstract protected function close();
  123. /**
  124. * @param $this $response
  125. */
  126. private static function initialize($response)
  127. {
  128. if (null !== $response->getInfo('error')) {
  129. throw new TransportException($response->getInfo('error'));
  130. }
  131. try {
  132. if (($response->initializer)($response, -0.0)) {
  133. foreach (self::stream([$response], -0.0) as $chunk) {
  134. if ($chunk->isFirst()) {
  135. break;
  136. }
  137. }
  138. }
  139. } catch (\Throwable $e) {
  140. // Persist timeouts thrown during initialization
  141. $response->info['error'] = $e->getMessage();
  142. $response->close();
  143. throw $e;
  144. }
  145. $response->initializer = null;
  146. }
  147. private function checkStatusCode()
  148. {
  149. $code = $this->getInfo('http_code');
  150. if (500 <= $code) {
  151. throw new ServerException($this);
  152. }
  153. if (400 <= $code) {
  154. throw new ClientException($this);
  155. }
  156. if (300 <= $code) {
  157. throw new RedirectionException($this);
  158. }
  159. }
  160. }