Passed
Pull Request — master (#14)
by Timon
02:48
created

Socket   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 60.64%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 2
dl 0
loc 267
ccs 57
cts 94
cp 0.6064
rs 8.2857
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
A openStream() 0 12 1
A __toString() 0 8 2
A detach() 0 6 1
A getSize() 0 4 1
A tell() 0 4 1
A eof() 0 4 1
A isSeekable() 0 4 1
A seek() 0 4 1
A rewind() 0 4 1
A isWritable() 0 4 2
A close() 0 5 1
B write() 0 29 6
A isReadable() 0 4 2
C read() 0 34 8
A getContent() 0 15 4
A getContents() 0 11 3
A getMetadata() 0 6 1
1
<?php
2
declare(strict_types=1);
3
4
namespace TBolier\RethinkQL\Connection\Socket;
5
6
use Psr\Http\Message\StreamInterface;
7
use TBolier\RethinkQL\Connection\OptionsInterface;
8
9
class Socket implements StreamInterface
10
{
11
    /**
12
     * @var resource
13
     */
14
    private $stream;
15
16
    /**
17
     * @var int
18
     */
19
    private $tellPos = 0;
20
21
    /**
22
     * @var bool
23
     */
24
    private $nullTerminated = false;
25
26
    /**
27
     * @param OptionsInterface $options
28
     */
29 14
    public function __construct(OptionsInterface $options)
30
    {
31 14
        $this->openStream(
32 14
            ($options->isSsl() ? 'ssl' : 'tcp') . '://' . $options->getHostname() . ':' . $options->getPort(),
33 14
            $options->getTimeout(),
34 14
            $options->getTimeoutStream()
35
        );
36 14
    }
37
38
    /**
39
     * @param string $remote_socket
40
     * @param float $timeout
41
     * @param int $timeoutStream
42
     */
43 14
    private function openStream(string $remote_socket, float $timeout, int $timeoutStream): void
44
    {
45 14
        $this->stream = stream_socket_client(
46 14
            $remote_socket,
47 14
            $errno,
48 14
            $errstr,
49 14
            $timeout,
50 14
            STREAM_CLIENT_CONNECT
51
        );
52
53 14
        stream_set_timeout($this->stream, $timeoutStream);
54 14
    }
55
56
    /**
57
     * @inheritdoc
58
     */
59
    public function __toString()
60
    {
61
        try {
62
            return $this->getContents();
63
        } catch (\Exception $e) {
64
            return '';
65
        }
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71
    public function close()
72
    {
73
        fclose($this->stream);
74
        $this->stream = null;
75
    }
76
77
    /**
78
     * @inheritdoc
79
     */
80
    public function detach()
81
    {
82
        $this->close();
83
84
        return null;
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90
    public function getSize()
91
    {
92
        return null;
93
    }
94
95
    /**
96
     * @inheritdoc
97
     */
98
    public function tell()
99
    {
100
        return $this->tellPos;
101
    }
102
103
    /**
104
     * @inheritdoc
105
     */
106 14
    public function eof()
107
    {
108 14
        return feof($this->stream);
109
    }
110
111
    /**
112
     * @inheritdoc
113
     */
114
    public function isSeekable()
115
    {
116
        return false;
117
    }
118
119
    /**
120
     * @inheritdoc
121
     */
122
    public function seek($offset, $whence = SEEK_SET)
123
    {
124
        throw new Exception('Cannot seek a socket stream');
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130
    public function rewind()
131
    {
132
        $this->seek(0);
133
    }
134
135
    /**
136
     * Returns whether or not the stream is writable.
137
     *
138
     * @return bool
139
     */
140 14
    public function isWritable()
141
    {
142 14
        return $this->stream ? true : false;
143
    }
144
145
    /**
146
     * Write data to the stream.
147
     *
148
     * @param string $string The string that is to be written.
149
     * @return int Returns the number of bytes written to the stream.
150
     * @throws \RuntimeException on failure.
151
     */
152 14
    public function write($string)
153
    {
154 14
        if (!$this->isWritable()) {
155
            throw new \RuntimeException('The stream is not writable.');
156
        }
157
158 14
        $writeLength = \strlen($string);
159 14
        $this->tellPos = $writeLength;
160
161 14
        $bytesWritten = 0;
162 14
        while ($bytesWritten < $writeLength) {
163 14
            $result = fwrite($this->stream, substr($string, $bytesWritten));
164 14
            if ($result === false || $result === 0) {
165
                $this->detach();
166
                if ($this->getMetadata('timed_out')) {
167
                    throw new Exception(
168
                        'Timed out while writing to socket. Disconnected. '
169
                        . 'Call setTimeout(seconds) on the connection to change '
170
                        . 'the timeout.'
171
                    );
172
                }
173
                throw new Exception('Unable to write to socket. Disconnected.');
174
            }
175 14
            $bytesWritten += $result;
176 14
            $this->tellPos -= $bytesWritten;
177
        }
178
179 14
        return $bytesWritten;
180
    }
181
182
    /**
183
     * Returns whether or not the stream is readable.
184
     *
185
     * @return bool
186
     */
187
    public function isReadable()
188
    {
189
        return $this->stream ? true : false;
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 14
    public function read($length)
196
    {
197 14
        $this->tellPos = 0;
198 14
        $s = '';
199
200 14
        if ($length === -1) {
201 14
            while (true) {
202 14
                $char = $this->getContent(1);
203
204
                // skip initial null-terminated byte
205 14
                if ($s === '' && $char === \chr(0)) {
206
                    continue;
207
                }
208
209
                // reach a null-terminated byte, stop the stream.
210 14
                if ($char === '' || $char === \chr(0)) {
211 14
                    $this->nullTerminated = true;
212 14
                    break;
213
                }
214
215 14
                $s .= $char;
216 14
                $this->tellPos += $length - \strlen($s);
217
            }
218
219 14
            return $s;
220
        }
221
222 14
        while (\strlen($s) < $length) {
223 14
            $s .= $this->getContent($length - \strlen($s));
224 14
            $this->tellPos += $length - \strlen($s);
225
        }
226
227 14
        return $s;
228
    }
229
230
    /**
231
     * @param $length
232
     * @return string
233
     * @throws Exception
234
     */
235 14
    private function getContent($length): string
236
    {
237 14
        $string = stream_get_contents($this->stream, $length);
238 14
        if ($string === false || $this->eof()) {
239
            if ($this->getMetadata('timed_out')) {
240
                throw new Exception(
241
                    'Timed out while reading from socket. Disconnected. '
242
                    . 'Call setTimeout(seconds) on the connection to change '
243
                    . 'the timeout.'
244
                );
245
            }
246
        }
247
248 14
        return $string;
249
    }
250
251
    /**
252
     * @inheritdoc
253
     */
254 14
    public function getContents()
255
    {
256 14
        $result = '';
257 14
        while (!$this->eof() && !$this->nullTerminated) {
258 14
            $result .= $this->read(-1);
259
        }
260
261 14
        $this->nullTerminated = false;
262
263 14
        return $result;
264
    }
265
266
    /**
267
     * @inheritdoc
268
     */
269
    public function getMetadata($key = null)
270
    {
271
        $meta = stream_get_meta_data($this->stream);
272
273
        return $meta[$key] ?? $meta;
274
    }
275
}
276