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