krz/vox-populi

A Tumblr web client.

clone: git clone https://gitbay.org/krz/vox-populi.git

main: vendor/guzzlehttp/psr7/src/AppendStream.php · raw

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