123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- <?php
- namespace GuzzleHttp\Psr7;
- use Psr\Http\Message\StreamInterface;
- /**
- * Reads from multiple streams, one after the other.
- *
- * This is a read-only stream decorator.
- */
- class AppendStream implements StreamInterface
- {
- /** @var StreamInterface[] Streams being decorated */
- private $streams = [];
- private $seekable = true;
- private $current = 0;
- private $pos = 0;
- /**
- * @param StreamInterface[] $streams Streams to decorate. Each stream must
- * be readable.
- */
- public function __construct(array $streams = [])
- {
- foreach ($streams as $stream) {
- $this->addStream($stream);
- }
- }
- public function __toString()
- {
- try {
- $this->rewind();
- return $this->getContents();
- } catch (\Exception $e) {
- return '';
- }
- }
- /**
- * Add a stream to the AppendStream
- *
- * @param StreamInterface $stream Stream to append. Must be readable.
- *
- * @throws \InvalidArgumentException if the stream is not readable
- */
- public function addStream(StreamInterface $stream)
- {
- if (!$stream->isReadable()) {
- throw new \InvalidArgumentException('Each stream must be readable');
- }
- // The stream is only seekable if all streams are seekable
- if (!$stream->isSeekable()) {
- $this->seekable = false;
- }
- $this->streams[] = $stream;
- }
- public function getContents()
- {
- return copy_to_string($this);
- }
- /**
- * Closes each attached stream.
- *
- * {@inheritdoc}
- */
- public function close()
- {
- $this->pos = $this->current = 0;
- $this->seekable = true;
- foreach ($this->streams as $stream) {
- $stream->close();
- }
- $this->streams = [];
- }
- /**
- * Detaches each attached stream.
- *
- * Returns null as it's not clear which underlying stream resource to return.
- *
- * {@inheritdoc}
- */
- public function detach()
- {
- $this->pos = $this->current = 0;
- $this->seekable = true;
- foreach ($this->streams as $stream) {
- $stream->detach();
- }
- $this->streams = [];
- }
- public function tell()
- {
- return $this->pos;
- }
- /**
- * Tries to calculate the size by adding the size of each stream.
- *
- * If any of the streams do not return a valid number, then the size of the
- * append stream cannot be determined and null is returned.
- *
- * {@inheritdoc}
- */
- public function getSize()
- {
- $size = 0;
- foreach ($this->streams as $stream) {
- $s = $stream->getSize();
- if ($s === null) {
- return null;
- }
- $size += $s;
- }
- return $size;
- }
- public function eof()
- {
- return !$this->streams ||
- ($this->current >= count($this->streams) - 1 &&
- $this->streams[$this->current]->eof());
- }
- public function rewind()
- {
- $this->seek(0);
- }
- /**
- * Attempts to seek to the given position. Only supports SEEK_SET.
- *
- * {@inheritdoc}
- */
- public function seek($offset, $whence = SEEK_SET)
- {
- if (!$this->seekable) {
- throw new \RuntimeException('This AppendStream is not seekable');
- } elseif ($whence !== SEEK_SET) {
- throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
- }
- $this->pos = $this->current = 0;
- // Rewind each stream
- foreach ($this->streams as $i => $stream) {
- try {
- $stream->rewind();
- } catch (\Exception $e) {
- throw new \RuntimeException('Unable to seek stream '
- . $i . ' of the AppendStream', 0, $e);
- }
- }
- // Seek to the actual position by reading from each stream
- while ($this->pos < $offset && !$this->eof()) {
- $result = $this->read(min(8096, $offset - $this->pos));
- if ($result === '') {
- break;
- }
- }
- }
- /**
- * Reads from all of the appended streams until the length is met or EOF.
- *
- * {@inheritdoc}
- */
- public function read($length)
- {
- $buffer = '';
- $total = count($this->streams) - 1;
- $remaining = $length;
- $progressToNext = false;
- while ($remaining > 0) {
- // Progress to the next stream if needed.
- if ($progressToNext || $this->streams[$this->current]->eof()) {
- $progressToNext = false;
- if ($this->current === $total) {
- break;
- }
- $this->current++;
- }
- $result = $this->streams[$this->current]->read($remaining);
- // Using a loose comparison here to match on '', false, and null
- if ($result == null) {
- $progressToNext = true;
- continue;
- }
- $buffer .= $result;
- $remaining = $length - strlen($buffer);
- }
- $this->pos += strlen($buffer);
- return $buffer;
- }
- public function isReadable()
- {
- return true;
- }
- public function isWritable()
- {
- return false;
- }
- public function isSeekable()
- {
- return $this->seekable;
- }
- public function write($string)
- {
- throw new \RuntimeException('Cannot write to an AppendStream');
- }
- public function getMetadata($key = null)
- {
- return $key ? null : [];
- }
- }
|