Completed
Pull Request — master (#13)
by Harry
02:43
created

StreamWriter::__destruct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of graze/dog-statsd
4
 *
5
 * Copyright (c) 2017 Nature Delivered Ltd. <https://www.graze.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license https://github.com/graze/dog-statsd/blob/master/LICENSE.md
11
 * @link    https://github.com/graze/dog-statsd
12
 */
13
14
namespace Graze\DogStatsD\Stream;
15
16
use Graze\DogStatsD\Exception\ConnectionException;
17
18
/**
19
 * StreamWriter will attempt to write a message to a udp socket.
20
 *
21
 * If the connection fails, it will never try and reconnect to prevent application blocking
22
 */
23
class StreamWriter implements WriterInterface
24
{
25
    /**
26
     * Seconds to wait (as a base) for exponential back-off on connection
27
     *
28
     * minDelay = RETRY_INTERVAL * (2 ^ num_failed_attempts)
29
     *
30
     * e.g.
31
     * 0, 0.1 0.2 0.4 0.8 1.6 3.2 6.4 12.8 25.6 51.2 102.4 etc...
32
     */
33
    const RETRY_INTERVAL = 0.1;
34
35
    const ON_ERROR_ERROR     = 'error';
36
    const ON_ERROR_EXCEPTION = 'exception';
37
    const ON_ERROR_IGNORE    = 'ignore';
38
39
    /** @var resource|null */
40
    protected $socket;
41
    /** @var string */
42
    private $host;
43
    /** @var int */
44
    private $port;
45
    /** @var string */
46
    private $onError;
47
    /** @var float|null */
48
    private $timeout;
49
    /** @var string */
50
    private $instance;
51
    /** @var int */
52
    private $numFails = 0;
53
    /** @var float */
54
    private $waitTill = 0.0;
55
56
    /**
57
     * @param string     $instance
58
     * @param string     $host
59
     * @param int        $port
60
     * @param string     $onError What to do on connection error
61
     * @param float|null $timeout
62
     */
63 39
    public function __construct(
64
        $instance = 'writer',
65
        $host = '127.0.0.1',
66
        $port = 8125,
67
        $onError = self::ON_ERROR_EXCEPTION,
68
        $timeout = null
69
    ) {
70 39
        $this->instance = $instance;
71 39
        $this->host = $host;
72 39
        $this->port = $port;
73 39
        $this->onError = $onError;
74 39
        $this->timeout = $timeout;
75 39
    }
76
77 3
    public function __destruct()
78
    {
79 3
        if ($this->socket) {
80 2
            @fclose($this->socket);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
81 2
        }
82 3
    }
83
84
    /**
85
     * @param string $message
86
     *
87
     * @return bool
88
     */
89 39
    public function write($message)
90
    {
91 39
        $this->ensureConnection();
92 38
        if ($this->socket) {
93 35
            if (@fwrite($this->socket, $message) === false) {
94
                // attempt to re-send on socket resource failure
95 1
                $this->socket = $this->connect();
96 1
                if ($this->socket) {
97 1
                    return (@fwrite($this->socket, $message) !== false);
98
                }
99
            } else {
100 35
                return true;
101
            }
102
        }
103 3
        return false;
104
    }
105
106
    /**
107
     * Ensure that we are currently connected to the socket
108
     */
109 39
    protected function ensureConnection()
110
    {
111 39
        if ((!$this->socket) && ($this->canConnect())) {
112 38
            $this->socket = $this->connect();
113 37
        }
114 38
    }
115
116
    /**
117
     * @return bool
118
     */
119 39
    protected function canConnect()
120
    {
121 39
        return (microtime(true) > $this->waitTill);
122
    }
123
124
    /**
125
     * Attempt to connect to a stream
126
     *
127
     * @return null|resource
128
     */
129 39
    protected function connect()
130
    {
131 39
        $socket = @fsockopen('udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout);
132 39
        if ($socket === false) {
133 4
            $this->waitTill = microtime(true) + (static::RETRY_INTERVAL * (pow(2, $this->numFails++)));
134
135 4
            switch ($this->onError) {
136 4
                case static::ON_ERROR_ERROR:
137 1
                    trigger_error(
138 1
                        sprintf('StatsD server connection failed (udp://%s:%d)', $this->host, $this->port),
139
                        E_USER_WARNING
140 1
                    );
141 1
                    break;
142 3
                case static::ON_ERROR_EXCEPTION:
143 1
                    throw new ConnectionException($this->instance, '(' . $errno . ') ' . $errstr);
144 3
            }
145 3
        } else {
146 35
            $this->numFails = 0;
147 35
            $this->waitTill = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $waitTill was declared of type double, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
148
149 35
            $sec = (int) $this->timeout;
150 35
            $ms = (int) (($this->timeout - $sec) * 1000);
151 35
            stream_set_timeout($socket, $sec, $ms);
152
        }
153 38
        return $socket;
154
    }
155
}
156