酒店预订平台
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

247 行
5.6 KiB

  1. <?php
  2. namespace GuzzleHttp\Psr7;
  3. use Psr\Http\Message\StreamInterface;
  4. /**
  5. * Reads from multiple streams, one after the other.
  6. *
  7. * This is a read-only stream decorator.
  8. *
  9. * @final
  10. */
  11. class AppendStream implements StreamInterface
  12. {
  13. /** @var StreamInterface[] Streams being decorated */
  14. private $streams = [];
  15. private $seekable = true;
  16. private $current = 0;
  17. private $pos = 0;
  18. /**
  19. * @param StreamInterface[] $streams Streams to decorate. Each stream must
  20. * be readable.
  21. */
  22. public function __construct(array $streams = [])
  23. {
  24. foreach ($streams as $stream) {
  25. $this->addStream($stream);
  26. }
  27. }
  28. public function __toString()
  29. {
  30. try {
  31. $this->rewind();
  32. return $this->getContents();
  33. } catch (\Exception $e) {
  34. return '';
  35. }
  36. }
  37. /**
  38. * Add a stream to the AppendStream
  39. *
  40. * @param StreamInterface $stream Stream to append. Must be readable.
  41. *
  42. * @throws \InvalidArgumentException if the stream is not readable
  43. */
  44. public function addStream(StreamInterface $stream)
  45. {
  46. if (!$stream->isReadable()) {
  47. throw new \InvalidArgumentException('Each stream must be readable');
  48. }
  49. // The stream is only seekable if all streams are seekable
  50. if (!$stream->isSeekable()) {
  51. $this->seekable = false;
  52. }
  53. $this->streams[] = $stream;
  54. }
  55. public function getContents()
  56. {
  57. return Utils::copyToString($this);
  58. }
  59. /**
  60. * Closes each attached stream.
  61. *
  62. * {@inheritdoc}
  63. */
  64. public function close()
  65. {
  66. $this->pos = $this->current = 0;
  67. $this->seekable = true;
  68. foreach ($this->streams as $stream) {
  69. $stream->close();
  70. }
  71. $this->streams = [];
  72. }
  73. /**
  74. * Detaches each attached stream.
  75. *
  76. * Returns null as it's not clear which underlying stream resource to return.
  77. *
  78. * {@inheritdoc}
  79. */
  80. public function detach()
  81. {
  82. $this->pos = $this->current = 0;
  83. $this->seekable = true;
  84. foreach ($this->streams as $stream) {
  85. $stream->detach();
  86. }
  87. $this->streams = [];
  88. return null;
  89. }
  90. public function tell()
  91. {
  92. return $this->pos;
  93. }
  94. /**
  95. * Tries to calculate the size by adding the size of each stream.
  96. *
  97. * If any of the streams do not return a valid number, then the size of the
  98. * append stream cannot be determined and null is returned.
  99. *
  100. * {@inheritdoc}
  101. */
  102. public function getSize()
  103. {
  104. $size = 0;
  105. foreach ($this->streams as $stream) {
  106. $s = $stream->getSize();
  107. if ($s === null) {
  108. return null;
  109. }
  110. $size += $s;
  111. }
  112. return $size;
  113. }
  114. public function eof()
  115. {
  116. return !$this->streams ||
  117. ($this->current >= count($this->streams) - 1 &&
  118. $this->streams[$this->current]->eof());
  119. }
  120. public function rewind()
  121. {
  122. $this->seek(0);
  123. }
  124. /**
  125. * Attempts to seek to the given position. Only supports SEEK_SET.
  126. *
  127. * {@inheritdoc}
  128. */
  129. public function seek($offset, $whence = SEEK_SET)
  130. {
  131. if (!$this->seekable) {
  132. throw new \RuntimeException('This AppendStream is not seekable');
  133. } elseif ($whence !== SEEK_SET) {
  134. throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
  135. }
  136. $this->pos = $this->current = 0;
  137. // Rewind each stream
  138. foreach ($this->streams as $i => $stream) {
  139. try {
  140. $stream->rewind();
  141. } catch (\Exception $e) {
  142. throw new \RuntimeException('Unable to seek stream '
  143. . $i . ' of the AppendStream', 0, $e);
  144. }
  145. }
  146. // Seek to the actual position by reading from each stream
  147. while ($this->pos < $offset && !$this->eof()) {
  148. $result = $this->read(min(8096, $offset - $this->pos));
  149. if ($result === '') {
  150. break;
  151. }
  152. }
  153. }
  154. /**
  155. * Reads from all of the appended streams until the length is met or EOF.
  156. *
  157. * {@inheritdoc}
  158. */
  159. public function read($length)
  160. {
  161. $buffer = '';
  162. $total = count($this->streams) - 1;
  163. $remaining = $length;
  164. $progressToNext = false;
  165. while ($remaining > 0) {
  166. // Progress to the next stream if needed.
  167. if ($progressToNext || $this->streams[$this->current]->eof()) {
  168. $progressToNext = false;
  169. if ($this->current === $total) {
  170. break;
  171. }
  172. $this->current++;
  173. }
  174. $result = $this->streams[$this->current]->read($remaining);
  175. // Using a loose comparison here to match on '', false, and null
  176. if ($result == null) {
  177. $progressToNext = true;
  178. continue;
  179. }
  180. $buffer .= $result;
  181. $remaining = $length - strlen($buffer);
  182. }
  183. $this->pos += strlen($buffer);
  184. return $buffer;
  185. }
  186. public function isReadable()
  187. {
  188. return true;
  189. }
  190. public function isWritable()
  191. {
  192. return false;
  193. }
  194. public function isSeekable()
  195. {
  196. return $this->seekable;
  197. }
  198. public function write($string)
  199. {
  200. throw new \RuntimeException('Cannot write to an AppendStream');
  201. }
  202. public function getMetadata($key = null)
  203. {
  204. return $key ? null : [];
  205. }
  206. }