Completed
Push — releases/v0.2.1 ( d424a1...b63a3e )
by Luke
03:04
created

BufferStream::readChunk()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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 CSVelte\Traits\IsReadable;
17
use CSVelte\Traits\IsWritable;
18
use CSVelte\Contract\Streamable;
19
20
/**
21
 * Buffered Stream.
22
 *
23
 * Read operations pull from the buffer, write operations fill up the buffer.
24
 * When the buffer reaches a
25
 *
26
 * @package    CSVelte
27
 * @subpackage CSVelte\IO
28
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
29
 * @author     Luke Visinoni <[email protected]>
30
 * @since      v0.2.1
31
 * @todo       Add methods to convert KB and MB to bytes so that you don't have
32
 *             to actually know how many bytes are in 16KB. You would just do
33
 *             $buffer = new BufferStream('16KB');
34
 */
35
class BufferStream implements Streamable
36
{
37
    use IsReadable, IsWritable;
38
39
    /**
40
     * Buffer contents
41
     * @var string A string containing the buffer contents
42
     */
43
    protected $buffer = '';
44
45
    /**
46
     * Is stream readable?
47
     *
48
     * @var boolean Whether stream is readable
49
     */
50
    protected $readable = true;
51
52
    /**
53
     * Is stream writable?
54
     *
55
     * @var boolean Whether stream is writable
56
     */
57
    protected $writable = true;
58
59
    /**
60
     * Is stream seekable?
61
     *
62
     * @var boolean Whether stream is seekable
63
     */
64
    protected $seekable = false;
65
66
    /**
67
     * @var array Stream meta data
68
     *            hwm: "high water mark" - once buffer reaches this number (in bytes)
69
     *                 write() operations will begin returning false defaults to 16384 bytes (16KB)
70
     */
71
    protected $meta = [
72
        'hwm' => 16384
73
    ];
74
75
    /**
76
     * Instantiate a buffer stream.
77
     *
78
     * Instantiate a new buffer stream, optionally changing the high water mark
79
     * from its default of 16384 bytes (16KB). Once buffer reaches high water
80
     * mark, write operations will begin returning false. It's possible for buffer
81
     * size to exceed this level since it is only AFTER it is reached that writes
82
     * begin returning false.
83
     *
84
     * @param int Number (in bytes) representing buffer "high water mark"
85
     */
86 31
    public function __construct($hwm = null)
87
    {
88 31
        if (!is_null($hwm)) {
89 9
            $this->meta['hwm'] = $hwm;
90 9
        }
91 31
    }
92
93 12
    public function isEmpty()
94
    {
95 12
        return $this->getSize() === 0;
96
    }
97
98 12
    public function isFull()
99
    {
100 12
        return $this->getSize() >= $this->getMetadata('hwm');
101
    }
102
103
    /**
104
     * Readability accessor.
105
     *
106
     * Despite the fact that any class that implements this interface must also
107
     * define methods such as read and readLine, that is no guarantee that an
108
     * object will necessarily be readable. This method should tell the user
109
     * whether a stream is, in fact, readable.
110
     *
111
     * @return boolean True if readable, false otherwise
112
     */
113 1
    public function isReadable()
114
    {
115 1
        return $this->readable;
116
    }
117
118
    /**
119
     * Read in the specified amount of characters from the input source
120
     *
121
     * @param integer Amount of characters to read from input source
122
     * @return string|boolean The specified amount of characters read from input source
123
     */
124 18
    public function read($chars)
125
    {
126 18
        return $this->readChunk(null, $chars);
127
    }
128
129
    /**
130
     * Read a chunk of buffer data.
131
     *
132
     * Removes a specific chunk of data from the buffer and return it.
133
     *
134
     * @param int|null $start
135
     * @param int|null $length
136
     * @return string The chunk of data read from the buffer
137
     */
138 19
    public function readChunk($start = null, $length = null)
139
    {
140
        //dd($this->buffer, false);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
141 19
        if ($this->buffer === false) return false;
142 18
        $top = substr($this->buffer, 0, $start);
143 18
        $data = substr($this->buffer, $start, $length);
144 18
        $bottom = substr($this->buffer, $start + $length);
145 18
        $this->buffer = $top.$bottom;
146 18
        return $data;
147
    }
148
149
    /**
150
     * Read the entire stream, beginning to end.
151
     *
152
     * In most stream implementations, __toString() differs from getContents()
153
     * in that it returns the entire stream rather than just the remainder, but
154
     * due to the way this stream works (sort of like a conveyor belt), this
155
     * method is an alias to getContents()
156
     *
157
     * @return string The entire stream, beginning to end
158
     */
159
    public function __toString()
160
    {
161
        return $this->getContents();
162
    }
163
164
    /**
165
     * Read the remainder of the stream
166
     *
167
     * @return string The remainder of the stream
168
     */
169 3
    public function getContents()
170
    {
171 3
        $buffer = $this->buffer;
172 3
        $this->buffer = "";
173 3
        return $buffer;
174
    }
175
176
    /**
177
     * Return the size (in bytes) of this readable (if known).
178
     *
179
     * @return int|null Size (in bytes) of this readable
180
     */
181 21
    public function getSize()
182
    {
183 21
        return strlen($this->buffer);
184
    }
185
186
    /**
187
     * Return the current position within the stream/readable
188
     *
189
     * @return int The current position within readable
190
     */
191 2
    public function tell()
192
    {
193 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::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...
194
    }
195
196
    /**
197
     * Determine whether the end of the readable resource has been reached
198
     *
199
     * @return boolean Whether we're at the end of the readable
200
     */
201 6
    public function eof()
202
    {
203 6
        return empty($this->buffer);
204
    }
205
206
    /**
207
     * File must be able to be rewound when the end is reached
208
     */
209 3
    public function rewind()
210
    {
211 3
        $this->buffer = '';
212 3
    }
213
214
    /**
215
     * Get stream metadata as an associative array or retrieve a specific key.
216
     *
217
     * The keys returned are identical to the keys returned from PHP's
218
     * stream_get_meta_data() function.
219
     *
220
     * @param string $key Specific metadata to retrieve.
221
     * @return array|mixed|null Returns an associative array if no key is
222
     *     provided. Returns a specific key value if a key is provided and the
223
     *     value is found, or null if the key is not found.
224
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
225
     */
226 22
    public function getMetadata($key = null)
227
    {
228 22
        if (!is_null($key)) {
229 22
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
230
        }
231 2
        return $this->meta;
232
    }
233
234
    /**
235
     * Closes the stream and any underlying resources.
236
     *
237
     * @return void
238
     */
239 2
    public function close()
240
    {
241 2
        $this->buffer = false;
0 ignored issues
show
Documentation Bug introduced by
The property $buffer was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
242 2
    }
243
244
    /**
245
     * Separates any underlying resources from the stream.
246
     *
247
     * After the stream has been detached, the stream is in an unusable state.
248
     *
249
     * @return string|null Underlying PHP stream, if any
250
     */
251 1
    public function detach()
252
    {
253 1
        $buffer = $this->buffer;
254 1
        $this->buffer = false;
0 ignored issues
show
Documentation Bug introduced by
The property $buffer was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
255 1
        return $buffer;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $buffer; (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...
256
    }
257
258
    /**
259
     * Writability accessor.
260
     *
261
     * Despite the fact that any class that implements this interface must also
262
     * define methods such as write and writeLine, that is no guarantee that an
263
     * object will necessarily be writable. This method should tell the user
264
     * whether a stream is, in fact, writable.
265
     *
266
     * @return boolean True if writable, false otherwise
267
     */
268 1
    public function isWritable()
269
    {
270 1
        return $this->writable;
271
    }
272
273
    /**
274
     * Write data to the output.
275
     *
276
     * @param string The data to write
277
     * @return int The number of bytes written
278
     */
279 21
    public function write($data)
280
    {
281 21
        if ($this->getSize() >= $this->getMetadata('hwm')) {
282 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::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...
283
        }
284 21
        $this->buffer .= $data;
285 21
        return strlen($data);
286
    }
287
288
     /**
289
      * Seekability accessor.
290
      *
291
      * Despite the fact that any class that implements this interface must also
292
      * define methods such as seek, that is no guarantee that an
293
      * object will necessarily be seekable. This method should tell the user
294
      * whether a stream is, in fact, seekable.
295
      *
296
      * @return boolean True if seekable, false otherwise
297
      */
298 1
    public function isSeekable()
299
    {
300 1
        return $this->seekable;
301
    }
302
303
    /**
304
     * Seek to specified offset.
305
     *
306
     * @param integer Offset to seek to
307
     * @param integer Position from whence the offset should be applied
308
     * @return boolean True if seek was successful
309
     */
310 1
    public function seek($offset, $whence = SEEK_SET)
311
    {
312 1
        return $this->seekable;
313
    }
314
315
}
316