Passed
Push — v3 ( efc739...bb78f7 )
by Austin
02:17
created

Native::close()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 10
cc 2
nc 2
nop 0
crap 6
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 30
    public function write($data)
56
    {
57
58
        try {
59
            // No socket for this server, make one
60 30
            if (is_null($this->socket)) {
61 30
                $this->create();
62
            }
63
64
            // Send the packet
65
            return fwrite($this->socket, $data);
66 30
        } catch (\Exception $e) {
67 30
            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 30
    protected function create()
89
    {
90
91
        // Create the remote address
92 30
        $remote_addr = sprintf("%s://%s:%d", $this->transport, $this->ip, $this->port);
93
94
        // Create context
95 30
        $context = stream_context_create([
96 30
            'socket' => [
97 25
                'bindto' => '0:0', // Bind to any available IP and OS decided port
98 25
            ],
99 25
        ]);
100
101
        // Define these first
102 30
        $errno = null;
103 30
        $errstr = null;
104
105
        // Create the socket
106 30
        if (($this->socket =
107 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...
108 5
            !== 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 30
            $this->socket = null;
124
125
            // Something bad happened, throw query exception
126 30
            throw new Exception(
127 30
                __METHOD__ . " - Error creating socket to server {$this->ip}:{$this->port}. Error: " . $errstr,
128 30
                $errno
129 25
            );
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 30
    public function getResponses(array $sockets, $timeout, $stream_timeout)
146
    {
147
148
        // Set the loop to active
149 30
        $loop_active = true;
150
151
        // Will hold the responses read from the sockets
152 30
        $responses = [];
153
154
        // To store the sockets
155 30
        $sockets_tmp = [];
156
157
        // Loop and pull out all the actual sockets we need to listen on
158 30
        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 30
        $read = $sockets_tmp;
171 30
        $write = null;
172 30
        $except = null;
173
174
        // Check to see if $read is empty, if so stream_select() will throw a warning
175 30
        if (empty($read)) {
176 30
            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