Completed
Push — master ( 42bf17...fdb7de )
by Sergey
04:25
created

StreamIO   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 312
Duplicated Lines 3.85 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 76.47%

Importance

Changes 7
Bugs 0 Features 1
Metric Value
wmc 40
c 7
b 0
f 1
lcom 1
cbo 6
dl 12
loc 312
ccs 91
cts 119
cp 0.7647
rs 8.2608

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B open() 0 52 6
A setReadingTimeout() 0 10 2
A setConnectionTimeout() 0 6 1
A close() 0 14 2
C write() 0 28 7
A peek() 0 16 3
A read() 12 12 2
B recv() 0 30 6
A applyReadingTimeout() 0 6 1
A isOpen() 0 4 2
C createStreamContext() 0 30 7

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like StreamIO 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 StreamIO, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ButterAMQP\IO;
4
5
use ButterAMQP\Exception\IOClosedException;
6
use ButterAMQP\Exception\IOException;
7
use ButterAMQP\IOInterface;
8
use ButterAMQP\Binary\ReadableBinaryData;
9
use Psr\Log\LoggerAwareInterface;
10
use Psr\Log\LoggerAwareTrait;
11
use Psr\Log\NullLogger;
12
13
class StreamIO implements IOInterface, LoggerAwareInterface
14
{
15
    use LoggerAwareTrait;
16
17
    /**
18
     * @var resource|null
19
     */
20
    private $stream;
21
22
    /**
23
     * @var string
24
     */
25
    private $buffer;
26
27
    /**
28
     * @var int|float
29
     */
30
    private $connectionTimeout;
31
32
    /**
33
     * @var int|float
34
     */
35
    private $readingTimeout;
36
37
    /**
38
     * @var int
39
     */
40
    private $readAheadSize;
41
42
    /**
43
     * Initialize default logger.
44
     *
45
     * @param int|float $connectionTimeout
46
     * @param int|float $readingTimeout
47
     * @param int       $readAheadSize
48
     */
49 25
    public function __construct($connectionTimeout = 30, $readingTimeout = 1, $readAheadSize = 130000)
0 ignored issues
show
Unused Code introduced by
The parameter $readAheadSize is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
50
    {
51 25
        $this->logger = new NullLogger();
52
53 25
        $this->setConnectionTimeout($connectionTimeout);
54 25
        $this->setReadingTimeout($readingTimeout);
55 25
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 25
    public function open($protocol, $host, $port, array $parameters = [])
61
    {
62 25
        if ($this->stream && $this->isOpen()) {
63
            return $this;
64
        }
65
66
        //$this->logger->debug(sprintf('Connecting to "%s://%s:%d"...', $protocol, $host, $port), [
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% 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...
67
        //    'protocol' => $protocol,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
68
        //    'host' => $host,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
69
        //    'port' => $port,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
70
        //    'parameters' => $parameters,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
71
        //]);
72
73 25
        $context = $this->createStreamContext($parameters);
74
75 25
        if (isset($parameters['connection_timeout'])) {
76
            $this->setConnectionTimeout($parameters['connection_timeout']);
77
        }
78
79 25
        if (isset($parameters['timeout'])) {
80
            $this->setReadingTimeout($parameters['timeout']);
81
        }
82
83 25
        $this->stream = @stream_socket_client(
84 25
            sprintf('%s://%s:%d', $protocol, $host, $port),
85 25
            $errno,
86 25
            $errstr,
87 25
            $this->connectionTimeout,
88 25
            STREAM_CLIENT_CONNECT,
89
            $context
90 25
        );
91
92 25
        if (!$this->stream) {
93
            throw new IOException(sprintf(
94
                'Unable to connect to "%s:%d" using stream socket: %s',
95
                $host,
96
                $port,
97
                $errstr
98
            ));
99
        }
100
101
        //$this->logger->debug(sprintf('Connection established'), [
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...
102
        //    'host' => $host,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
103
        //    'port' => $port,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% 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...
104
        //]);
105
106 25
        $this->buffer = '';
107
108 25
        $this->applyReadingTimeout();
109
110 25
        return $this;
111
    }
112
113
    /**
114
     * @param float|int $timeout
115
     *
116
     * @return $this
117
     */
118 25
    private function setReadingTimeout($timeout)
119
    {
120 25
        $this->readingTimeout = $timeout;
121
122 25
        if ($this->stream) {
123
            $this->applyReadingTimeout();
124
        }
125
126 25
        return $this;
127
    }
128
129
    /**
130
     * @param float|int $connectionTimeout
131
     *
132
     * @return $this
133
     */
134 25
    private function setConnectionTimeout($connectionTimeout)
135
    {
136 25
        $this->connectionTimeout = $connectionTimeout;
137
138 25
        return $this;
139
    }
140
141
    /**
142
     * @return $this
143
     */
144 12
    public function close()
145
    {
146 12
        if (!$this->stream) {
147
            return $this;
148
        }
149
150 12
        fclose($this->stream);
151
152 12
        $this->stream = null;
153
154
        //$this->logger->debug('Connection closed');
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% 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...
155
156 12
        return $this;
157
    }
158
159
    /**
160
     * @param string   $data
161
     * @param int|null $length
162
     *
163
     * @return $this
164
     *
165
     * @throws IOException
166
     */
167 22
    public function write($data, $length = null)
168
    {
169 22
        if ($this->stream === null) {
170
            throw new IOClosedException('Connection is not open');
171
        }
172
173 22
        if ($length === null) {
174 22
            $length = strlen($data);
175 22
        }
176
177 22
        $this->logger->debug(new ReadableBinaryData('Sending', $data));
178
179 22
        while ($length > 0) {
180 22
            if ($this->isOpen()) {
181 1
                throw new IOClosedException('Connection is closed');
182
            }
183
184 22
            $written = @fwrite($this->stream, $data, $length);
185 22
            if ($written === false) {
186
                throw new IOException('An error occur while writing to socket');
187
            }
188
189 22
            $length -= $written;
190 22
            $data = $length ? substr($data, $written, $length) : '';
191 22
        }
192
193 22
        return $this;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 23
    public function peek($length, $blocking = true)
200
    {
201 23
        $received = strlen($this->buffer);
202
203 23
        if ($received >= $length) {
204 2
            return $this->buffer;
205
        }
206
207 23
        $this->buffer .= $this->recv($length - $received, $blocking);
208
209 23
        if (strlen($this->buffer) >= $length) {
210 22
            return $this->buffer;
211
        }
212
213 4
        return null;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 22 View Code Duplication
    public function read($length, $blocking = true)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
    {
221 22
        if (!$this->peek($length, $blocking)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->peek($length, $blocking) of type string|null is loosely compared to false; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
222 3
            return null;
223
        }
224
225 21
        $data = substr($this->buffer, 0, $length);
226
227 21
        $this->buffer = substr($this->buffer, $length, strlen($this->buffer) - $length);
228
229 21
        return $data;
230
    }
231
232
    /**
233
     * @param int  $length
234
     * @param bool $blocking
235
     *
236
     * @return string
237
     *
238
     * @throws IOException
239
     */
240 23
    private function recv($length, $blocking)
241
    {
242 23
        if ($this->stream === null) {
243
            throw new IOClosedException('Connection is not open');
244
        }
245
246 23
        if ($this->isOpen()) {
247 1
            throw new IOClosedException('Connection is closed');
248
        }
249
250 23
        if ($this->readAheadSize) {
251
            $meta = stream_get_meta_data($this->stream);
252
253
            if ($length < $meta['unread_bytes']) {
254
                $length = min($this->readAheadSize, $meta['unread_bytes']);
255
            }
256
        }
257
258 23
        stream_set_blocking($this->stream, $blocking);
259
260 23
        if (($received = fread($this->stream, $length)) === false) {
261
            throw new IOException('An error occur while reading from the socket');
262
        }
263
264
        //if ($received) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% 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...
265
        //    $this->logger->debug(new ReadableBinaryData('Receive', $received));
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
266
        //}
267
268 23
        return $received;
269
    }
270
271
    /**
272
     * Apply reading timeout to active stream.
273
     */
274 25
    private function applyReadingTimeout()
275
    {
276 25
        list($sec, $usec) = explode('|', number_format($this->readingTimeout, 6, '|', ''));
277
278 25
        stream_set_timeout($this->stream, $sec, $usec);
279 25
    }
280
281
    /**
282
     * @return bool
283
     */
284 25
    public function isOpen()
285
    {
286 25
        return is_resource($this->stream) && feof($this->stream);
287
    }
288
289
    /**
290
     * @param array $parameters
291
     *
292
     * @return resource
293
     */
294 25
    protected function createStreamContext(array $parameters)
295
    {
296 25
        $context = stream_context_create();
297
298 25
        if (isset($parameters['certfile'])) {
299 1
            stream_context_set_option($context, 'ssl', 'local_cert', $parameters['certfile']);
300 1
        }
301
302 25
        if (isset($parameters['keyfile'])) {
303
            stream_context_set_option($context, 'ssl', 'local_pk', $parameters['keyfile']);
304
        }
305
306 25
        if (isset($parameters['cacertfile'])) {
307
            stream_context_set_option($context, 'ssl', 'cafile', $parameters['cacertfile']);
308
        }
309
310 25
        if (isset($parameters['passphrase'])) {
311
            stream_context_set_option($context, 'ssl', 'passphrase', $parameters['passphrase']);
312
        }
313
314 25
        if (isset($parameters['verify'])) {
315 1
            stream_context_set_option($context, 'ssl', 'verify_peer', (bool) $parameters['verify']);
316 1
        }
317
318 25
        if (isset($parameters['allow_self_signed'])) {
319 1
            stream_context_set_option($context, 'ssl', 'allow_self_signed', (bool) $parameters['allow_self_signed']);
320 1
        }
321
322 25
        return $context;
323
    }
324
}
325