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