Passed
Pull Request — v3 (#613)
by
unknown
04:35 queued 02:19
created

Server::setOption()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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