Passed
Push — v3 ( 01700e...6888fb )
by
unknown
07:39 queued 05:23
created

Native   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 11
Bugs 4 Features 0
Metric Value
wmc 20
eloc 60
c 11
b 4
f 0
dl 0
loc 198
ccs 0
cts 90
cp 0
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A close() 0 6 2
A get() 0 9 2
A write() 0 13 3
B getResponses() 0 81 11
A create() 0 41 2
1
<?php
2
/**
3
 * This file is part of GameQ.
4
 *
5
 * GameQ is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * GameQ is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace GameQ\Query;
20
21
use GameQ\Exception\Query as Exception;
22
23
/**
24
 * Native way of querying servers
25
 *
26
 * @author Austin Bischoff <[email protected]>
27
 */
28
class Native extends Core
29
{
30
    /**
31
     * Get the current socket or create one and return
32
     *
33
     * @return resource|null
34
     * @throws \GameQ\Exception\Query
35
     */
36
    public function get()
37
    {
38
39
        // No socket for this server, make one
40
        if (is_null($this->socket)) {
41
            $this->create();
42
        }
43
44
        return $this->socket;
45
    }
46
47
    /**
48
     * Write data to the socket
49
     *
50
     * @param string $data
51
     *
52
     * @return int The number of bytes written
53
     * @throws \GameQ\Exception\Query
54
     */
55
    public function write($data)
56
    {
57
58
        try {
59
            // No socket for this server, make one
60
            if (is_null($this->socket)) {
61
                $this->create();
62
            }
63
64
            // Send the packet
65
            return fwrite($this->socket, $data);
66
        } catch (\Exception $e) {
67
            throw new Exception($e->getMessage(), $e->getCode(), $e);
68
        }
69
    }
70
71
    /**
72
     * Close the current socket
73
     */
74
    public function close()
75
    {
76
77
        if ($this->socket) {
78
            fclose($this->socket);
79
            $this->socket = null;
80
        }
81
    }
82
83
    /**
84
     * Create a new socket for this query
85
     *
86
     * @throws \GameQ\Exception\Query
87
     */
88
    protected function create()
89
    {
90
91
        // Create the remote address
92
        $remote_addr = sprintf("%s://%s:%d", $this->transport, $this->ip, $this->port);
93
94
        // Create context
95
        $context = stream_context_create([
96
            'socket' => [
97
                'bindto' => '0:0', // Bind to any available IP and OS decided port
98
            ],
99
        ]);
100
101
        // Define these first
102
        $errno = null;
103
        $errstr = null;
104
105
        // Create the socket
106
        if (($this->socket =
107
                @stream_socket_client($remote_addr, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context))
0 ignored issues
show
Documentation Bug introduced by
It seems like @stream_socket_client($r...IENT_CONNECT, $context) can also be of type false. However, the property $socket is declared as type null|resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
108
            !== false
109
        ) {
110
            // Set the read timeout on the streams
111
            stream_set_timeout($this->socket, $this->timeout);
112
113
            // Set blocking mode
114
            stream_set_blocking($this->socket, $this->blocking);
115
116
            // Set the read buffer
117
            stream_set_read_buffer($this->socket, 0);
118
119
            // Set the write buffer
120
            stream_set_write_buffer($this->socket, 0);
121
        } else {
122
            // Reset socket
123
            $this->socket = null;
124
125
            // Something bad happened, throw query exception
126
            throw new Exception(
127
                __METHOD__ . " - Error creating socket to server {$this->ip}:{$this->port}. Error: " . $errstr,
128
                $errno
129
            );
130
        }
131
    }
132
133
    /**
134
     * Pull the responses out of the stream
135
     *
136
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
137
     * @SuppressWarnings(PHPMD.NPathComplexity)
138
     *
139
     * @param array $sockets
140
     * @param int   $timeout
141
     * @param int   $stream_timeout
142
     *
143
     * @return array Raw responses
144
     */
145
    public function getResponses(array $sockets, $timeout, $stream_timeout)
146
    {
147
148
        // Set the loop to active
149
        $loop_active = true;
150
151
        // Will hold the responses read from the sockets
152
        $responses = [];
153
154
        // To store the sockets
155
        $sockets_tmp = [];
156
157
        // Loop and pull out all the actual sockets we need to listen on
158
        foreach ($sockets as $socket_id => $socket_data) {
159
            // Get the socket
160
            /* @var $socket \GameQ\Query\Core */
161
            $socket = $socket_data['socket'];
162
163
            // Append the actual socket we are listening to
164
            $sockets_tmp[$socket_id] = $socket->get();
165
166
            unset($socket);
167
        }
168
169
        // Init some variables
170
        $read = $sockets_tmp;
171
        $write = null;
172
        $except = null;
173
174
        // Check to see if $read is empty, if so stream_select() will throw a warning
175
        if (empty($read)) {
176
            return $responses;
177
        }
178
179
        // This is when it should stop
180
        $time_stop = microtime(true) + $timeout;
181
182
        // Let's loop until we break something.
183
        while ($loop_active && microtime(true) < $time_stop) {
184
            // Check to make sure $read is not empty, if so we are done
185
            if (empty($read)) {
186
                break;
187
            }
188
189
            // Now lets listen for some streams, but do not cross the streams!
190
            $streams = stream_select($read, $write, $except, 0, $stream_timeout);
191
192
            // We had error or no streams left, kill the loop
193
            if ($streams === false || ($streams <= 0)) {
194
                break;
195
            }
196
197
            // Loop the sockets that received data back
198
            foreach ($read as $socket) {
199
                /* @var $socket resource */
200
201
                // See if we have a response
202
                if (($response = fread($socket, 32768)) === false) {
203
                    continue; // No response yet so lets continue.
204
                }
205
206
                // Check to see if the response is empty, if so we are done with this server
207
                if (strlen($response) == 0) {
208
                    // Remove this server from any future read loops
209
                    unset($sockets_tmp[(int)$socket]);
210
                    continue;
211
                }
212
213
                // Add the response we got back
214
                $responses[(int)$socket][] = $response;
215
            }
216
217
            // Because stream_select modifies read we need to reset it each time to the original array of sockets
218
            $read = $sockets_tmp;
219
        }
220
221
        // Free up some memory
222
        unset($streams, $read, $write, $except, $sockets_tmp, $time_stop, $response);
223
224
        // Return all of the responses, may be empty if something went wrong
225
        return $responses;
226
    }
227
}
228