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}