Buffer   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Test Coverage

Coverage 44.17%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 96
c 2
b 0
f 0
dl 0
loc 384
ccs 53
cts 120
cp 0.4417
rs 7.44
wmc 52

28 Methods

Rating   Name   Duplication   Size   Complexity  
A writeInt() 0 3 1
B factory() 0 24 7
A getMetaData() 0 3 1
A writeNumeric() 0 3 1
A writeShort() 0 3 1
A rewind() 0 4 2
A position() 0 4 1
A allocate() 0 11 3
A readInt() 0 3 1
A __construct() 0 9 3
A writeFloat() 0 3 1
A readBytes() 0 8 2
A readNumeric() 0 13 3
A readFloat() 0 3 1
A seek() 0 5 1
A skip() 0 3 1
A readShort() 0 3 1
A write() 0 6 2
A saveToFile() 0 5 1
A read() 0 8 2
A readDouble() 0 3 1
A writeDouble() 0 3 1
A truncate() 0 4 1
A readString() 0 17 5
A writeNull() 0 3 1
A writeStream() 0 15 3
A getStream() 0 3 1
A writeString() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like Buffer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Buffer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SPSS;
4
5
class Buffer
6
{
7
    /**
8
     * @var mixed
9
     */
10
    public $context;
11
12
    /**
13
     * @var bool
14
     */
15
    public $isBigEndian = false;
16
17
    /**
18
     * @var string
19
     */
20
    public $charset;
21
22
    /**
23
     * @var resource
24
     */
25
    private $_stream;
26
27
    /**
28
     * @var int
29
     */
30
    private $_position = 0;
31
32
    /**
33
     * Buffer constructor.
34
     *
35
     * @param resource $stream Stream resource to wrap.
36
     * @param array $options Associative array of options.
37
     */
38 5
    private function __construct($stream, $options = [])
39
    {
40 5
        if (! is_resource($stream)) {
41
            throw new \InvalidArgumentException('Stream must be a resource.');
42
        }
43 5
        $this->_stream = $stream;
44
45 5
        if (isset($options['context'])) {
46
            $this->context = $options['context'];
47
        }
48 5
    }
49
50
    /**
51
     * Create a new stream based on the input type.
52
     *
53
     * @param resource|string $resource Entity body data
54
     * @param array $options Additional options
55
     * @return Buffer
56
     */
57 5
    public static function factory($resource = '', $options = [])
58
    {
59 5
        $type = gettype($resource);
60
61
        switch ($type) {
62 5
            case 'string':
63 2
                $stream = isset($options['memory']) ?
64 2
                    fopen('php://memory', 'r+') :
65 2
                    fopen('php://temp', 'r+');
66 2
                if ($resource !== '') {
67
                    fwrite($stream, $resource);
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type resource; however, parameter $string of fwrite() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
                    fwrite($stream, /** @scrutinizer ignore-type */ $resource);
Loading history...
Bug introduced by
It seems like $stream can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
                    fwrite(/** @scrutinizer ignore-type */ $stream, $resource);
Loading history...
68
                    fseek($stream, 0);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $handle of fseek() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

68
                    fseek(/** @scrutinizer ignore-type */ $stream, 0);
Loading history...
69
                }
70
71 2
                return new self($stream, $options);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
                return new self(/** @scrutinizer ignore-type */ $stream, $options);
Loading history...
72 3
            case 'resource':
73 3
                return new self($resource, $options);
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type string; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

73
                return new self(/** @scrutinizer ignore-type */ $resource, $options);
Loading history...
74
            case 'object':
75
                if (method_exists($resource, '__toString')) {
76
                    return self::factory((string) $resource, $options);
77
                }
78
        }
79
80
        throw new \InvalidArgumentException(sprintf('Invalid resource type: %s.', $type));
81
    }
82
83
    /**
84
     * @param int $length
85
     * @param bool $skip
86
     * @return Buffer
87
     * @throws Exception
88
     */
89
    public function allocate($length, $skip = true)
90
    {
91
        $stream = fopen('php://memory', 'r+');
92
        if (stream_copy_to_stream($this->_stream, $stream, $length)) {
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
        if (stream_copy_to_stream($this->_stream, /** @scrutinizer ignore-type */ $stream, $length)) {
Loading history...
93
            if ($skip) {
94
                $this->skip($length);
95
            }
96
97
            return new self($stream);
0 ignored issues
show
Bug introduced by
It seems like $stream can also be of type false; however, parameter $stream of SPSS\Buffer::__construct() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            return new self(/** @scrutinizer ignore-type */ $stream);
Loading history...
98
        }
99
        throw new Exception('Buffer allocation failed.');
100
    }
101
102
    /**
103
     * @param int $length
104
     * @return void
105
     */
106 2
    public function skip($length)
107
    {
108 2
        $this->_position += $length;
109 2
    }
110
111
    /**
112
     * @param string $file Path to file
113
     * @return false|int
114
     */
115
    public function saveToFile($file)
116
    {
117
        rewind($this->_stream);
118
119
        return file_put_contents($file, $this->_stream);
120
    }
121
122
    /**
123
     * @param resource $resource
124
     * @param null|int $maxlength
125
     * @return false|int
126
     */
127
    public function writeStream($resource, $maxlength = null)
128
    {
129
        if (! is_resource($resource)) {
130
            throw new \InvalidArgumentException('Invalid resource type.');
131
        }
132
133
        if ($maxlength) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxlength of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
134
            $length = stream_copy_to_stream($resource, $this->_stream, $maxlength);
135
        } else {
136
            $length = stream_copy_to_stream($resource, $this->_stream);
137
        }
138
139
        $this->_position += $length;
140
141
        return $length;
142
    }
143
144
    /**
145
     * @return resource
146
     */
147
    public function getStream()
148
    {
149
        return $this->_stream;
150
    }
151
152
    /**
153
     * @param int $length
154
     * @param int $round
155
     * @param null $charset
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $charset is correct as it would always require null to be passed?
Loading history...
156
     * @return false|string
157
     */
158
    public function readString($length, $round = 0, $charset = null)
159
    {
160
        if ($bytes = $this->readBytes($length)) {
161
            if ($round) {
162
                $this->skip(Utils::roundUp($length, $round) - $length);
163
            }
164
            $str = Utils::bytesToString($bytes);
165
            if ($charset) {
0 ignored issues
show
introduced by
$charset is of type null, thus it always evaluated to false.
Loading history...
166
                $str = mb_convert_encoding($str, 'utf8', $charset);
167
            } elseif ($this->charset) {
168
                $str = mb_convert_encoding($str, 'utf8', $this->charset);
169
            }
170
171
            return $str;
172
        }
173
174
        return false;
175
    }
176
177
    /**
178
     * @param $length
179
     * @return false|array
180
     */
181 3
    public function readBytes($length)
182
    {
183 3
        $bytes = $this->read($length);
184 3
        if ($bytes != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $bytes of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
185 3
            return array_values(unpack('C*', $bytes));
0 ignored issues
show
Bug introduced by
It seems like unpack('C*', $bytes) can also be of type false; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

185
            return array_values(/** @scrutinizer ignore-type */ unpack('C*', $bytes));
Loading history...
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * @param int $length
193
     * @return false|string
194
     */
195 5
    public function read($length = null)
196
    {
197 5
        $bytes = stream_get_contents($this->_stream, $length, $this->_position);
198 5
        if ($bytes !== false) {
199 5
            $this->_position += $length;
200
        }
201
202 5
        return $bytes;
203
    }
204
205
    /**
206
     * @param $data
207
     * @param int|string $length
208
     * @param null $charset
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $charset is correct as it would always require null to be passed?
Loading history...
209
     * @return false|int
210
     */
211
    public function writeString($data, $length = '*', $charset = null)
212
    {
213
        if ($charset) {
0 ignored issues
show
introduced by
$charset is of type null, thus it always evaluated to false.
Loading history...
214
            $data = mb_convert_encoding($data, 'utf8', $charset);
215
        } elseif ($this->charset) {
216
            $data = mb_convert_encoding($data, 'utf8', $this->charset);
217
        }
218
219
        return $this->write(pack('A' . $length, $data));
220
    }
221
222
    /**
223
     * @param string $data
224
     * @param null|int $length
225
     * @return false|int
226
     */
227 5
    public function write($data, $length = null)
228
    {
229 5
        $length = $length ? fwrite($this->_stream, $data, $length) : fwrite($this->_stream, $data);
230 5
        $this->_position += $length;
231
232 5
        return $length;
233
    }
234
235
    /**
236
     * @return double
237
     */
238 2
    public function readDouble()
239
    {
240 2
        return $this->readNumeric(8, 'd');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->readNumeric(8, 'd') could also return false which is incompatible with the documented return type double. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
241
    }
242
243
    /**
244
     * @param $data
245
     * @return false|int
246
     */
247 2
    public function writeDouble($data)
248
    {
249 2
        return $this->writeNumeric($data, 'd', 8);
250
    }
251
252
    /**
253
     * @param $data
254
     * @param $format
255
     * @param null $length
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $length is correct as it would always require null to be passed?
Loading history...
256
     * @return false|int
257
     */
258 2
    public function writeNumeric($data, $format, $length = null)
259
    {
260 2
        return $this->write(pack($format, $data), $length);
261
    }
262
263
    /**
264
     * @return false|float
265
     */
266
    public function readFloat()
267
    {
268
        return $this->readNumeric(4, 'f');
269
    }
270
271
    /**
272
     * @param $data
273
     * @return false|int
274
     */
275
    public function writeFloat($data)
276
    {
277
        return $this->writeNumeric($data, 'f', 4);
278
    }
279
280
    /**
281
     * @return int
282
     */
283 2
    public function readInt()
284
    {
285 2
        return $this->readNumeric(4, 'i');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->readNumeric(4, 'i') could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
286
    }
287
288
    /**
289
     * @param $data
290
     * @return false|int
291
     */
292 2
    public function writeInt($data)
293
    {
294 2
        return $this->writeNumeric($data, 'i', 4);
295
    }
296
297
    /**
298
     * @return false|int
299
     */
300
    public function readShort()
301
    {
302
        return $this->readNumeric(2, 'v');
303
    }
304
305
    /**
306
     * @param $data
307
     * @return false|int
308
     */
309
    public function writeShort($data)
310
    {
311
        return $this->writeNumeric($data, 'v', 2);
312
    }
313
314
    /**
315
     * @param $length
316
     * @return false|int
317
     */
318
    public function writeNull($length)
319
    {
320
        return $this->write(pack('x' . $length));
321
    }
322
323
    /**
324
     * @return int
325
     */
326 2
    public function position()
327
    {
328
        //        return ftell($this->_stream);
329 2
        return $this->_position;
330
    }
331
332
    /**
333
     * @param int $offset
334
     * @param int $whence
335
     * @return int
336
     */
337
    public function seek($offset, $whence = SEEK_SET)
338
    {
339
        $this->_position = $offset;
340
341
        return fseek($this->_stream, $offset, $whence);
342
    }
343
344
    /**
345
     * @return void
346
     */
347 5
    public function rewind()
348
    {
349 5
        if (rewind($this->_stream)) {
350 5
            $this->_position = 0;
351
        }
352 5
    }
353
354
    /**
355
     * @return void
356
     */
357
    public function truncate()
358
    {
359
        ftruncate($this->_stream, 0);
360
        $this->_position = 0;
361
    }
362
363
    /**
364
     * @return array
365
     */
366
    public function getMetaData()
367
    {
368
        return stream_get_meta_data($this->_stream);
369
    }
370
371
    /**
372
     * @param int $length
373
     * @param string $format
374
     * @return false|int|float|double
375
     */
376 2
    private function readNumeric($length, $format)
377
    {
378 2
        $bytes = $this->read($length);
379 2
        if ($bytes != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $bytes of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
380 2
            if ($this->isBigEndian) {
381
                $bytes = strrev($bytes);
382
            }
383 2
            $data = unpack($format, $bytes);
384
385 2
            return $data[1];
386
        }
387
388
        return false;
389
    }
390
}
391