AppendStream.php 4.8 KB

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