Issues (8)

src/StreamIterator.php (8 issues)

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
The method rewind() 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 ignore-call  annotation

70
                $this->iterator->/** @scrutinizer ignore-call */ 
71
                                 rewind();
Loading history...
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
Bug Best Practice introduced by
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.
    }
}
Loading history...
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 ignore-call  annotation

132
        return !$this->iterator->/** @scrutinizer ignore-call */ valid();
Loading history...
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
The condition is_int($offset) is always true.
Loading history...
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 ignore-call  annotation

164
        /** @scrutinizer ignore-call */ 
165
        $key = $this->iterator->key();
Loading history...
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 ignore-call  annotation

176
            $this->iterator->/** @scrutinizer ignore-call */ 
177
                             next();
Loading history...
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
Bug Best Practice introduced by
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.
    }
}
Loading history...
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 ignore-call  annotation

241
                $contents .= $this->stringify->call($this, $this->iterator->/** @scrutinizer ignore-call */ current());
Loading history...
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