Native   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Test Coverage

Coverage 48.44%

Importance

Changes 9
Bugs 4 Features 0
Metric Value
wmc 20
eloc 60
c 9
b 4
f 0
dl 0
loc 193
ccs 31
cts 64
cp 0.4844
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
B getResponses() 0 80 11
A close() 0 5 2
A get() 0 8 2
A write() 0 12 3
A create() 0 40 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
        // No socket for this server, make one
39
        if (is_null($this->socket)) {
40
            $this->create();
41
        }
42
43
        return $this->socket;
44
    }
45
46
    /**
47
     * Write data to the socket
48
     *
49
     * @param string $data
50
     *
51
     * @return int The number of bytes written
52
     * @throws \GameQ\Exception\Query
53
     */
54 30
    public function write($data)
55
    {
56
        try {
57
            // No socket for this server, make one
58 30
            if (is_null($this->socket)) {
59 30
                $this->create();
60
            }
61
62
            // Send the packet
63
            return fwrite($this->socket, $data);
64 30
        } catch (\Exception $e) {
65 30
            throw new Exception($e->getMessage(), $e->getCode(), $e);
66
        }
67
    }
68
69
    /**
70
     * Close the current socket
71
     */
72
    public function close()
73
    {
74
        if ($this->socket) {
75
            fclose($this->socket);
76
            $this->socket = null;
77
        }
78
    }
79
80
    /**
81
     * Create a new socket for this query
82
     *
83
     * @throws \GameQ\Exception\Query
84
     */
85 30
    protected function create()
86
    {
87
        // Create the remote address
88 30
        $remote_addr = sprintf("%s://%s:%d", $this->transport, $this->ip, $this->port);
89
90
        // Create context
91 30
        $context = stream_context_create([
92 30
            'socket' => [
93 30
                'bindto' => '0:0', // Bind to any available IP and OS decided port
94 30
            ],
95 30
        ]);
96
97
        // Define these first
98 30
        $errno = null;
99 30
        $errstr = null;
100
101
        // Create the socket
102 30
        if (($this->socket =
103 30
                @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...
104
            !== false
105
        ) {
106
            // Set the read timeout on the streams
107
            stream_set_timeout($this->socket, $this->timeout);
108
109
            // Set blocking mode
110
            stream_set_blocking($this->socket, $this->blocking);
111
112
            // Set the read buffer
113
            stream_set_read_buffer($this->socket, 0);
114
115
            // Set the write buffer
116
            stream_set_write_buffer($this->socket, 0);
117
        } else {
118
            // Reset socket
119 30
            $this->socket = null;
120
121
            // Something bad happened, throw query exception
122 30
            throw new Exception(
123 30
                __METHOD__ . " - Error creating socket to server {$this->ip}:{$this->port}. Error: " . $errstr,
124 30
                $errno
125 30
            );
126
        }
127
    }
128
129
    /**
130
     * Pull the responses out of the stream
131
     *
132
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
133
     * @SuppressWarnings(PHPMD.NPathComplexity)
134
     *
135
     * @param array $sockets
136
     * @param int   $timeout
137
     * @param int   $stream_timeout
138
     *
139
     * @return array Raw responses
140
     */
141 30
    public function getResponses(array $sockets, $timeout, $stream_timeout)
142
    {
143
        // Set the loop to active
144 30
        $loop_active = true;
145
146
        // Will hold the responses read from the sockets
147 30
        $responses = [];
148
149
        // To store the sockets
150 30
        $sockets_tmp = [];
151
152
        // Loop and pull out all the actual sockets we need to listen on
153 30
        foreach ($sockets as $socket_id => $socket_data) {
154
            // Get the socket
155
            // @var $socket \GameQ\Query\Core
156
            $socket = $socket_data['socket'];
157
158
            // Append the actual socket we are listening to
159
            $sockets_tmp[$socket_id] = $socket->get();
160
161
            unset($socket);
162
        }
163
164
        // Init some variables
165 30
        $read = $sockets_tmp;
166 30
        $write = null;
167 30
        $except = null;
168
169
        // Check to see if $read is empty, if so stream_select() will throw a warning
170 30
        if (empty($read)) {
171 30
            return $responses;
172
        }
173
174
        // This is when it should stop
175
        $time_stop = microtime(true) + $timeout;
176
177
        // Let's loop until we break something.
178
        while ($loop_active && microtime(true) < $time_stop) {
179
            // Check to make sure $read is not empty, if so we are done
180
            if (empty($read)) {
181
                break;
182
            }
183
184
            // Now lets listen for some streams, but do not cross the streams!
185
            $streams = stream_select($read, $write, $except, 0, $stream_timeout);
186
187
            // We had error or no streams left, kill the loop
188
            if ($streams === false || ($streams <= 0)) {
189
                break;
190
            }
191
192
            // Loop the sockets that received data back
193
            foreach ($read as $socket) {
194
                // @var $socket resource
195
196
                // See if we have a response
197
                if (($response = fread($socket, 32768)) === false) {
198
                    continue; // No response yet so lets continue.
199
                }
200
201
                // Check to see if the response is empty, if so we are done with this server
202
                if (strlen($response) == 0) {
203
                    // Remove this server from any future read loops
204
                    unset($sockets_tmp[(int)$socket]);
205
                    continue;
206
                }
207
208
                // Add the response we got back
209
                $responses[(int)$socket][] = $response;
210
            }
211
212
            // Because stream_select modifies read we need to reset it each time to the original array of sockets
213
            $read = $sockets_tmp;
214
        }
215
216
        // Free up some memory
217
        unset($streams, $read, $write, $except, $sockets_tmp, $time_stop, $response);
218
219
        // Return all of the responses, may be empty if something went wrong
220
        return $responses;
221
    }
222
}
223