Completed
Pull Request — master (#37)
by Eugene
03:10
created

Stream::send()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2.0185
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Tarantool Client package.
7
 *
8
 * (c) Eugene Leonovich <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Tarantool\Client\Connection;
15
16
use Tarantool\Client\Exception\CommunicationFailed;
17
use Tarantool\Client\Exception\ConnectionFailed;
18
use Tarantool\Client\IProto;
19
use Tarantool\Client\Packer\PackUtils;
20
21
final class Stream implements Connection
22
{
23
    private const DEFAULT_URI = 'tcp://127.0.0.1:3301';
24
25
    private $uri;
26
27
    private $options = [
28
        'connect_timeout' => 5,
29
        'socket_timeout' => 5,
30
        'tcp_nodelay' => true,
31
    ];
32
33
    private $stream;
34
35 256
    public function __construct(string $uri = self::DEFAULT_URI, array $options = [])
36
    {
37 256
        $this->uri = $uri;
38
39 256
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
40 8
            $this->options = $options + $this->options;
41
        }
42 256
    }
43
44 256
    public function open() : string
45
    {
46 256
        $this->close();
47
48 256
        $stream = @\stream_socket_client(
49 256
            $this->uri,
50 256
            $errorCode,
51 256
            $errorMessage,
52 256
            (float) $this->options['connect_timeout'],
53 256
            \STREAM_CLIENT_CONNECT,
54 256
            \stream_context_create(['socket' => ['tcp_nodelay' => (bool) $this->options['tcp_nodelay']]])
55
        );
56
57 256
        if (false === $stream) {
58 6
            throw ConnectionFailed::fromUriAndReason($this->uri, $errorMessage);
59
        }
60
61 250
        $this->stream = $stream;
62 250
        \stream_set_timeout($this->stream, $this->options['socket_timeout']);
63
64 250
        $greeting = $this->read(IProto::GREETING_SIZE, 'Unable to read greeting.');
65
66 244
        return IProto::parseGreeting($greeting);
67
    }
68
69 256
    public function close() : void
70
    {
71 256
        if ($this->stream) {
72 4
            \fclose($this->stream);
73 4
            $this->stream = null;
74
        }
75 256
    }
76
77 216
    public function isClosed() : bool
78
    {
79 216
        return !\is_resource($this->stream);
80
    }
81
82 214
    public function send(string $data) : string
83
    {
84 214
        if (!\fwrite($this->stream, $data)) {
85
            throw new CommunicationFailed('Unable to write request.');
86
        }
87
88 214
        $length = $this->read(IProto::LENGTH_SIZE, 'Unable to read response length.');
89 210
        $length = PackUtils::unpackLength($length);
90
91 210
        return $this->read($length, 'Unable to read response.');
92
    }
93
94 250
    private function read(int $length, string $errorMessage) : string
95
    {
96 250
        if ($data = \stream_get_contents($this->stream, $length)) {
97 244
            return $data;
98
        }
99
100 14
        $meta = \stream_get_meta_data($this->stream);
101 14
        throw new CommunicationFailed($meta['timed_out'] ? 'Read timed out.' : $errorMessage);
102
    }
103
}
104