Passed
Push — v3 ( 962004...bdfc5f )
by Austin
06:52
created

Native::getResponses()   B

Complexity

Conditions 11
Paths 8

Size

Total Lines 81
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 6
Bugs 2 Features 0
Metric Value
eloc 29
c 6
b 2
f 0
dl 0
loc 81
ccs 0
cts 39
cp 0
rs 7.3166
cc 11
nc 8
nop 3
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        } else {
116
            // Reset socket
117
            $this->socket = null;
118
119
            // Something bad happened, throw query exception
120
            throw new Exception(
121
                __METHOD__ . " - Error creating socket to server {$this->ip}:{$this->port}. Error: " . $errstr,
122
                $errno
123
            );
124
        }
125
    }
126
127
    /**
128
     * Pull the responses out of the stream
129
     *
130
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
131
     * @SuppressWarnings(PHPMD.NPathComplexity)
132
     *
133
     * @param array $sockets
134
     * @param int   $timeout
135
     * @param int   $stream_timeout
136
     *
137
     * @return array Raw responses
138
     */
139
    public function getResponses(array $sockets, $timeout, $stream_timeout)
140
    {
141
142
        // Set the loop to active
143
        $loop_active = true;
144
145
        // Will hold the responses read from the sockets
146
        $responses = [];
147
148
        // To store the sockets
149
        $sockets_tmp = [];
150
151
        // Loop and pull out all the actual sockets we need to listen on
152
        foreach ($sockets as $socket_id => $socket_data) {
153
            // Get the socket
154
            /* @var $socket \GameQ\Query\Core */
155
            $socket = $socket_data['socket'];
156
157
            // Append the actual socket we are listening to
158
            $sockets_tmp[$socket_id] = $socket->get();
159
160
            unset($socket);
161
        }
162
163
        // Init some variables
164
        $read = $sockets_tmp;
165
        $write = null;
166
        $except = null;
167
168
        // Check to see if $read is empty, if so stream_select() will throw a warning
169
        if (empty($read)) {
170
            return $responses;
171
        }
172
173
        // This is when it should stop
174
        $time_stop = microtime(true) + $timeout;
175
176
        // Let's loop until we break something.
177
        while ($loop_active && microtime(true) < $time_stop) {
178
            // Check to make sure $read is not empty, if so we are done
179
            if (empty($read)) {
180
                break;
181
            }
182
183
            // Now lets listen for some streams, but do not cross the streams!
184
            $streams = stream_select($read, $write, $except, 0, $stream_timeout);
0 ignored issues
show
Bug introduced by
$write of type null is incompatible with the type array expected by parameter $write of stream_select(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

184
            $streams = stream_select($read, /** @scrutinizer ignore-type */ $write, $except, 0, $stream_timeout);
Loading history...
Bug introduced by
$except of type null is incompatible with the type array expected by parameter $except of stream_select(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

184
            $streams = stream_select($read, $write, /** @scrutinizer ignore-type */ $except, 0, $stream_timeout);
Loading history...
185
186
            // We had error or no streams left, kill the loop
187
            if ($streams === false || ($streams <= 0)) {
188
                break;
189
            }
190
191
            // Loop the sockets that received data back
192
            foreach ($read as $socket) {
193
                /* @var $socket resource */
194
195
                // See if we have a response
196
                if (($response = fread($socket, 8192)) === false) {
197
                    continue; // No response yet so lets continue.
198
                }
199
200
                // Check to see if the response is empty, if so we are done with this server
201
                if (strlen($response) == 0) {
202
                    // Remove this server from any future read loops
203
                    unset($sockets_tmp[(int)$socket]);
204
                    continue;
205
                }
206
207
                // Add the response we got back
208
                $responses[(int)$socket][] = $response;
209
            }
210
211
            // Because stream_select modifies read we need to reset it each time to the original array of sockets
212
            $read = $sockets_tmp;
213
        }
214
215
        // Free up some memory
216
        unset($streams, $read, $write, $except, $sockets_tmp, $time_stop, $response);
217
218
        // Return all of the responses, may be empty if something went wrong
219
        return $responses;
220
    }
221
}
222