Completed
Pull Request — master (#84)
by Pavel
05:39
created

StreamSocketClient   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 92.54%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 0
dl 0
loc 255
ccs 62
cts 67
cp 0.9254
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A __destruct() 0 4 1
B initSocket() 0 30 3
A getSocket() 0 9 2
A read() 0 4 1
A close() 0 9 2
A getConnectTimeout() 0 4 1
A setConnectTimeout() 0 4 1
B buildSocket() 0 36 3
B write() 0 33 4
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
16
/**
17
 * StreamSocketClient is a very simple OO-Wrapper around the PHP
18
 * stream_socket-library and some specific stream-functions like
19
 * fwrite, etc.
20
 *
21
 * @author Benjamin Zikarsky <[email protected]>
22
 */
23
class StreamSocketClient
24
{
25
    /**
26
     * @deprecated deprecated since v1.4.0
27
     */
28
    const SOCKET_TIMEOUT = 30;
29
30
    /**
31
     * @var string
32
     */
33
    protected $host;
34
35
    /**
36
     * @var integer
37
     */
38
    protected $port;
39
40
    /**
41
     * @var string
42
     */
43
    protected $scheme;
44
45
    /**
46
     * @var array
47
     */
48
    protected $context;
49
50
    /**
51
     * @var resource
52
     */
53
    protected $socket;
54
55
    /**
56
     * @var int
57
     */
58
    protected $connectTimeout = self::SOCKET_TIMEOUT;
59
60
    /**
61
     * @param string  $scheme
62
     * @param string  $host
63
     * @param integer $port
64
     * @param array   $context
65
     */
66 41
    public function __construct($scheme, $host, $port, array $context = array())
67
    {
68 41
        $this->scheme = $scheme;
69 41
        $this->host = $host;
70 41
        $this->port = $port;
71 41
        $this->context = $context;
72 41
    }
73
74
    /**
75
     * Destructor, closes socket if possible
76
     */
77 41
    public function __destruct()
78
    {
79 41
        $this->close();
80 41
    }
81
82
    /**
83
     * Initializes socket-client
84
     *
85
     * @deprecated deprecated since v1.4.0
86
     *
87
     * @param string  $scheme  like "udp" or "tcp"
88
     * @param string  $host
89
     * @param integer $port
90
     * @param array   $context
91
     *
92
     * @return resource
93
     *
94
     * @throws RuntimeException on connection-failure
95
     */
96
    protected static function initSocket($scheme, $host, $port, array $context)
97
    {
98
        $socketDescriptor = sprintf("%s://%s:%d", $scheme, $host, $port);
99
        $socket = @stream_socket_client(
100
            $socketDescriptor,
101
            $errNo,
102
            $errStr,
103
            static::SOCKET_TIMEOUT,
104
            \STREAM_CLIENT_CONNECT,
105
            stream_context_create($context)
106
        );
107
108
        if ($socket === false) {
109
            throw new RuntimeException(
110
                sprintf(
111
                    "Failed to create socket-client for %s: %s (%s)",
112
                    $socketDescriptor,
113
                    $errStr,
114
                    $errNo
115
                )
116
            );
117
        }
118
119
        // set non-blocking for UDP
120
        if (strcasecmp("udp", $scheme) == 0) {
121
            stream_set_blocking($socket, 0);
122
        }
123
124
        return $socket;
125
    }
126
127
128
    /**
129
     * Internal function mimicking the behaviour of static::initSocket
130
     * which will get new functionality instead of the deprecated
131
     * "factory"
132
     *
133
     * @return resource
134
     *
135
     * @throws RuntimeException on connection-failure
136
     */
137 14
    private function buildSocket()
138
    {
139 14
        $socketDescriptor = sprintf(
140 14
            "%s://%s:%d",
141 14
            $this->scheme,
142 14
            $this->host,
143 14
            $this->port
144
        );
145
146 14
        $socket = @stream_socket_client(
147
            $socketDescriptor,
148
            $errNo,
149
            $errStr,
150 14
            $this->connectTimeout,
151 14
            \STREAM_CLIENT_CONNECT,
152 14
            stream_context_create($this->context)
153
        );
154
155 14
        if ($socket === false) {
156 1
            throw new RuntimeException(
157 1
                sprintf(
158 1
                    "Failed to create socket-client for %s: %s (%s)",
159
                    $socketDescriptor,
160
                    $errStr,
161 1
                    $errNo
162
                )
163
            );
164
        }
165
166
        // set non-blocking for UDP
167 13
        if (strcasecmp("udp", $this->scheme) == 0) {
168 3
            stream_set_blocking($socket, 0);
169
        }
170
171 13
        return $socket;
172
    }
173
174
    /**
175
     * Returns raw-socket-resource
176
     *
177
     * @return resource
178
     */
179 14
    public function getSocket()
180
    {
181
        // lazy initializing of socket-descriptor
182 14
        if (!$this->socket) {
183 14
            $this->socket = $this->buildSocket();
184
        }
185
186 13
        return $this->socket;
187
    }
188
189
    /**
190
     * Writes a given string to the socket and returns the
191
     * number of written bytes
192
     *
193
     * @param string $buffer
194
     *
195
     * @return int
196
     *
197
     * @throws RuntimeException on write-failure
198
     */
199 8
    public function write($buffer)
200
    {
201 8
        $buffer = (string) $buffer;
202 8
        $bufLen = strlen($buffer);
203
204 8
        $socket = $this->getSocket();
205 8
        $written = 0;
206
207 8
        while ($written < $bufLen) {
208
            // PHP's fwrite does not behave nice in regards to errors, so we wrap
209
            // it with a temporary error handler and treat every warning/notice as
210
            // a error
211 8
            $failed = false;
212 8
            $errorMessage = "Failed to write to socket";
213 8
            set_error_handler(function ($errno, $errstr) use (&$failed, &$errorMessage) {
214 1
                $failed = true;
215 1
                $errorMessage .= ": $errstr ($errno)";
216 8
            });
217 8
            $byteCount = fwrite($socket, substr($buffer, $written));
218 8
            restore_error_handler();
219
220
221 8
            if ($failed || $byteCount === false) {
222 1
                throw new \RuntimeException($errorMessage);
223
            }
224
225
226 8
            $written += $byteCount;
227
        }
228
229
230 8
        return $written;
231
    }
232
233
    /**
234
     * Reads a given number of bytes from the socket
235
     *
236
     * @param integer $byteCount
237
     *
238
     * @return string
239
     */
240 2
    public function read($byteCount)
241
    {
242 2
        return fread($this->getSocket(), $byteCount);
243
    }
244
245
    /**
246
     * Closes underlying socket explicitly
247
     */
248 41
    public function close()
249
    {
250 41
        if (!is_resource($this->socket)) {
251 29
            return;
252
        }
253
254 13
        fclose($this->socket);
255 13
        $this->socket = null;
256 13
    }
257
258
    /**
259
     * Returns the current connect-timeout
260
     *
261
     * @return int
262
     */
263 1
    public function getConnectTimeout()
264
    {
265 1
        return $this->connectTimeout;
266
    }
267
268
    /**
269
     * Sets the connect-timeout
270
     *
271
     * @param int $timeout
272
     */
273 1
    public function setConnectTimeout($timeout)
274
    {
275 1
        $this->connectTimeout = $timeout;
276 1
    }
277
}
278