Passed
Push — master ( f25e4d...a2be21 )
by Benjamin
01:49
created

StreamSocketClient   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Test Coverage

Coverage 79.59%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 26
eloc 88
c 5
b 0
f 0
dl 0
loc 293
ccs 78
cts 98
cp 0.7959
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A close() 0 8 2
A read() 0 3 1
A buildSocket() 0 35 3
A setConnectTimeout() 0 7 2
A getConnectTimeout() 0 3 1
A isClosed() 0 3 1
A getContext() 0 3 1
A setContext() 0 7 2
A __destruct() 0 3 1
A __construct() 0 6 1
A getSocket() 0 8 2
A write() 0 34 6
A initSocket() 0 29 3
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 45
    public function __construct($scheme, $host, $port, array $context = array())
68
    {
69 45
        $this->scheme = $scheme;
70 45
        $this->host = $host;
71 45
        $this->port = $port;
72 45
        $this->context = $context;
73 45
    }
74
75
    /**
76
     * Destructor, closes socket if possible
77
     */
78 33
    public function __destruct()
79
    {
80 33
        $this->close();
81 33
    }
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 15
    private function buildSocket()
139
    {
140 15
        $socketDescriptor = sprintf(
141 15
            "%s://%s:%d",
142 15
            $this->scheme,
143 15
            $this->host,
144 15
            $this->port
145
        );
146
147 15
        $socket = @stream_socket_client(
148 15
            $socketDescriptor,
149 15
            $errNo,
150 15
            $errStr,
151 15
            $this->connectTimeout,
152 15
            \STREAM_CLIENT_CONNECT,
153 15
            stream_context_create($this->context)
154
        );
155
156 15
        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 1
                    $errNo
163
                )
164
            );
165
        }
166
167
        // set non-blocking for UDP
168 14
        if (strcasecmp("udp", $this->scheme) == 0) {
169 2
            stream_set_blocking($socket, 0);
170
        }
171
172 14
        return $socket;
173
    }
174
175
    /**
176
     * Returns raw-socket-resource
177
     *
178
     * @return resource
179
     */
180 15
    public function getSocket()
181
    {
182
        // lazy initializing of socket-descriptor
183 15
        if (!$this->socket) {
184 15
            $this->socket = $this->buildSocket();
185
        }
186
187 14
        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
        }
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 33
    public function close()
252
    {
253 33
        if (!is_resource($this->socket)) {
254 20
            return;
255
        }
256
257 14
        fclose($this->socket);
258 14
        $this->socket = null;
259 14
    }
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