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\ConnectionException; |
17
|
|
|
use Tarantool\Client\IProto; |
18
|
|
|
use Tarantool\Client\Packer\PackUtils; |
19
|
|
|
|
20
|
|
|
final class StreamConnection implements Connection |
21
|
|
|
{ |
22
|
|
|
private const DEFAULT_URI = 'tcp://127.0.0.1:3301'; |
23
|
|
|
|
24
|
|
|
private $uri; |
25
|
|
|
|
26
|
|
|
private $options = [ |
27
|
|
|
'connect_timeout' => 5, |
28
|
|
|
'socket_timeout' => 5, |
29
|
|
|
'tcp_nodelay' => true, |
30
|
|
|
]; |
31
|
|
|
|
32
|
|
|
private $stream; |
33
|
|
|
|
34
|
126 |
|
public function __construct(string $uri = self::DEFAULT_URI, array $options = []) |
35
|
|
|
{ |
36
|
126 |
|
$this->uri = $uri; |
37
|
|
|
|
38
|
126 |
|
if ($options) { |
|
|
|
|
39
|
4 |
|
$this->options = $options + $this->options; |
40
|
|
|
} |
41
|
126 |
|
} |
42
|
|
|
|
43
|
126 |
|
public function open() : string |
44
|
|
|
{ |
45
|
126 |
|
$this->close(); |
46
|
|
|
|
47
|
126 |
|
$stream = @\stream_socket_client( |
48
|
126 |
|
$this->uri, |
49
|
126 |
|
$errorCode, |
50
|
126 |
|
$errorMessage, |
51
|
126 |
|
(float) $this->options['connect_timeout'], |
52
|
126 |
|
\STREAM_CLIENT_CONNECT, |
53
|
126 |
|
\stream_context_create(['socket' => ['tcp_nodelay' => (bool) $this->options['tcp_nodelay']]]) |
54
|
|
|
); |
55
|
|
|
|
56
|
126 |
|
if (false === $stream) { |
57
|
3 |
|
throw new ConnectionException(\sprintf('Unable to connect to %s: %s.', $this->uri, $errorMessage)); |
58
|
|
|
} |
59
|
|
|
|
60
|
123 |
|
$this->stream = $stream; |
61
|
123 |
|
\stream_set_timeout($this->stream, $this->options['socket_timeout']); |
62
|
|
|
|
63
|
123 |
|
$greeting = $this->read(IProto::GREETING_SIZE, 'Unable to read greeting.'); |
64
|
|
|
|
65
|
120 |
|
return IProto::parseGreeting($greeting); |
66
|
|
|
} |
67
|
|
|
|
68
|
126 |
|
public function close() : void |
69
|
|
|
{ |
70
|
126 |
|
if ($this->stream) { |
71
|
2 |
|
\fclose($this->stream); |
72
|
2 |
|
$this->stream = null; |
73
|
|
|
} |
74
|
126 |
|
} |
75
|
|
|
|
76
|
106 |
|
public function isClosed() : bool |
77
|
|
|
{ |
78
|
106 |
|
return !\is_resource($this->stream); |
79
|
|
|
} |
80
|
|
|
|
81
|
105 |
|
public function send(string $data) : string |
82
|
|
|
{ |
83
|
105 |
|
if (!\fwrite($this->stream, $data)) { |
84
|
|
|
throw new ConnectionException('Unable to write request.'); |
85
|
|
|
} |
86
|
|
|
|
87
|
105 |
|
$length = $this->read(IProto::LENGTH_SIZE, 'Unable to read response length.'); |
88
|
103 |
|
$length = PackUtils::unpackLength($length); |
89
|
|
|
|
90
|
103 |
|
return $this->read($length, 'Unable to read response.'); |
91
|
|
|
} |
92
|
|
|
|
93
|
123 |
|
private function read(int $length, string $errorMessage) : string |
94
|
|
|
{ |
95
|
123 |
|
if ($data = \stream_get_contents($this->stream, $length)) { |
96
|
120 |
|
return $data; |
97
|
|
|
} |
98
|
|
|
|
99
|
7 |
|
$meta = \stream_get_meta_data($this->stream); |
100
|
7 |
|
throw new ConnectionException($meta['timed_out'] ? 'Read timed out.' : $errorMessage); |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
|
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.