| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 | <?phpnamespace 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;    private $detached = false;    /**     * @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;        foreach ($this->streams as $stream) {            $stream->close();        }        $this->streams = [];    }    /**     * Detaches each attached stream     *     * {@inheritdoc}     */    public function detach()    {        $this->close();        $this->detached = true;    }    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 : [];    }}
 |