Passed
Pull Request — v3 (#705)
by Zeky
34:40
created

Server::getCustom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 0
crap 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;
20
21
use GameQ\Exception\Server as Exception;
22
23
/**
24
 * Server class to represent each server entity
25
 *
26
 * @author Austin Bischoff <[email protected]>
27
 */
28
class Server
29
{
30
    /*
31
     * Server array keys
32
     */
33
    const SERVER_TYPE = 'type';
34
35
    const SERVER_HOST = 'host';
36
37
    const SERVER_ID = 'id';
38
39
    const SERVER_OPTIONS = 'options';
40
41
    const SERVER_CUSTOM = 'custom';
42
43
    /*
44
     * Server options keys
45
     */
46
47
    /*
48
     * Use this option when the query_port and client connect ports are different
49
     */
50
    const SERVER_OPTIONS_QUERY_PORT = 'query_port';
51
52
    /**
53
     * The protocol class for this server
54
     *
55
     * @type \GameQ\Protocol
56
     */
57
    protected $protocol = null;
58
59
    /**
60
     * Id of this server
61
     *
62
     * @type string
63
     */
64
    public $id = null;
65
66
    /**
67
     * IP Address of this server
68
     *
69
     * @type string
70
     */
71
    public $ip = null;
72
73
    /**
74
     * The server's client port (connect port)
75
     *
76
     * @type int
77
     */
78
    public $port_client = null;
79
80
    /**
81
     * The server's query port
82
     *
83
     * @type int
84
     */
85
    public $port_query = null;
86
87
    /**
88
     * The server's custom data
89
     *
90
     * @type array
91
     */
92
    public $custom = [];
93
94
    /**
95
     * Holds other server specific options
96
     *
97
     * @type array
98
     */
99
    protected $options = [];
100
101
    /**
102
     * Holds the sockets already open for this server
103
     *
104
     * @type array
105
     */
106 2058
    protected $sockets = [];
107
108
    /**
109
     * Construct the class with the passed options
110 2058
     *
111 6
     * @param array $server_info
112
     *
113
     * @throws \GameQ\Exception\Server
114
     */
115 2052
    public function __construct(array $server_info = [])
116 6
    {
117
118
        // Check for server type
119
        if (!array_key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE])) {
120 2046
            throw new Exception("Missing server info key '" . self::SERVER_TYPE . "'!");
121
        }
122
123 2022
        // Check for server host
124
        if (!array_key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST])) {
125 12
            throw new Exception("Missing server info key '" . self::SERVER_HOST . "'!");
126 2
        }
127
128 2016
        // IP address and port check
129
        $this->checkAndSetIpPort($server_info[self::SERVER_HOST], $server_info[self::SERVER_TYPE]);
130
131
        // Check for server id
132 2022
        if (array_key_exists(self::SERVER_ID, $server_info) && !empty($server_info[self::SERVER_ID])) {
133
            // Set the server id
134 1908
            $this->id = $server_info[self::SERVER_ID];
135 318
        } else {
136
            // Make an id so each server has an id when returned
137
            $this->id = sprintf('%s:%d', $this->ip, $this->port_client);
138
        }
139 2022
140 2022
        // Check and set server options
141 1011
        if (array_key_exists(self::SERVER_OPTIONS, $server_info)) {
142
            // Set the options
143 2016
            $this->options = $server_info[self::SERVER_OPTIONS];
144 342
        }
145 6
146
        // Check and set custom parameters
147
        if (array_key_exists(self::SERVER_CUSTOM, $server_info)) {
148
            // Set the parameters
149 2016
            $this->custom = $server_info[self::SERVER_CUSTOM];
150
        }
151 2016
152 1344
        try {
153
            // Make the protocol class for this type
154
            $class = new \ReflectionClass(
155
                sprintf('GameQ\\Protocols\\%s', ucfirst(strtolower($server_info[self::SERVER_TYPE])))
156
            );
157
158
            $this->protocol = $class->newInstanceArgs([$this->options]);
159
        } catch (\ReflectionException $e) {
160
            throw new Exception("Unable to locate Protocols class for '{$server_info[self::SERVER_TYPE]}'!");
161 2046
        }
162
163
        // Check and set any server options
164
        $this->checkAndSetServerOptions();
165 2046
166
        unset($server_info, $class);
167 18
    }
168
169 12
    /**
170
     * Check and set the ip address for this server
171
     *
172 12
     * @param $ip_address
173
     *
174
     * @throws \GameQ\Exception\Server
175 12
     */
176
    protected function checkAndSetIpPort($ip_address, &$type)
177 12
    {
178 2
        // Test for IPv6
179
        if (substr_count($ip_address, ':') > 1 & $type != 'discord') {
180 6
            // See if we have a port, input should be in the format [::1]:27015 or similar
181 6
            if (strstr($ip_address, ']:')) {
182 5
                // Explode to get port
183 3
                $server_addr = explode(':', $ip_address);
184
185
                // Port is the last item in the array, remove it and save
186
                $this->port_client = (int)array_pop($server_addr);
187 12
188 11
                // The rest is the address, recombine
189
                $this->ip = implode(':', $server_addr);
190 1
191
                unset($server_addr);
192 2028
            } else {
193 2022
                // Just the IPv6 address, no port defined, fail
194
                throw new Exception(
195
                    "The host address '{$ip_address}' is missing the port.  All "
196 2022
                    . "servers must have a port defined!"
197 337
                );
198
            }
199 6
200 6
            // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
201 5
            if (!filter_var(trim($this->ip, '[]'), FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV6,])) {
202 3
                throw new Exception("The IPv6 address '{$this->ip}' is invalid.");
203
            }
204
        } 
205
        else if($type != 'discord') {
206 2022
            // We have IPv4 with a port defined
207
            if (strstr($ip_address, ':')) {
208 48
                list($this->ip, $this->port_client) = explode(':', $ip_address);
209
210
                // Type case the port
211 48
                $this->port_client = (int)$this->port_client;
212
            } else {
213 6
                // No port, fail
214
                throw new Exception(
215 42
                    "The host address '{$ip_address}' is missing the port. All "
216
                    . "servers must have a port defined!"
217 7
                );
218
            }
219 1348
220
            // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname.
221
            if (! filter_var($this->ip, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4,])) {
222
                // Try to resolve the hostname to IPv4
223
                $resolved = gethostbyname($this->ip);
224 2016
225
                // When gethostbyname() fails it returns the original string
226
                if ($this->ip === $resolved) {
227
                    // so if ip and the result from gethostbyname() are equal this failed.
228 2016
                    throw new Exception("Unable to resolve the host '{$this->ip}' to an IP address.");
229 126
                } else {
230 21
                    $this->ip = $resolved;
231
                }
232 1890
            }
233
234 1344
        // ! HURAAYYY, IGNORE IF TYPE IS DISCORD
235
        // ! AND SET PORT/QPORT AS 1 AND ONLY 1
236
        }else{
237
            if (strstr($ip_address, ':')) {
238
                list($this->ip, $this->port_client) = explode(':', $ip_address);
239
240
                // Type case the port
241
                $this->options['invite'] = $this->port_client;
0 ignored issues
show
Documentation Bug introduced by
The property $port_client was declared of type integer, but $this->port_client is of type string. 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...
242
                if(is_string($this->port_client)){
0 ignored issues
show
introduced by
The condition is_string($this->port_client) is always true.
Loading history...
243
                    $this->port_client = 1;
244 6
                }
245
            } else {
246
                // No port, fail
247 6
                throw new Exception(
248
                    "The host address '{$ip_address}' is missing the invite."
249 6
                    ."All discord servers must have invite like: discord.gg:<invite>"
250
                );
251
            }
252
        }
253
    }
254
255
    /**
256
     * Check and set any server specific options
257
     */
258
    protected function checkAndSetServerOptions()
259 6
    {
260
261
        // Specific query port defined
262 6
        if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
263
            $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
264
        }else{
265 6
            // Do math based on the protocol class
266
            $this->port_query = $this->protocol->findQueryPort($this->port_client);
267 6
        }
268
    }
269
270
    /**
271
     * Set an option for this server
272
     *
273
     * @param $key
274
     * @param $value
275 42
     *
276
     * @return $this
277
     */
278 42
    public function setOption($key, $value)
279
    {
280
281
        $this->options[$key] = $value;
282
283
        return $this; // Make chainable
284
    }
285
286 1728
    /**
287
     * Return set option value
288
     *
289 1728
     * @param mixed $key
290
     *
291
     * @return mixed
292
     */
293
    public function getOption($key)
294
    {
295
296
        return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
297 1776
    }
298
299
    public function getOptions()
300 1776
    {
301
        return $this->options;
302
    }
303
304
    /**
305
     * Get the ID for this server
306
     *
307
     * @return string
308 1710
     */
309
    public function id()
310
    {
311 1710
312
        return $this->id;
313
    }
314
315
    /**
316
     * Get the IP address for this server
317
     *
318
     * @return string
319 1914
     */
320
    public function ip()
321
    {
322 1914
323
        return $this->ip;
324
    }
325
326
    /**
327
     * Get the client port for this server
328
     *
329
     * @return int
330 1734
     */
331
    public function portClient()
332
    {
333 1734
334
        return $this->port_client;
335
    }
336
337
    /**
338
     * Get the query port for this server
339
     *
340
     * @return int
341
     */
342
    public function portQuery()
343
    {
344
345
        return $this->port_query;
346
    }
347
348
    /**
349
     * Return the protocol class for this server
350
     *
351
     * @return \GameQ\Protocol
352
     */
353
    public function protocol()
354
    {
355
356
        return $this->protocol;
357
    }
358
359
    /**
360
     * Get the join link for this server
361
     *
362
     * @return string
363
     */
364
    public function getJoinLink()
365
    {
366
367
        return @sprintf($this->protocol->joinLink(), $this->ip, $this->portClient());
368
    }
369
370
    /*
371
     * Socket holding
372
     */
373
374
    /**
375
     * Add a socket for this server to be reused
376
     *
377
     * @codeCoverageIgnore
378
     *
379
     * @param \GameQ\Query\Core $socket
380
     */
381
    public function socketAdd(Query\Core $socket)
382
    {
383
384
        $this->sockets[] = $socket;
385
    }
386
387
    /**
388
     * Get a socket from the list to reuse, if any are available
389
     *
390
     * @codeCoverageIgnore
391
     *
392
     * @return \GameQ\Query\Core|null
393
     */
394
    public function socketGet()
395
    {
396
397
        $socket = null;
398
399
        if (count($this->sockets) > 0) {
400
            $socket = array_pop($this->sockets);
401
        }
402
403
        return $socket;
404
    }
405
406
    /**
407
     * Clear any sockets still listed and attempt to close them
408
     *
409
     * @codeCoverageIgnore
410
     */
411
    public function socketCleanse()
412
    {
413
414
        // Close all of the sockets available
415
        foreach ($this->sockets as $socket) {
416
            /* @var $socket \GameQ\Query\Core */
417
            $socket->close();
418
        }
419
420
        // Reset the sockets list
421
        $this->sockets = [];
422
    }
423
424
    /**
425
     * Get custom parameters
426
     *
427
     * @return array
428
     */
429
    public function getCustom()
430
    {
431
        return $this->custom;
432
    }
433
}
434