Issues (57)

src/Connection/Socket/Socket.php (1 issue)

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