1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | /** |
||||||
6 | * Stream iterator |
||||||
7 | * |
||||||
8 | * @copyright Copryright (c) 2018 gyselroth GmbH (https://gyselroth.com) |
||||||
9 | * @license MIT https://opensource.org/licenses/MIT |
||||||
10 | */ |
||||||
11 | |||||||
12 | namespace StreamIterator; |
||||||
13 | |||||||
14 | use Closure; |
||||||
15 | use Countable; |
||||||
16 | use IteratorAggregate; |
||||||
17 | use Psr\Http\Message\StreamInterface; |
||||||
18 | use Traversable; |
||||||
19 | |||||||
20 | /** |
||||||
21 | * Wraps an interator to iterate through and cast each entry to string via a callback. |
||||||
22 | */ |
||||||
23 | class StreamIterator implements StreamInterface |
||||||
24 | { |
||||||
25 | /** |
||||||
26 | * @var Traversable |
||||||
27 | */ |
||||||
28 | private $iterator; |
||||||
29 | |||||||
30 | /** |
||||||
31 | * Current position in iterator. |
||||||
32 | * |
||||||
33 | * @var int |
||||||
34 | */ |
||||||
35 | private $position = 0; |
||||||
36 | |||||||
37 | /** |
||||||
38 | * Stringify callback. |
||||||
39 | * |
||||||
40 | * @var Closure |
||||||
41 | */ |
||||||
42 | private $stringify; |
||||||
43 | |||||||
44 | protected $exception_handler; |
||||||
45 | |||||||
46 | /** |
||||||
47 | * Construct a stream instance using an iterator. |
||||||
48 | * |
||||||
49 | * If the iterator is an IteratorAggregate, pulls the inner iterator |
||||||
50 | * and composes that instead, to ensure we have access to the various |
||||||
51 | * iterator capabilities. |
||||||
52 | */ |
||||||
53 | 20 | public function __construct(Traversable $iterator, Closure $stringify = null, Closure $exception_handler=null) |
|||||
54 | { |
||||||
55 | 20 | if ($iterator instanceof IteratorAggregate) { |
|||||
56 | $iterator = $iterator->getIterator(); |
||||||
57 | } |
||||||
58 | 20 | $this->iterator = $iterator; |
|||||
59 | 20 | $this->stringify = $stringify; |
|||||
60 | 20 | $this->exception_handler = $exception_handler; |
|||||
61 | 20 | } |
|||||
62 | |||||||
63 | /** |
||||||
64 | * @return string |
||||||
65 | */ |
||||||
66 | 2 | public function __toString() |
|||||
67 | { |
||||||
68 | try { |
||||||
69 | 2 | if($this->position !== 0) { |
|||||
70 | $this->iterator->rewind(); |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||||
71 | } |
||||||
72 | |||||||
73 | 2 | return $this->getContents(); |
|||||
74 | } catch(\Throwable $e) { |
||||||
75 | if($this->exception_handler !== null) { |
||||||
76 | return ''.$this->exception_handler->call($this, $e); |
||||||
77 | } |
||||||
78 | |||||||
79 | throw $e; |
||||||
80 | } |
||||||
81 | } |
||||||
82 | |||||||
83 | /** |
||||||
84 | * No-op. |
||||||
85 | */ |
||||||
86 | 1 | public function close() |
|||||
87 | { |
||||||
88 | 1 | } |
|||||
89 | |||||||
90 | /** |
||||||
91 | * @return null|Traversable |
||||||
92 | */ |
||||||
93 | 1 | public function detach() |
|||||
94 | { |
||||||
95 | 1 | $iterator = $this->iterator; |
|||||
96 | 1 | $this->iterator = null; |
|||||
97 | |||||||
98 | 1 | return $iterator; |
|||||
0 ignored issues
–
show
The expression
return $iterator returns the type Traversable which is incompatible with the return type mandated by Psr\Http\Message\StreamInterface::detach() of null|resource .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||||
99 | } |
||||||
100 | |||||||
101 | /** |
||||||
102 | * @return null|int returns the size of the iterator, or null if unknown |
||||||
103 | */ |
||||||
104 | 1 | public function getSize() |
|||||
105 | { |
||||||
106 | 1 | if ($this->iterator instanceof Countable) { |
|||||
107 | 1 | return count($this->iterator); |
|||||
108 | } |
||||||
109 | |||||||
110 | return null; |
||||||
111 | } |
||||||
112 | |||||||
113 | /** |
||||||
114 | * @return int Position of the iterator |
||||||
115 | */ |
||||||
116 | 2 | public function tell() |
|||||
117 | { |
||||||
118 | 2 | return $this->position; |
|||||
119 | } |
||||||
120 | |||||||
121 | /** |
||||||
122 | * End of File. |
||||||
123 | * |
||||||
124 | * @return bool |
||||||
125 | */ |
||||||
126 | 2 | public function eof() |
|||||
127 | { |
||||||
128 | 2 | if ($this->iterator instanceof Countable) { |
|||||
129 | 2 | return $this->position === count($this->iterator); |
|||||
130 | } |
||||||
131 | |||||||
132 | return !$this->iterator->valid(); |
||||||
0 ignored issues
–
show
The method
valid() does not exist on Traversable . It seems like you code against a sub-type of Traversable such as Yaf_Config_Simple or Yaf\Session or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or Nette\Utils\Finder or Nette\Utils\Html or Nette\Utils\ArrayList or SimpleXMLIterator .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
133 | } |
||||||
134 | |||||||
135 | /** |
||||||
136 | * Check if seekable. |
||||||
137 | * |
||||||
138 | * @return bool |
||||||
139 | */ |
||||||
140 | 1 | public function isSeekable() |
|||||
141 | { |
||||||
142 | 1 | return true; |
|||||
143 | } |
||||||
144 | |||||||
145 | /** |
||||||
146 | * Seek the iterator. |
||||||
147 | * |
||||||
148 | * @param int $offset Stream offset |
||||||
149 | * @param int $whence ignored |
||||||
150 | * |
||||||
151 | * @return bool returns TRUE on success or FALSE on failure |
||||||
152 | */ |
||||||
153 | 2 | public function seek($offset, $whence = SEEK_SET) |
|||||
154 | { |
||||||
155 | 2 | if (!is_int($offset) && !is_numeric($offset)) { |
|||||
0 ignored issues
–
show
|
|||||||
156 | 1 | return false; |
|||||
157 | } |
||||||
158 | |||||||
159 | 1 | $offset = (int) $offset; |
|||||
160 | 1 | if ($offset < 0) { |
|||||
161 | return false; |
||||||
162 | } |
||||||
163 | |||||||
164 | 1 | $key = $this->iterator->key(); |
|||||
0 ignored issues
–
show
The method
key() does not exist on Traversable . It seems like you code against a sub-type of Traversable such as Yaf_Config_Simple or Yaf\Session or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or MongoGridFSCursor or Nette\Utils\Finder or Nette\Utils\Html or Nette\Utils\ArrayList or SimpleXMLIterator .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
165 | 1 | if (!is_int($key) && !is_numeric($key)) { |
|||||
166 | $key = 0; |
||||||
167 | $this->iterator->rewind(); |
||||||
168 | } |
||||||
169 | |||||||
170 | 1 | if ($key >= $offset) { |
|||||
171 | $key = 0; |
||||||
172 | $this->iterator->rewind(); |
||||||
173 | } |
||||||
174 | |||||||
175 | 1 | while ($this->iterator->valid() && $key < $offset) { |
|||||
176 | 1 | $this->iterator->next(); |
|||||
0 ignored issues
–
show
The method
next() does not exist on Traversable . It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or Yaf_Config_Simple or Yaf\Session or IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or IntlBreakIterator or MongoGridFSCursor or Nette\Utils\Finder or Nette\Utils\Html or Nette\Utils\ArrayList or SimpleXMLIterator .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
177 | 1 | ++$key; |
|||||
178 | } |
||||||
179 | |||||||
180 | 1 | $this->position = $key; |
|||||
181 | |||||||
182 | 1 | return true; |
|||||
183 | } |
||||||
184 | |||||||
185 | /** |
||||||
186 | * @see seek() |
||||||
187 | * |
||||||
188 | * @return bool returns true on success or false on failure |
||||||
189 | */ |
||||||
190 | 1 | public function rewind() |
|||||
191 | { |
||||||
192 | 1 | $this->iterator->rewind(); |
|||||
193 | 1 | $this->position = 0; |
|||||
194 | |||||||
195 | 1 | return true; |
|||||
196 | } |
||||||
197 | |||||||
198 | /** |
||||||
199 | * Non-writable. |
||||||
200 | * |
||||||
201 | * @return bool Always returns false |
||||||
202 | */ |
||||||
203 | 1 | public function isWritable() |
|||||
204 | { |
||||||
205 | 1 | return false; |
|||||
206 | } |
||||||
207 | |||||||
208 | /** |
||||||
209 | * Non-writable. |
||||||
210 | * |
||||||
211 | * @param string $string the string that is to be written |
||||||
212 | * |
||||||
213 | * @return bool|int Always returns false |
||||||
214 | */ |
||||||
215 | 1 | public function write($string) |
|||||
216 | { |
||||||
217 | 1 | return false; |
|||||
0 ignored issues
–
show
The expression
return false returns the type false which is incompatible with the return type mandated by Psr\Http\Message\StreamInterface::write() of integer .
In the issue above, the returned value is violating the contract defined by the mentioned interface. Let's take a look at an example: interface HasName {
/** @return string */
public function getName();
}
class Name {
public $name;
}
class User implements HasName {
/** @return string|Name */
public function getName() {
return new Name('foo'); // This is a violation of the ``HasName`` interface
// which only allows a string value to be returned.
}
}
![]() |
|||||||
218 | } |
||||||
219 | |||||||
220 | /** |
||||||
221 | * @return bool |
||||||
222 | */ |
||||||
223 | 1 | public function isReadable() |
|||||
224 | { |
||||||
225 | 1 | return true; |
|||||
226 | } |
||||||
227 | |||||||
228 | /** |
||||||
229 | * Read content from iterator with a lenght limit (number of entries). |
||||||
230 | * |
||||||
231 | * @param int $length Read up to $length items from the iterator |
||||||
232 | * |
||||||
233 | * @return string |
||||||
234 | */ |
||||||
235 | 5 | public function read($length) |
|||||
236 | { |
||||||
237 | 5 | $index = 0; |
|||||
238 | 5 | $contents = ''; |
|||||
239 | 5 | while ($this->iterator->valid() && $index < $length) { |
|||||
240 | 5 | if ($this->stringify !== null) { |
|||||
241 | 1 | $contents .= $this->stringify->call($this, $this->iterator->current()); |
|||||
0 ignored issues
–
show
The method
current() does not exist on Traversable . It seems like you code against a sub-type of Traversable such as IntlCodePointBreakIterator or Yaf_Config_Simple or Yaf\Session or IntlRuleBasedBreakIterator or Yaf_Session or Yaf\Config\Simple or Yaf\Config\Ini or Iterator or Yaf_Config_Ini or IntlBreakIterator or MongoGridFSCursor or Nette\Utils\Finder or Nette\Utils\Html or Nette\Utils\ArrayList or SimpleXMLIterator .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
242 | } else { |
||||||
243 | 4 | $contents .= $this->iterator->current(); |
|||||
244 | } |
||||||
245 | |||||||
246 | 5 | $this->iterator->next(); |
|||||
247 | 5 | ++$this->position; |
|||||
248 | 5 | ++$index; |
|||||
249 | } |
||||||
250 | |||||||
251 | 5 | return $contents; |
|||||
252 | } |
||||||
253 | |||||||
254 | /** |
||||||
255 | * @return string |
||||||
256 | */ |
||||||
257 | 4 | public function getContents() |
|||||
258 | { |
||||||
259 | 4 | $contents = ''; |
|||||
260 | 4 | while ($this->iterator->valid()) { |
|||||
261 | 4 | if ($this->stringify !== null) { |
|||||
262 | 1 | $contents .= $this->stringify->call($this, $this->iterator->current()); |
|||||
263 | } else { |
||||||
264 | 3 | $contents .= $this->iterator->current(); |
|||||
265 | } |
||||||
266 | |||||||
267 | 4 | $this->iterator->next(); |
|||||
268 | 4 | ++$this->position; |
|||||
269 | } |
||||||
270 | |||||||
271 | 4 | return $contents; |
|||||
272 | } |
||||||
273 | |||||||
274 | /** |
||||||
275 | * @param string $key specific metadata to retrieve |
||||||
276 | * |
||||||
277 | * @return null|array returns an empty array if no key is provided, and |
||||||
278 | * null otherwise |
||||||
279 | */ |
||||||
280 | 2 | public function getMetadata($key = null) |
|||||
281 | { |
||||||
282 | 2 | return ($key === null) ? [] : null; |
|||||
283 | } |
||||||
284 | } |
||||||
285 |