Passed
Push — master ( 4502a3...14b87a )
by Benjamin
05:57 queued 03:52
created

StreamSocketClient::getSocket()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
/*
4
 * This file is part of the php-gelf package.
5
 *
6
 * (c) Benjamin Zikarsky <http://benjamin-zikarsky.de>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Gelf\Transport;
13
14
use RuntimeException;
15
use ParagonIE\ConstantTime\Binary;
16
17
/**
18
 * StreamSocketClient is a very simple OO-Wrapper around the PHP
19
 * stream_socket-library and some specific stream-functions like
20
 * fwrite, etc.
21
 *
22
 * @author Benjamin Zikarsky <[email protected]>
23
 */
24
class StreamSocketClient
25
{
26
    /**
27
     * @deprecated deprecated since v1.4.0
28
     */
29
    const SOCKET_TIMEOUT = 30;
30
31
    /**
32
     * @var string
33
     */
34
    protected $host;
35
36
    /**
37
     * @var integer
38
     */
39
    protected $port;
40
41
    /**
42
     * @var string
43
     */
44
    protected $scheme;
45
46
    /**
47
     * @var array
48
     */
49
    protected $context;
50
51
    /**
52
     * @var resource
53
     */
54
    protected $socket;
55
56
    /**
57
     * @var int
58
     */
59
    protected $connectTimeout = self::SOCKET_TIMEOUT;
60
61
    /**
62
     * @param string  $scheme
63
     * @param string  $host
64
     * @param integer $port
65
     * @param array   $context
66
     */
67 49
    public function __construct($scheme, $host, $port, array $context = array())
68
    {
69 49
        $this->scheme = $scheme;
70 49
        $this->host = $host;
71 49
        $this->port = $port;
72 49
        $this->context = $context;
73 49
    }
74
75
    /**
76
     * Destructor, closes socket if possible
77
     */
78 37
    public function __destruct()
79
    {
80 37
        $this->close();
81 37
    }
82
83
    /**
84
     * Initializes socket-client
85
     *
86
     * @deprecated deprecated since v1.4.0
87
     *
88
     * @param string  $scheme  like "udp" or "tcp"
89
     * @param string  $host
90
     * @param integer $port
91
     * @param array   $context
92
     *
93
     * @return resource
94
     *
95
     * @throws RuntimeException on connection-failure
96
     */
97
    protected static function initSocket($scheme, $host, $port, array $context)
98
    {
99
        $socketDescriptor = sprintf("%s://%s:%d", $scheme, $host, $port);
100
        $socket = @stream_socket_client(
101
            $socketDescriptor,
102
            $errNo,
103
            $errStr,
104
            static::SOCKET_TIMEOUT,
0 ignored issues
show
Deprecated Code introduced by
The constant Gelf\Transport\StreamSocketClient::SOCKET_TIMEOUT has been deprecated: deprecated since v1.4.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

104
            /** @scrutinizer ignore-deprecated */ static::SOCKET_TIMEOUT,

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
105
            \STREAM_CLIENT_CONNECT,
106
            stream_context_create($context)
107
        );
108
109
        if ($socket === false) {
110
            throw new RuntimeException(
111
                sprintf(
112
                    "Failed to create socket-client for %s: %s (%s)",
113
                    $socketDescriptor,
114
                    $errStr,
115
                    $errNo
116
                )
117
            );
118
        }
119
120
        // set non-blocking for UDP
121
        if (strcasecmp("udp", $scheme) == 0) {
122
            stream_set_blocking($socket, 0);
123
        }
124
125
        return $socket;
126
    }
127
128
129
    /**
130
     * Internal function mimicking the behaviour of static::initSocket
131
     * which will get new functionality instead of the deprecated
132
     * "factory"
133
     *
134
     * @return resource
135
     *
136
     * @throws RuntimeException on connection-failure
137
     */
138 17
    private function buildSocket()
139
    {
140 17
        $socketDescriptor = sprintf(
141 17
            "%s://%s:%d",
142 17
            $this->scheme,
143 17
            $this->host,
144 17
            $this->port
145 17
        );
146
147 17
        $socket = @stream_socket_client(
148 17
            $socketDescriptor,
149 17
            $errNo,
150 17
            $errStr,
151 17
            $this->connectTimeout,
152 17
            \STREAM_CLIENT_CONNECT,
153 17
            stream_context_create($this->context)
154 17
        );
155
156 17
        if ($socket === false) {
157 1
            throw new RuntimeException(
158 1
                sprintf(
159 1
                    "Failed to create socket-client for %s: %s (%s)",
160 1
                    $socketDescriptor,
161 1
                    $errStr,
162
                    $errNo
163 1
                )
164 1
            );
165
        }
166
167
        // set non-blocking for UDP
168 16
        if (strcasecmp("udp", $this->scheme) == 0) {
169 3
            stream_set_blocking($socket, 0);
170 3
        }
171
172 16
        return $socket;
173
    }
174
175
    /**
176
     * Returns raw-socket-resource
177
     *
178
     * @return resource
179
     */
180 17
    public function getSocket()
181
    {
182
        // lazy initializing of socket-descriptor
183 17
        if (!$this->socket) {
184 17
            $this->socket = $this->buildSocket();
185 16
        }
186
187 16
        return $this->socket;
188
    }
189
190
    /**
191
     * Writes a given string to the socket and returns the
192
     * number of written bytes
193
     *
194
     * @param string $buffer
195
     *
196
     * @return int
197
     *
198
     * @throws RuntimeException on write-failure
199
     */
200 8
    public function write($buffer)
201
    {
202 8
        $buffer = (string) $buffer;
203 8
        $bufLen = Binary::safeStrlen($buffer);
204
205 8
        $socket = $this->getSocket();
206 8
        $written = 0;
207
208 8
        while ($written < $bufLen) {
209
            // PHP's fwrite does not behave nice in regards to errors, so we wrap
210
            // it with a temporary error handler and treat every warning/notice as
211
            // a error
212 8
            $failed = false;
213 8
            $errorMessage = "Failed to write to socket";
214 8
            set_error_handler(function ($errno, $errstr) use (&$failed, &$errorMessage) {
215 1
                $failed = true;
216 1
                $errorMessage .= ": $errstr ($errno)";
217 8
            });
218 8
            $byteCount = fwrite($socket, Binary::safeSubstr($buffer, $written));
219 8
            restore_error_handler();
220
221 8
            if ($byteCount === 0 && defined('HHVM_VERSION')) {
222
                $failed = true;
223
            }
224
225 8
            if ($failed || $byteCount === false) {
226 1
                throw new \RuntimeException($errorMessage);
227
            }
228
229 8
            $written += $byteCount;
230 8
        }
231
232
233 8
        return $written;
234
    }
235
236
    /**
237
     * Reads a given number of bytes from the socket
238
     *
239
     * @param integer $byteCount
240
     *
241
     * @return string
242
     */
243 2
    public function read($byteCount)
244
    {
245 2
        return fread($this->getSocket(), $byteCount);
246
    }
247
248
    /**
249
     * Closes underlying socket explicitly
250
     */
251 37
    public function close()
252
    {
253 37
        if (!is_resource($this->socket)) {
254 22
            return;
255
        }
256
257 16
        fclose($this->socket);
258 16
        $this->socket = null;
259 16
    }
260
261
    /**
262
     * Checks if the socket is closed
263
     *
264
     * @return bool
265
     */
266 6
    public function isClosed()
267
    {
268 6
        return $this->socket === null;
269
    }
270
271
    /**
272
     * Returns the current connect-timeout
273
     *
274
     * @return int
275
     */
276 1
    public function getConnectTimeout()
277
    {
278 1
        return $this->connectTimeout;
279
    }
280
281
    /**
282
     * Sets the connect-timeout
283
     *
284
     * @param int $timeout
285
     */
286 2
    public function setConnectTimeout($timeout)
287
    {
288 2
        if (!$this->isClosed()) {
289 1
            throw new \LogicException("Cannot change socket properties with an open connection");
290
        }
291
292 1
        $this->connectTimeout = $timeout;
293 1
    }
294
295
    /**
296
     * Returns the stream context
297
     *
298
     * @return array
299
     */
300 2
    public function getContext()
301
    {
302 2
        return $this->context;
303
    }
304
305
    /**
306
     * Sets the stream context
307
     *
308
     * @param array $context
309
     */
310 2
    public function setContext(array $context)
311
    {
312 2
        if (!$this->isClosed()) {
313 1
            throw new \LogicException("Cannot change socket properties with an open connection");
314
        }
315
316 1
        $this->context = $context;
317 1
    }
318
}
319