Passed
Pull Request — master (#183)
by Luke
03:12
created

BufferStream::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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
 * @copyright Copyright (c) 2018 Luke Visinoni
10
 * @author    Luke Visinoni <[email protected]>
11
 * @license   See LICENSE file (MIT license)
12
 */
13
namespace CSVelte\IO;
14
15
use CSVelte\Contract\Streamable;
16
use CSVelte\Traits\IsReadable;
17
use CSVelte\Traits\IsWritable;
18
19
/**
20
 * Buffered Stream.
21
 *
22
 * Read operations pull from the buffer, write operations fill up the buffer.
23
 * When the buffer reaches a
24
 *
25
 * @todo       Add methods to convert KB and MB to bytes so that you don't have
26
 *             to actually know how many bytes are in 16KB. You would just do
27
 *             $buffer = new BufferStream('16KB');
28
 */
29
class BufferStream implements Streamable
30
{
31
    use IsReadable, IsWritable;
32
33
    /**
34
     * Buffer contents.
35
     *
36
     * @var string|false A string containing the buffer contents
37
     */
38
    protected $buffer = '';
39
40
    /**
41
     * Is stream readable?
42
     *
43
     * @var bool Whether stream is readable
44
     */
45
    protected $readable = true;
46
47
    /**
48
     * Is stream writable?
49
     *
50
     * @var bool Whether stream is writable
51
     */
52
    protected $writable = true;
53
54
    /**
55
     * Is stream seekable?
56
     *
57
     * @var bool Whether stream is seekable
58
     */
59
    protected $seekable = false;
60
61
    /**
62
     * @var array Stream meta data
63
     *            hwm: "high water mark" - once buffer reaches this number (in bytes)
64
     *            write() operations will begin returning false defaults to 16384 bytes (16KB)
65
     */
66
    protected $meta = [
67
        'hwm' => 16384,
68
    ];
69
70
    /**
71
     * Instantiate a buffer stream.
72
     *
73
     * Instantiate a new buffer stream, optionally changing the high water mark
74
     * from its default of 16384 bytes (16KB). Once buffer reaches high water
75
     * mark, write operations will begin returning false. It's possible for buffer
76
     * size to exceed this level since it is only AFTER it is reached that writes
77
     * begin returning false.
78
     *
79
     * @param int Number (in bytes) representing buffer "high water mark"
80
     * @param null|mixed $hwm
81
     */
82 29
    public function __construct($hwm = null)
83
    {
84 29
        if (!is_null($hwm)) {
85 9
            $this->meta['hwm'] = $hwm;
86 9
        }
87 29
    }
88
89
    /**
90
     * Read the entire stream, beginning to end.
91
     *
92
     * In most stream implementations, __toString() differs from getContents()
93
     * in that it returns the entire stream rather than just the remainder, but
94
     * due to the way this stream works (sort of like a conveyor belt), this
95
     * method is an alias to getContents()
96
     *
97
     * @return string The entire stream, beginning to end
98
     */
99 1
    public function __toString()
100
    {
101 1
        return (string) $this->getContents();
102
    }
103
104 10
    public function isEmpty()
105
    {
106 10
        return $this->getSize() === 0;
107
    }
108
109 10
    public function isFull()
110
    {
111 10
        return $this->getSize() >= $this->getMetadata('hwm');
112
    }
113
114
    /**
115
     * Readability accessor.
116
     *
117
     * Despite the fact that any class that implements this interface must also
118
     * define methods such as read and readLine, that is no guarantee that an
119
     * object will necessarily be readable. This method should tell the user
120
     * whether a stream is, in fact, readable.
121
     *
122
     * @return bool True if readable, false otherwise
123
     */
124 1
    public function isReadable()
125
    {
126 1
        return $this->readable;
127
    }
128
129
    /**
130
     * Read in the specified amount of characters from the input source.
131
     *
132
     * @param int $chars Amount of characters to read from input source
133
     *
134
     * @return string|bool The specified amount of characters read from input source
135
     */
136 17
    public function read($chars)
137
    {
138 17
        return $this->readChunk(null, $chars);
139
    }
140
141
    /**
142
     * Read a chunk of buffer data.
143
     *
144
     * Removes a specific chunk of data from the buffer and return it.
145
     *
146
     * @param int|null $start
147
     * @param int|null $length
148
     *
149
     * @return string The chunk of data read from the buffer
150
     */
151 18
    public function readChunk($start = null, $length = null)
152
    {
153 18
        if ($this->buffer === false) {
154 2
            return false;
155
        }
156 17
        $top          = substr($this->buffer, 0, $start);
157 17
        $data         = substr($this->buffer, $start, $length);
158 17
        $bottom       = substr($this->buffer, $start + $length);
159 17
        $this->buffer = $top . $bottom;
160
161 17
        return $data;
162
    }
163
164
    /**
165
     * Read the remainder of the stream.
166
     *
167
     * @return string The remainder of the stream
168
     */
169 4
    public function getContents()
170
    {
171 4
        $buffer       = $this->buffer;
172 4
        $this->buffer = '';
173
174 4
        return (string) $buffer;
175
    }
176
177
    /**
178
     * Return the size (in bytes) of this readable (if known).
179
     *
180
     * @return int|null Size (in bytes) of this readable
181
     */
182 20
    public function getSize()
183
    {
184 20
        return strlen($this->buffer);
185
    }
186
187
    /**
188
     * Return the current position within the stream/readable.
189
     *
190
     * @return int|false The current position within readable
191
     */
192 2
    public function tell()
193
    {
194 2
        return false;
195
    }
196
197
    /**
198
     * Determine whether the end of the readable resource has been reached.
199
     *
200
     * @return bool Whether we're at the end of the readable
201
     */
202 7
    public function eof()
203
    {
204 7
        return empty($this->buffer);
205
    }
206
207
    /**
208
     * File must be able to be rewound when the end is reached.
209
     */
210 4
    public function rewind()
211
    {
212 4
        $this->buffer = '';
213 4
    }
214
215
    /**
216
     * Get stream metadata as an associative array or retrieve a specific key.
217
     *
218
     * The keys returned are identical to the keys returned from PHP's
219
     * stream_get_meta_data() function.
220
     *
221
     * @param string $key Specific metadata to retrieve.
222
     *
223
     * @return array|mixed|null Returns an associative array if no key is
224
     *                          provided. Returns a specific key value if a key is provided and the
225
     *                          value is found, or null if the key is not found.
226
     *
227
     * @see http://php.net/manual/en/function.stream-get-meta-data.php
228
     */
229 21
    public function getMetadata($key = null)
230
    {
231 21
        if (!is_null($key)) {
232 21
            return isset($this->meta[$key]) ? $this->meta[$key] : null;
233
        }
234
235 2
        return $this->meta;
236
    }
237
238
    /**
239
     * Closes the stream and any underlying resources.
240
     *
241
     * @return true
242
     */
243 2
    public function close()
244
    {
245 2
        $this->buffer = false;
246
247 2
        return true;
248
    }
249
250
    /**
251
     * Separates any underlying resources from the stream.
252
     *
253
     * After the stream has been detached, the stream is in an unusable state.
254
     *
255
     * @return BufferStream|null Underlying PHP stream, if any
256
     */
257 1
    public function detach()
258
    {
259 1
        $buffer       = $this->buffer;
260 1
        $this->buffer = false;
261
262 1
        return $buffer;
263
    }
264
265
    /**
266
     * Writability accessor.
267
     *
268
     * Despite the fact that any class that implements this interface must also
269
     * define methods such as write and writeLine, that is no guarantee that an
270
     * object will necessarily be writable. This method should tell the user
271
     * whether a stream is, in fact, writable.
272
     *
273
     * @return bool True if writable, false otherwise
274
     */
275 1
    public function isWritable()
276
    {
277 1
        return $this->writable;
278
    }
279
280
    /**
281
     * Write data to the output.
282
     *
283
     * @param string $data The data to write
284
     *
285
     * @return false|int The number of bytes written
286
     */
287 20
    public function write($data)
288
    {
289 20
        if ($this->getSize() >= $this->getMetadata('hwm')) {
290 1
            return false;
291
        }
292 20
        $this->buffer .= $data;
293
294 20
        return strlen($data);
295
    }
296
297
    /**
298
     * Seekability accessor.
299
     *
300
     * Despite the fact that any class that implements this interface must also
301
     * define methods such as seek, that is no guarantee that an
302
     * object will necessarily be seekable. This method should tell the user
303
     * whether a stream is, in fact, seekable.
304
     *
305
     * @return bool True if seekable, false otherwise
306
     */
307 1
    public function isSeekable()
308
    {
309 1
        return $this->seekable;
310
    }
311
312
    /**
313
     * Seek to specified offset.
314
     *
315
     * @param int $offset Offset to seek to
316
     * @param int $whence Position from whence the offset should be applied
317
     *
318
     * @return bool True if seek was successful
319
     */
320 1
    public function seek($offset, $whence = SEEK_SET)
321
    {
322 1
        return $this->seekable;
323
    }
324
}
325