Passed
Pull Request — master (#11)
by Michel
02:36
created

Socket::getSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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