Completed
Push — master ( a552ca...886306 )
by Benjamin
02:11
created

StreamSocketClient::write()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6.027

Importance

Changes 0
Metric Value
dl 0
loc 35
ccs 20
cts 22
cp 0.9091
rs 8.7377
c 0
b 0
f 0
cc 6
nc 5
nop 1
crap 6.027
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 41
    public function __construct($scheme, $host, $port, array $context = array())
68
    {
69 41
        $this->scheme = $scheme;
70 41
        $this->host = $host;
71 41
        $this->port = $port;
72 41
        $this->context = $context;
73 41
    }
74
75
    /**
76
     * Destructor, closes socket if possible
77
     */
78 41
    public function __destruct()
79
    {
80 41
        $this->close();
81 41
    }
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,
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 14
    private function buildSocket()
139
    {
140 14
        $socketDescriptor = sprintf(
141 14
            "%s://%s:%d",
142 14
            $this->scheme,
143 14
            $this->host,
144 14
            $this->port
145 14
        );
146
147 14
        $socket = @stream_socket_client(
148 14
            $socketDescriptor,
149 14
            $errNo,
150 14
            $errStr,
151 14
            $this->connectTimeout,
152 14
            \STREAM_CLIENT_CONNECT,
153 14
            stream_context_create($this->context)
154 14
        );
155
156 14
        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 13
        if (strcasecmp("udp", $this->scheme) == 0) {
169 3
            stream_set_blocking($socket, 0);
170 3
        }
171
172 13
        return $socket;
173
    }
174
175
    /**
176
     * Returns raw-socket-resource
177
     *
178
     * @return resource
179
     */
180 14
    public function getSocket()
181
    {
182
        // lazy initializing of socket-descriptor
183 14
        if (!$this->socket) {
184 14
            $this->socket = $this->buildSocket();
185 13
        }
186
187 13
        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 41
    public function close()
252
    {
253 41
        if (!is_resource($this->socket)) {
254 29
            return;
255
        }
256
257 13
        fclose($this->socket);
258 13
        $this->socket = null;
259 13
    }
260
261
    /**
262
     * Returns the current connect-timeout
263
     *
264
     * @return int
265
     */
266 1
    public function getConnectTimeout()
267
    {
268 1
        return $this->connectTimeout;
269
    }
270
271
    /**
272
     * Sets the connect-timeout
273
     *
274
     * @param int $timeout
275
     */
276 1
    public function setConnectTimeout($timeout)
277
    {
278 1
        $this->connectTimeout = $timeout;
279 1
    }
280
}
281