Server   A
last analyzed

Complexity

Total Complexity 34

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 34
eloc 83
c 3
b 1
f 0
dl 0
loc 347
ccs 70
cts 70
cp 1
rs 9.68

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getJoinLink() 0 12 2
A checkAndSetServerOptions() 0 8 2
A getOptions() 0 3 1
A id() 0 3 1
A socketCleanse() 0 10 2
A setOption() 0 5 1
A ip() 0 3 1
A portQuery() 0 3 1
B __construct() 0 45 9
B checkAndSetIpPort() 0 54 7
A portClient() 0 3 1
A socketGet() 0 9 2
A protocol() 0 3 1
A socketAdd() 0 3 1
A getOption() 0 3 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
    // Server array keys
31
    const SERVER_TYPE = 'type';
32
33
    const SERVER_HOST = 'host';
34
35
    const SERVER_ID = 'id';
36
37
    const SERVER_OPTIONS = 'options';
38
39
    // Server options keys
40
41
    // Use this option when the query_port and client connect ports are different
42
    const SERVER_OPTIONS_QUERY_PORT = 'query_port';
43
44
    /**
45
     * The protocol class for this server
46
     *
47
     * @var \GameQ\Protocol
48
     */
49
    protected $protocol = null;
50
51
    /**
52
     * Id of this server
53
     *
54
     * @var string
55
     */
56
    public $id = null;
57
58
    /**
59
     * IP Address of this server
60
     *
61
     * @var string
62
     */
63
    public $ip = null;
64
65
    /**
66
     * The server's client port (connect port)
67
     *
68
     * @var int
69
     */
70
    public $port_client = null;
71
72
    /**
73
     * The server's query port
74
     *
75
     * @var int
76
     */
77
    public $port_query = null;
78
79
    /**
80
     * Holds other server specific options
81
     *
82
     * @var array
83
     */
84
    protected $options = [];
85
86
    /**
87
     * Holds the sockets already open for this server
88
     *
89
     * @var array
90
     */
91
    protected $sockets = [];
92
93
    /**
94
     * Construct the class with the passed options
95
     *
96
     * @param array $server_info
97
     *
98
     * @throws \GameQ\Exception\Server
99
     */
100 2094
    public function __construct(array $server_info = [])
101
    {
102
        // Check for server type
103 2094
        if (!array_key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE])) {
104 6
            throw new Exception("Missing server info key '" . self::SERVER_TYPE . "'!");
105
        }
106
107
        // Check for server host
108 2088
        if (!array_key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST])) {
109 6
            throw new Exception("Missing server info key '" . self::SERVER_HOST . "'!");
110
        }
111
112
        // IP address and port check
113 2082
        $this->checkAndSetIpPort($server_info[self::SERVER_HOST]);
114
115
        // Check for server id
116 2058
        if (array_key_exists(self::SERVER_ID, $server_info) && !empty($server_info[self::SERVER_ID])) {
117
            // Set the server id
118 12
            $this->id = $server_info[self::SERVER_ID];
119
        } else {
120
            // Make an id so each server has an id when returned
121 2052
            $this->id = sprintf('%s:%d', $this->ip, $this->port_client);
122
        }
123
124
        // Check and set server options
125 2058
        if (array_key_exists(self::SERVER_OPTIONS, $server_info)) {
126
            // Set the options
127 1944
            $this->options = $server_info[self::SERVER_OPTIONS];
128
        }
129
130
        try {
131
            // Make the protocol class for this type
132 2058
            $class = new \ReflectionClass(
133 2058
                sprintf('GameQ\\Protocols\\%s', ucfirst(strtolower($server_info[self::SERVER_TYPE])))
134 2058
            );
135
136 2052
            $this->protocol = $class->newInstanceArgs([$this->options]);
137 6
        } catch (\ReflectionException $e) {
138 6
            throw new Exception("Unable to locate Protocols class for '{$server_info[self::SERVER_TYPE]}'!");
139
        }
140
141
        // Check and set any server options
142 2052
        $this->checkAndSetServerOptions();
143
144 2052
        unset($server_info, $class);
145
    }
146
147
    /**
148
     * Check and set the ip address for this server
149
     *
150
     * @param $ip_address
151
     *
152
     * @throws \GameQ\Exception\Server
153
     */
154 2082
    protected function checkAndSetIpPort($ip_address)
155
    {
156
        // Test for IPv6
157 2082
        if (substr_count($ip_address, ':') > 1) {
158
            // See if we have a port, input should be in the format [::1]:27015 or similar
159 18
            if (strstr($ip_address, ']:')) {
160
                // Explode to get port
161 12
                $server_addr = explode(':', $ip_address);
162
163
                // Port is the last item in the array, remove it and save
164 12
                $this->port_client = (int)array_pop($server_addr);
165
166
                // The rest is the address, recombine
167 12
                $this->ip = implode(':', $server_addr);
168
169 12
                unset($server_addr);
170
            } else {
171
                // Just the IPv6 address, no port defined, fail
172 6
                throw new Exception(
173 6
                    "The host address '{$ip_address}' is missing the port.  All "
174 6
                    . "servers must have a port defined!"
175 6
                );
176
            }
177
178
            // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
179 12
            if (!filter_var(trim($this->ip, '[]'), FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV6,])) {
180 9
                throw new Exception("The IPv6 address '{$this->ip}' is invalid.");
181
            }
182
        } else {
183
            // We have IPv4 with a port defined
184 2064
            if (strstr($ip_address, ':')) {
185 2058
                list($this->ip, $this->port_client) = explode(':', $ip_address);
186
187
                // Type case the port
188 2058
                $this->port_client = (int)$this->port_client;
189
            } else {
190
                // No port, fail
191 6
                throw new Exception(
192 6
                    "The host address '{$ip_address}' is missing the port. All "
193 6
                    . "servers must have a port defined!"
194 6
                );
195
            }
196
197
            // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname.
198 2058
            if (! filter_var($this->ip, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4,])) {
199
                // Try to resolve the hostname to IPv4
200 48
                $resolved = gethostbyname($this->ip);
201
202
                // When gethostbyname() fails it returns the original string
203 48
                if ($this->ip === $resolved) {
204
                    // so if ip and the result from gethostbyname() are equal this failed.
205 6
                    throw new Exception("Unable to resolve the host '{$this->ip}' to an IP address.");
206
                } else {
207 42
                    $this->ip = $resolved;
208
                }
209
            }
210
        }
211
    }
212
213
    /**
214
     * Check and set any server specific options
215
     */
216 2052
    protected function checkAndSetServerOptions()
217
    {
218
        // Specific query port defined
219 2052
        if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
220 126
            $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
221
        } else {
222
            // Do math based on the protocol class
223 1926
            $this->port_query = $this->protocol->findQueryPort($this->port_client);
224
        }
225
    }
226
227
    /**
228
     * Set an option for this server
229
     *
230
     * @param $key
231
     * @param $value
232
     *
233
     * @return $this
234
     */
235 6
    public function setOption($key, $value)
236
    {
237 6
        $this->options[$key] = $value;
238
239 6
        return $this; // Make chainable
240
    }
241
242
    /**
243
     * Return set option value
244
     *
245
     * @param mixed $key
246
     *
247
     * @return mixed
248
     */
249 18
    public function getOption($key)
250
    {
251 18
        return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
252
    }
253
254 6
    public function getOptions()
255
    {
256 6
        return $this->options;
257
    }
258
259
    /**
260
     * Get the ID for this server
261
     *
262
     * @return string
263
     */
264 42
    public function id()
265
    {
266 42
        return $this->id;
267
    }
268
269
    /**
270
     * Get the IP address for this server
271
     *
272
     * @return string
273
     */
274 1752
    public function ip()
275
    {
276 1752
        return $this->ip;
277
    }
278
279
    /**
280
     * Get the client port for this server
281
     *
282
     * @return int
283
     */
284 1812
    public function portClient()
285
    {
286 1812
        return $this->port_client;
287
    }
288
289
    /**
290
     * Get the query port for this server
291
     *
292
     * @return int
293
     */
294 1734
    public function portQuery()
295
    {
296 1734
        return $this->port_query;
297
    }
298
299
    /**
300
     * Return the protocol class for this server
301
     *
302
     * @return \GameQ\Protocol
303
     */
304 1950
    public function protocol()
305
    {
306 1950
        return $this->protocol;
307
    }
308
309
    /**
310
     * Get the join link for this server
311
     *
312
     * @return null|string
313
     */
314 1770
    public function getJoinLink()
315
    {
316
        // Read the joinLink template defined by the Protocol
317 1770
        $joinLink = $this->protocol->joinLink();
318
319
        // Ensure the Protocol provides a joinLink template
320 1770
        if (is_null($joinLink)) {
321 606
            return null;
322
        }
323
        
324
        // Fill the template to build the final joinLink
325 1164
        return sprintf($joinLink, $this->ip, $this->portClient());
326
    }
327
328
    // Socket holding
329
330
    /**
331
     * Add a socket for this server to be reused
332
     *
333
     * @codeCoverageIgnore
334
     *
335
     * @param \GameQ\Query\Core $socket
336
     */
337
    public function socketAdd(Query\Core $socket)
338
    {
339
        $this->sockets[] = $socket;
340
    }
341
342
    /**
343
     * Get a socket from the list to reuse, if any are available
344
     *
345
     * @codeCoverageIgnore
346
     *
347
     * @return \GameQ\Query\Core|null
348
     */
349
    public function socketGet()
350
    {
351
        $socket = null;
352
353
        if (count($this->sockets) > 0) {
354
            $socket = array_pop($this->sockets);
355
        }
356
357
        return $socket;
358
    }
359
360
    /**
361
     * Clear any sockets still listed and attempt to close them
362
     *
363
     * @codeCoverageIgnore
364
     */
365
    public function socketCleanse()
366
    {
367
        // Close all of the sockets available
368
        foreach ($this->sockets as $socket) {
369
            // @var $socket \GameQ\Query\Core
370
            $socket->close();
371
        }
372
373
        // Reset the sockets list
374
        $this->sockets = [];
375
    }
376
}
377