Passed
Pull Request — v3 (#705)
by
unknown
32:41
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]);
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)
177 12
    {
178 2
179
        // Test for IPv6
180 6
        if (substr_count($ip_address, ':') > 1) {
181 6
            // See if we have a port, input should be in the format [::1]:27015 or similar
182 5
            if (strstr($ip_address, ']:')) {
183 3
                // Explode to get port
184
                $server_addr = explode(':', $ip_address);
185
186
                // Port is the last item in the array, remove it and save
187 12
                $this->port_client = (int)array_pop($server_addr);
188 11
189
                // The rest is the address, recombine
190 1
                $this->ip = implode(':', $server_addr);
191
192 2028
                unset($server_addr);
193 2022
            } else {
194
                // Just the IPv6 address, no port defined, fail
195
                throw new Exception(
196 2022
                    "The host address '{$ip_address}' is missing the port.  All "
197 337
                    . "servers must have a port defined!"
198
                );
199 6
            }
200 6
201 5
            // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
202 3
            if (!filter_var(trim($this->ip, '[]'), FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV6,])) {
203
                throw new Exception("The IPv6 address '{$this->ip}' is invalid.");
204
            }
205
        } else {
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
    }
235
236
    /**
237
     * Check and set any server specific options
238
     */
239
    protected function checkAndSetServerOptions()
240
    {
241
242
        // Specific query port defined
243
        if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
244 6
            $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
245
        } else {
246
            // Do math based on the protocol class
247 6
            $this->port_query = $this->protocol->findQueryPort($this->port_client);
248
        }
249 6
    }
250
251
    /**
252
     * Set an option for this server
253
     *
254
     * @param $key
255
     * @param $value
256
     *
257
     * @return $this
258
     */
259 6
    public function setOption($key, $value)
260
    {
261
262 6
        $this->options[$key] = $value;
263
264
        return $this; // Make chainable
265 6
    }
266
267 6
    /**
268
     * Return set option value
269
     *
270
     * @param mixed $key
271
     *
272
     * @return mixed
273
     */
274
    public function getOption($key)
275 42
    {
276
277
        return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
278 42
    }
279
280
    public function getOptions()
281
    {
282
        return $this->options;
283
    }
284
285
    /**
286 1728
     * Get the ID for this server
287
     *
288
     * @return string
289 1728
     */
290
    public function id()
291
    {
292
293
        return $this->id;
294
    }
295
296
    /**
297 1776
     * Get the IP address for this server
298
     *
299
     * @return string
300 1776
     */
301
    public function ip()
302
    {
303
304
        return $this->ip;
305
    }
306
307
    /**
308 1710
     * Get the client port for this server
309
     *
310
     * @return int
311 1710
     */
312
    public function portClient()
313
    {
314
315
        return $this->port_client;
316
    }
317
318
    /**
319 1914
     * Get the query port for this server
320
     *
321
     * @return int
322 1914
     */
323
    public function portQuery()
324
    {
325
326
        return $this->port_query;
327
    }
328
329
    /**
330 1734
     * Return the protocol class for this server
331
     *
332
     * @return \GameQ\Protocol
333 1734
     */
334
    public function protocol()
335
    {
336
337
        return $this->protocol;
338
    }
339
340
    /**
341
     * Get the join link for this server
342
     *
343
     * @return string
344
     */
345
    public function getJoinLink()
346
    {
347
348
        return sprintf($this->protocol->joinLink(), $this->ip, $this->portClient());
349
    }
350
351
    /*
352
     * Socket holding
353
     */
354
355
    /**
356
     * Add a socket for this server to be reused
357
     *
358
     * @codeCoverageIgnore
359
     *
360
     * @param \GameQ\Query\Core $socket
361
     */
362
    public function socketAdd(Query\Core $socket)
363
    {
364
365
        $this->sockets[] = $socket;
366
    }
367
368
    /**
369
     * Get a socket from the list to reuse, if any are available
370
     *
371
     * @codeCoverageIgnore
372
     *
373
     * @return \GameQ\Query\Core|null
374
     */
375
    public function socketGet()
376
    {
377
378
        $socket = null;
379
380
        if (count($this->sockets) > 0) {
381
            $socket = array_pop($this->sockets);
382
        }
383
384
        return $socket;
385
    }
386
387
    /**
388
     * Clear any sockets still listed and attempt to close them
389
     *
390
     * @codeCoverageIgnore
391
     */
392
    public function socketCleanse()
393
    {
394
395
        // Close all of the sockets available
396
        foreach ($this->sockets as $socket) {
397
            /* @var $socket \GameQ\Query\Core */
398
            $socket->close();
399
        }
400
401
        // Reset the sockets list
402
        $this->sockets = [];
403
    }
404
405
    /**
406
     * Get custom parameters
407
     *
408
     * @return array
409
     */
410
    public function getCustom()
411
    {
412
        return $this->custom;
413
    }
414
}
415