Completed
Branch refactor/142 (8a1d2c)
by Luke
02:46
created

BufferStream::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
ccs 5
cts 5
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 21
    public function __construct($hwm = null)
87
    {
88 21
        if (!is_null($hwm)) {
89 9
            $this->meta['hwm'] = $hwm;
90 9
        }
91 21
    }
92
93 11
    public function isEmpty()
94
    {
95 11
        return $this->getSize() === 0;
96
    }
97
98 11
    public function isFull()
99
    {
100 11
        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
        if (!$this->eof()) {
127 16
            $data = substr($this->buffer, 0, $chars);
128 16
            $this->buffer = substr($this->buffer, $chars);
129 16
            return $data;
130
        }
131 7
        return false;
132
    }
133
134
    /**
135
     * Read the entire stream, beginning to end.
136
     *
137
     * In most stream implementations, __toString() differs from getContents()
138
     * in that it returns the entire stream rather than just the remainder, but
139
     * due to the way this stream works (sort of like a conveyor belt), this
140
     * method is an alias to getContents()
141
     *
142
     * @return string The entire stream, beginning to end
143
     */
144 1
    public function __toString()
145
    {
146 1
        return $this->getContents();
147
    }
148
149
    /**
150
     * Read the remainder of the stream
151
     *
152
     * @return string The remainder of the stream
153
     */
154 3
    public function getContents()
155
    {
156 3
        $buffer = $this->buffer;
157 3
        $this->buffer = "";
158 3
        return $buffer;
159
    }
160
161
    /**
162
     * Return the size (in bytes) of this readable (if known).
163
     *
164
     * @return int|null Size (in bytes) of this readable
165
     */
166 20
    public function getSize()
167
    {
168 20
        return strlen($this->buffer);
169
    }
170
171
    /**
172
     * Return the current position within the stream/readable
173
     *
174
     * @return int The current position within readable
175
     */
176 2
    public function tell()
177
    {
178 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...
179
    }
180
181
    /**
182
     * Determine whether the end of the readable resource has been reached
183
     *
184
     * @return boolean Whether we're at the end of the readable
185
     */
186 18
    public function eof()
187
    {
188 18
        return empty($this->buffer);
189
    }
190
191
    /**
192
     * File must be able to be rewound when the end is reached
193
     */
194 3
    public function rewind()
195
    {
196 3
        $this->buffer = '';
197 3
    }
198
199
    /**
200
     * Get stream metadata as an associative array or retrieve a specific key.
201
     *
202
     * The keys returned are identical to the keys returned from PHP's
203
     * stream_get_meta_data() function.
204
     *
205
     * @param string $key Specific metadata to retrieve.
206
     * @return array|mixed|null Returns an associative array if no key is
207
     *     provided. Returns a specific key value if a key is provided and the
208
     *     value is found, or null if the key is not found.
209
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
210
     */
211 21
    public function getMetadata($key = null)
212
    {
213 21
        if (!is_null($key)) {
214 21
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
215
        }
216 2
        return $this->meta;
217
    }
218
219
    /**
220
     * Closes the stream and any underlying resources.
221
     *
222
     * @return void
223
     */
224 1
    public function close()
225
    {
226 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...
227 1
    }
228
229
    /**
230
     * Separates any underlying resources from the stream.
231
     *
232
     * After the stream has been detached, the stream is in an unusable state.
233
     *
234
     * @return string|null Underlying PHP stream, if any
235
     */
236 1
    public function detach()
237
    {
238 1
        $buffer = $this->buffer;
239 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...
240 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...
241
    }
242
243
    /**
244
     * Writability accessor.
245
     *
246
     * Despite the fact that any class that implements this interface must also
247
     * define methods such as write and writeLine, that is no guarantee that an
248
     * object will necessarily be writable. This method should tell the user
249
     * whether a stream is, in fact, writable.
250
     *
251
     * @return boolean True if writable, false otherwise
252
     */
253 1
    public function isWritable()
254
    {
255 1
        return $this->writable;
256
    }
257
258
    /**
259
     * Write data to the output.
260
     *
261
     * @param string The data to write
262
     * @return int The number of bytes written
263
     */
264 20
    public function write($data)
265
    {
266 20
        if ($this->getSize() >= $this->getMetadata('hwm')) {
267 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...
268
        }
269 20
        $this->buffer .= $data;
270 20
        return strlen($data);
271
    }
272
273
     /**
274
      * Seekability accessor.
275
      *
276
      * Despite the fact that any class that implements this interface must also
277
      * define methods such as seek, that is no guarantee that an
278
      * object will necessarily be seekable. This method should tell the user
279
      * whether a stream is, in fact, seekable.
280
      *
281
      * @return boolean True if seekable, false otherwise
282
      */
283 1
    public function isSeekable()
284
    {
285 1
        return $this->seekable;
286
    }
287
288
    /**
289
     * Seek to specified offset.
290
     *
291
     * @param integer Offset to seek to
292
     * @param integer Position from whence the offset should be applied
293
     * @return boolean True if seek was successful
294
     */
295 1
    public function seek($offset, $whence = SEEK_SET)
296
    {
297 1
        return $this->seekable;
298
    }
299
300
}
301