Completed
Push — releases/v0.2.1 ( 8d5d04...e66e88 )
by Luke
03:06
created

IteratorStream   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Test Coverage

Coverage 97.65%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 311
ccs 83
cts 85
cp 0.9765
rs 9.6
wmc 32
lcom 3
cbo 3

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 4
A isReadable() 0 4 1
B read() 0 18 5
A inflateBuffer() 0 8 3
A __toString() 0 5 1
A getContents() 0 12 2
A getSize() 0 4 1
A tell() 0 4 1
A eof() 0 7 2
A rewind() 0 5 1
A getMetadata() 0 7 3
A close() 0 10 3
A detach() 0 8 1
A isWritable() 0 4 1
A write() 0 4 1
A isSeekable() 0 4 1
A seek() 0 4 1
1
<?php
2
/**
3
 * CSVelte: Slender, elegant CSV for PHP.
4
 *
5
 * Inspired by Python's CSV module and Frictionless Data and the W3C's CSV
6
 * standardization efforts, CSVelte was written in an effort to take all the
7
 * suck out of working with CSV.
8
 *
9
 * @version   v0.2.1
10
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
11
 * @author    Luke Visinoni <[email protected]>
12
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
13
 */
14
namespace CSVelte\IO;
15
16
use InvalidArgumentException;
17
use \Iterator;
18
use CSVelte\IO\BufferStream;
19
use CSVelte\Traits\IsReadable;
20
use CSVelte\Traits\IsWritable;
21
use CSVelte\Contract\Streamable;
22
use Traversable;
23
use function CSVelte\getvalue;
24
25
/**
26
 * Iterator Stream.
27
 *
28
 * A read-only stream that uses an iterable to continuously fill up a buffer as
29
 * read operations deplete it.
30
 *
31
 * @package    CSVelte
32
 * @subpackage CSVelte\IO
33
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
34
 * @author     Luke Visinoni <[email protected]>
35
 * @since      v0.2.1
36
 */
37
class IteratorStream implements Streamable
38
{
39
    use IsReadable, IsWritable;
40
41
    /**
42
     * Buffer stream
43
     * @var \CSVelte\IO\BufferStream A BufferStream object
44
     */
45
    protected $buffer;
46
47
    /**
48
     * Iterator to read from
49
     *
50
     * @var Traversable
51
     */
52
    protected $iter;
53
54
    /**
55
     * Is stream readable?
56
     *
57
     * @var boolean Whether stream is readable
58
     */
59
    protected $readable = true;
60
61
    /**
62
     * Is stream writable?
63
     *
64
     * @var boolean Whether stream is writable
65
     */
66
    protected $writable = false;
67
68
    /**
69
     * Is stream seekable?
70
     *
71
     * @var boolean Whether stream is seekable
72
     */
73
    protected $seekable = false;
74
75
    /**
76
     * @var array Any additional options / meta data
77
     */
78
    protected $meta = [
79
80
    ];
81
82
    /**
83
     * Instantiate an iterator stream
84
     *
85
     * Instantiate a new iterator stream. The iterator is used to continually
86
     * refill a buffer as it is drained by read operations.
87
     *
88
     * @param \Iterator The iterator to stream data from
89
     * @param \CSVelte\IO\BufferIterator|null Either a buffer or null (to use
90
     *     default buffer)
91
     * @todo this should expect a BufferInterface or a Bufferable rather than
92
     * a BufferStream
93
     */
94 21
    public function __construct(Traversable $iter, $buffer = null)
95
    {
96 21
        $this->iter = $iter;
97 21
        if (is_null($buffer)) {
98 14
            $buffer = new BufferStream;
99 14
        }
100 21
        if (!($buffer instanceof BufferStream)) {
101 1
            throw new InvalidArgumentException(sprintf(
102 1
                "%s expected %s as second argument, got: %s",
103 1
                __CLASS__,
104 1
                BufferStream::class,
105 1
                is_object($buffer) ? get_class($buffer) : gettype($buffer)
106 1
            ));
107
        }
108 20
        $this->buffer = $buffer;
109 20
    }
110
111
    /**
112
     * Readability accessor.
113
     *
114
     * Despite the fact that any class that implements this interface must also
115
     * define methods such as read and readLine, that is no guarantee that an
116
     * object will necessarily be readable. This method should tell the user
117
     * whether a stream is, in fact, readable.
118
     *
119
     * @return boolean True if readable, false otherwise
120
     */
121 2
    public function isReadable()
122
    {
123 2
        return $this->readable;
124
    }
125
126 13
    public function read($bytes)
127
    {
128 13
        if ($this->buffer) {
129 12
            $data = '';
130 12
            while (strlen($data) < $bytes) {
131 12
                if ($this->buffer->isEmpty()) {
132 12
                    $this->inflateBuffer();
133 12
                }
134
135 12
                if (!$read = $this->buffer->read($bytes - strlen($data))) {
136 5
                    break;
137
                }
138 12
                $data .= $read;
139 12
            }
140 12
            return $data;
141
        }
142 2
        return false;
143
    }
144
145 12
    protected function inflateBuffer()
146
    {
147 12
        while (!$this->buffer->isFull() && $this->iter->valid()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method valid() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CSVelte\Reader, CSVelte\Reader\FilteredIterator, CSVelte\Table\AbstractRow, CSVelte\Table\HeaderRow, CSVelte\Table\Row, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, org\bovigo\vfs\vfsStreamContainerIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
148 12
            $data = $this->iter->current();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method current() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CSVelte\Reader, CSVelte\Reader\FilteredIterator, CSVelte\Table\AbstractRow, CSVelte\Table\HeaderRow, CSVelte\Table\Row, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, org\bovigo\vfs\vfsStreamContainerIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
149 12
            $this->buffer->write($data);
150 12
            $this->iter->next();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method next() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CSVelte\Reader, CSVelte\Reader\FilteredIterator, CSVelte\Table\AbstractRow, CSVelte\Table\HeaderRow, CSVelte\Table\Row, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, org\bovigo\vfs\vfsStreamContainerIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
151 12
        }
152 12
    }
153
154
    /**
155
     * Read the entire stream, beginning to end.
156
     *
157
     * In most stream implementations, __toString() differs from getContents()
158
     * in that it returns the entire stream rather than just the remainder, but
159
     * due to the way this stream works (sort of like a conveyor belt), this
160
     * method is an alias to getContents()
161
     *
162
     * @return string The entire stream, beginning to end
163
     */
164 4
    public function __toString()
165
    {
166 4
        $this->rewind();
167 4
        return $this->getContents();
168
    }
169
170
    /**
171
     * Read the remainder of the stream
172
     *
173
     * @return string The remainder of the stream
174
     */
175 5
    public function getContents()
176
    {
177 5
        $contents = '';
178 5
        while (!$this->eof()) {
179 5
            $contents .= $this->read(
180
                // kind of arbitrary... we have to specify something for the
181
                // chunk length, so I just used the buffer's "high water mark"
182 5
                $this->buffer->getMetadata('hwm')
183 5
            );
184 5
        }
185 5
        return $contents;
186
    }
187
188
    /**
189
     * Return the size (in bytes) of this stream (if known).
190
     *
191
     * @return int|null Size (in bytes) of this stream
192
     */
193 1
    public function getSize()
194
    {
195
        // no way to know so return null
196 1
    }
197
198
    /**
199
     * Return the current position within the stream/readable
200
     *
201
     * I can't decide whether there is any meaningful way to "tell" the
202
     * current position within this type of stream. For now I'm just
203
     * going to return false because the nature of this type of stream
204
     * means that it technically has no definitive beginning or end and
205
     * therefor no absolute position. If a "tell" is truly needed, I
206
     * suppose I could keep track of how many bytes have been read over
207
     * the lifetime of the object, but I don't think that is meaningful
208
     * and/or useful.
209
     *
210
     * @return int|false The current position within readable
211
     */
212 1
    public function tell()
213
    {
214 1
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface CSVelte\Contract\Streamable::tell of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
215
    }
216
217
    /**
218
     * Determine whether the end of the stream has been reached
219
     *
220
     * @return boolean Whether we're at the end of the stream
221
     */
222 5
    public function eof()
223
    {
224
        return (
225 5
            !$this->iter->valid() &&
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method valid() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CSVelte\Reader, CSVelte\Reader\FilteredIterator, CSVelte\Table\AbstractRow, CSVelte\Table\HeaderRow, CSVelte\Table\Row, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, org\bovigo\vfs\vfsStreamContainerIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
226 5
            $this->buffer->eof()
227 5
        );
228
    }
229
230
    /**
231
     * Rewind to beginning of stream
232
     */
233 4
    public function rewind()
234
    {
235 4
        $this->iter->rewind();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method rewind() does only exist in the following implementations of said interface: AppendIterator, ArrayIterator, CSVelte\Reader, CSVelte\Reader\FilteredIterator, CSVelte\Table\AbstractRow, CSVelte\Table\HeaderRow, CSVelte\Table\Row, CachingIterator, CallbackFilterIterator, DirectoryIterator, DoctrineTest\InstantiatorTestAsset\PharAsset, EmptyIterator, File_Iterator, FilesystemIterator, FilterIterator, Generator, GlobIterator, HttpMessage, HttpRequestPool, Imagick, ImagickPixelIterator, InfiniteIterator, Issue523, IteratorIterator, LimitIterator, MongoCommandCursor, MongoCursor, MongoGridFSCursor, MultipleIterator, NoRewindIterator, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_Token_Stream, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, SQLiteResult, SebastianBergmann\CodeCoverage\Node\Iterator, SimpleXMLIterator, SplDoublyLinkedList, SplFileObject, SplFixedArray, SplHeap, SplMaxHeap, SplMinHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, TestIterator, TestIterator2, org\bovigo\vfs\vfsStreamContainerIterator.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
236 4
        $this->buffer->rewind();
237 4
    }
238
239
    /**
240
     * Get stream metadata as an associative array or retrieve a specific key.
241
     *
242
     * The keys returned are identical to the keys returned from PHP's
243
     * stream_get_meta_data() function.
244
     *
245
     * @param string $key Specific metadata to retrieve.
246
     * @return array|mixed|null Returns an associative array if no key is
247
     *     provided. Returns a specific key value if a key is provided and the
248
     *     value is found, or null if the key is not found.
249
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
250
     */
251 2
    public function getMetadata($key = null)
252
    {
253 2
        if (!is_null($key)) {
254 1
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
255
        }
256 1
        return $this->meta;
257
    }
258
259
    /**
260
     * Closes the stream and any underlying resources.
261
     *
262
     * @return void
263
     */
264 1
    public function close()
265
    {
266 1
        $buff = $this->buffer->close();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $buff is correct as $this->buffer->close() (which targets CSVelte\IO\BufferStream::close()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
267 1
        $iter = true;
268 1
        if (method_exists($this->iter, 'close')) {
269
            $iter = $this->iter->close();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Traversable as the method close() does only exist in the following implementations of said interface: mysqli_result.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
270
        }
271 1
        $this->buffer = null;
272 1
        return $buff && $iter;
273
    }
274
275
    /**
276
     * Separates any underlying resources from the stream.
277
     *
278
     * After the stream has been detached, the stream is in an unusable state.
279
     *
280
     * @return array|Resource Underlying PHP stream, if any
281
     * @todo I'm not sure what detach is for so I don't know whether what I'm
282
     *     doing here is right. The reason I have the method at all is because
283
     *     psr7 StreamInterface has one.f
284
     */
285 1
    public function detach()
286
    {
287 1
        $data = (string) $this;
288 1
        $this->buffer = null;
289 1
        $this->iter = null;
290 1
        $this->readable = false;
291 1
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $data; (string) is incompatible with the return type declared by the interface CSVelte\Contract\Streamable::detach of type resource|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
292
    }
293
294
    /**
295
     * Writability accessor.
296
     *
297
     * Despite the fact that any class that implements this interface must also
298
     * define methods such as write and writeLine, that is no guarantee that an
299
     * object will necessarily be writable. This method should tell the user
300
     * whether a stream is, in fact, writable.
301
     *
302
     * @return boolean True if writable, false otherwise
303
     */
304 1
    public function isWritable()
305
    {
306 1
        return $this->writable;
307
    }
308
309
    /**
310
     * Write data to the output.
311
     *
312
     * @param string The data to write
313
     * @return int|false The number of bytes written
314
     */
315 2
    public function write($data)
316
    {
317 2
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface CSVelte\Contract\Streamable::write of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
318
    }
319
320
     /**
321
      * Seekability accessor.
322
      *
323
      * Despite the fact that any class that implements this interface must also
324
      * define methods such as seek, that is no guarantee that an
325
      * object will necessarily be seekable. This method should tell the user
326
      * whether a stream is, in fact, seekable.
327
      *
328
      * @return boolean True if seekable, false otherwise
329
      */
330 2
    public function isSeekable()
331
    {
332 2
        return $this->seekable;
333
    }
334
335
    /**
336
     * Seek to specified offset.
337
     *
338
     * @param integer Offset to seek to
339
     * @param integer Position from whence the offset should be applied
340
     * @return boolean True if seek was successful
341
     */
342 1
    public function seek($offset, $whence = SEEK_SET)
343
    {
344 1
        return $this->seekable;
345
    }
346
347
}
348