Completed
Push — master ( 73f6e1...42bf17 )
by Sergey
04:22
created

StreamIO::peek()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.4285
cc 3
eloc 8
nc 3
nop 2
crap 3
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 = 13000)
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 25
        $this->blocking = null;
0 ignored issues
show
Bug introduced by
The property blocking does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
67
68
        //$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...
69
        //    '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...
70
        //    '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...
71
        //    '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...
72
        //    '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...
73
        //]);
74
75 25
        $context = $this->createStreamContext($parameters);
76
77 25
        if (isset($parameters['connection_timeout'])) {
78
            $this->setConnectionTimeout($parameters['connection_timeout']);
79
        }
80
81 25
        if (isset($parameters['timeout'])) {
82
            $this->setReadingTimeout($parameters['timeout']);
83
        }
84
85 25
        $this->stream = @stream_socket_client(
86 25
            sprintf('%s://%s:%d', $protocol, $host, $port),
87 25
            $errno,
88 25
            $errstr,
89 25
            $this->connectionTimeout,
90 25
            STREAM_CLIENT_CONNECT,
91
            $context
92 25
        );
93
94 25
        if (!$this->stream) {
95
            throw new IOException(sprintf(
96
                'Unable to connect to "%s:%d" using stream socket: %s',
97
                $host,
98
                $port,
99
                $errstr
100
            ));
101
        }
102
103
        //$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...
104
        //    '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...
105
        //    '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...
106
        //]);
107
108 25
        $this->buffer = '';
109
110 25
        $this->applyReadingTimeout();
111
112 25
        return $this;
113
    }
114
115
    /**
116
     * @param float|int $timeout
117
     *
118
     * @return $this
119
     */
120 25
    private function setReadingTimeout($timeout)
121
    {
122 25
        $this->readingTimeout = $timeout;
123
124 25
        if ($this->stream) {
125
            $this->applyReadingTimeout();
126
        }
127
128 25
        return $this;
129
    }
130
131
    /**
132
     * @param float|int $connectionTimeout
133
     *
134
     * @return $this
135
     */
136 25
    private function setConnectionTimeout($connectionTimeout)
137
    {
138 25
        $this->connectionTimeout = $connectionTimeout;
139
140 25
        return $this;
141
    }
142
143
    /**
144
     * @return $this
145
     */
146 12
    public function close()
147
    {
148 12
        if (!$this->stream) {
149
            return $this;
150
        }
151
152 12
        fclose($this->stream);
153
154 12
        $this->stream = null;
155
156
        //$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...
157
158 12
        return $this;
159
    }
160
161
    /**
162
     * @param string   $data
163
     * @param int|null $length
164
     *
165
     * @return $this
166
     *
167
     * @throws IOException
168
     */
169 22
    public function write($data, $length = null)
170
    {
171 22
        if ($this->stream === null) {
172
            throw new IOClosedException('Connection is not open');
173
        }
174
175 22
        if ($length === null) {
176 22
            $length = strlen($data);
177 22
        }
178
179
        //$this->logger->debug(new ReadableBinaryData('Sending', $data));
0 ignored issues
show
Unused Code Comprehensibility introduced by
71% 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...
180
181 22
        while ($length > 0) {
182 22
            if ($this->isOpen()) {
183 1
                throw new IOClosedException('Connection is closed');
184
            }
185
186 22
            $written = @fwrite($this->stream, $data, $length);
187 22
            if ($written === false) {
188
                throw new IOException('An error occur while writing to socket');
189
            }
190
191 22
            $length -= $written;
192 22
            $data = $length ? substr($data, $written, $length) : '';
193 22
        }
194
195 22
        return $this;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 23
    public function peek($length, $blocking = true)
202
    {
203 23
        $received = strlen($this->buffer);
204
205 23
        if ($received >= $length) {
206 2
            return $this->buffer;
207
        }
208
209 23
        $this->buffer .= $this->recv($length - $received, $blocking);
210
211 23
        if (strlen($this->buffer) >= $length) {
212 22
            return $this->buffer;
213
        }
214
215 4
        return null;
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 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...
222
    {
223 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...
224 3
            return null;
225
        }
226
227 21
        $data = substr($this->buffer, 0, $length);
228
229 21
        $this->buffer = substr($this->buffer, $length, strlen($this->buffer) - $length);
230
231 21
        return $data;
232
    }
233
234
    /**
235
     * @param int  $length
236
     * @param bool $blocking
237
     *
238
     * @return string
239
     *
240
     * @throws IOException
241
     */
242 23
    private function recv($length, $blocking)
243
    {
244 23
        if ($this->stream === null) {
245
            throw new IOClosedException('Connection is not open');
246
        }
247
248 23
        if ($this->isOpen()) {
249 1
            throw new IOClosedException('Connection is closed');
250
        }
251
252 23
        if ($this->readAheadSize) {
253
            $meta = stream_get_meta_data($this->stream);
254
255
            if ($length < $meta['unread_bytes']) {
256
                $length = min($this->readAheadSize, $meta['unread_bytes']);
257
            }
258
        }
259
260 23
        stream_set_blocking($this->stream, $blocking);
261
262 23
        if (($received = fread($this->stream, $length)) === false) {
263
            throw new IOException('An error occur while reading from the socket');
264
        }
265
266 23
        return $received;
267
    }
268
269
    /**
270
     * Apply reading timeout to active stream.
271
     */
272 25
    private function applyReadingTimeout()
273
    {
274 25
        list($sec, $usec) = explode('|', number_format($this->readingTimeout, 6, '|', ''));
275
276 25
        stream_set_timeout($this->stream, $sec, $usec);
277 25
    }
278
279
    /**
280
     * @return bool
281
     */
282 25
    public function isOpen()
283
    {
284 25
        return is_resource($this->stream) && feof($this->stream);
285
    }
286
287
    /**
288
     * @param array $parameters
289
     *
290
     * @return resource
291
     */
292 25
    protected function createStreamContext(array $parameters)
293
    {
294 25
        $context = stream_context_create();
295
296 25
        if (isset($parameters['certfile'])) {
297 1
            stream_context_set_option($context, 'ssl', 'local_cert', $parameters['certfile']);
298 1
        }
299
300 25
        if (isset($parameters['keyfile'])) {
301
            stream_context_set_option($context, 'ssl', 'local_pk', $parameters['keyfile']);
302
        }
303
304 25
        if (isset($parameters['cacertfile'])) {
305
            stream_context_set_option($context, 'ssl', 'cafile', $parameters['cacertfile']);
306
        }
307
308 25
        if (isset($parameters['passphrase'])) {
309
            stream_context_set_option($context, 'ssl', 'passphrase', $parameters['passphrase']);
310
        }
311
312 25
        if (isset($parameters['verify'])) {
313 1
            stream_context_set_option($context, 'ssl', 'verify_peer', (bool) $parameters['verify']);
314 1
        }
315
316 25
        if (isset($parameters['allow_self_signed'])) {
317 1
            stream_context_set_option($context, 'ssl', 'allow_self_signed', (bool) $parameters['allow_self_signed']);
318 1
        }
319
320 25
        return $context;
321
    }
322
}
323