Completed
Push — v3 ( 02d867...fd8678 )
by Austin
05:43
created

Server::getOption()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2
Metric Value
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 9.4285
cc 2
eloc 2
nc 2
nop 1
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
    /*
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 145
    public function __construct(array $server_info = [])
108
    {
109
110
        // Check for server type
111 145
        if (!array_key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE])) {
112 1
            throw new Exception("Missing server info key '" . self::SERVER_TYPE . "'!");
113
        }
114
115
        // Check for server host
116 144
        if (!array_key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST])) {
117 1
            throw new Exception("Missing server info key '" . self::SERVER_HOST . "'!");
118
        }
119
120
        // IP address and port check
121 143
        $this->checkAndSetIpPort($server_info[self::SERVER_HOST]);
122
123
        // Check for server id
124 139
        if (array_key_exists(self::SERVER_ID, $server_info) && !empty($server_info[self::SERVER_ID])) {
125
            // Set the server id
126 2
            $this->id = $server_info[self::SERVER_ID];
127 2
        } else {
128
            // Make an id so each server has an id when returned
129 138
            $this->id = sprintf('%s:%d', $this->ip, $this->port_client);
130
        }
131
132
        // Check and set server options
133 139
        if (array_key_exists(self::SERVER_OPTIONS, $server_info)) {
134
            // Set the options
135 130
            $this->options = $server_info[self::SERVER_OPTIONS];
136 130
        }
137
138
        try {
139
            // Make the protocol class for this type
140 139
            $class = new \ReflectionClass(
141 139
                sprintf('GameQ\\Protocols\\%s', ucfirst(strtolower($server_info[self::SERVER_TYPE])))
142 139
            );
143
144 138
            $this->protocol = $class->newInstanceArgs([$this->options]);
145 139
        } catch (\ReflectionException $e) {
146 1
            throw new Exception("Unable to locate Protocols class for '{$server_info[self::SERVER_TYPE]}'!");
147
        }
148
149
        // Check and set any server options
150 138
        $this->checkAndSetServerOptions();
151
152 138
        unset($server_info, $class);
153 138
    }
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 143
    protected function checkAndSetIpPort($ip_address)
163
    {
164
165
        // Test for IPv6
166 143
        if (substr_count($ip_address, ':') > 1) {
167
            // See if we have a port, input should be in the format [::1]:27015 or similar
168 3
            if (strstr($ip_address, ']:')) {
169
                // Explode to get port
170 2
                $server_addr = explode(':', $ip_address);
171
172
                // Port is the last item in the array, remove it and save
173 2
                $this->port_client = (int)array_pop($server_addr);
174
175
                // The rest is the address, recombine
176 2
                $this->ip = implode(':', $server_addr);
177
178 2
                unset($server_addr);
179 2
            } else {
180
                // Just the IPv6 address, no port defined, fail
181 1
                throw new Exception(
182 1
                    "The host address '{$ip_address}' is missing the port.  All "
183
                    . "servers must have a port defined!"
184 1
                );
185
            }
186
187
            // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
188 2
            if (!filter_var(trim($this->ip, '[]'), FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV6,])) {
189 1
                throw new Exception("The IPv6 address '{$this->ip}' is invalid.");
190
            }
191 1
        } else {
192
            // We have IPv4 with a port defined
193 140
            if (strstr($ip_address, ':')) {
194 139
                list($this->ip, $this->port_client) = explode(':', $ip_address);
195
196
                // Type case the port
197 139
                $this->port_client = (int)$this->port_client;
198 139
            } else {
199
                // No port, fail
200 1
                throw new Exception(
201 1
                    "The host address '{$ip_address}' is missing the port. All "
202
                    . "servers must have a port defined!"
203 1
                );
204
            }
205
206
            // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname.  Try to resolve
207 139
            if (!filter_var($this->ip, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4,])
208 139
                && $this->ip === gethostbyname($this->ip)
209 139
            ) {
210
                // When gethostbyname() fails it returns the original string
211
                // so if ip and the result from gethostbyname() are equal this failed.
212 1
                throw new Exception("Unable to resolve the host '{$this->ip}' to an IP address.");
213
            }
214
        }
215 139
    }
216
217
    /**
218
     * Check and set any server specific options
219
     */
220 138
    protected function checkAndSetServerOptions()
221
    {
222
223
        // Specific query port defined
224 138
        if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
225 17
            $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
226 17
        } else {
227
            // Do math based on the protocol class
228 121
            $this->port_query = $this->protocol->findQueryPort($this->port_client);
229
        }
230 138
    }
231
232
    /**
233
     * Set an option for this server
234
     *
235
     * @param $key
236
     * @param $value
237
     *
238
     * @return $this
239
     */
240 1
    public function setOption($key, $value)
241
    {
242
243 1
        $this->options[$key] = $value;
244
245 1
        return $this; // Make chainable
246
    }
247
248
    /**
249
     * Return set option value
250
     *
251
     * @param mixed $key
252
     *
253
     * @return mixed
254
     */
255 1
    public function getOption($key)
256
    {
257
258 1
        return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
259
    }
260
261
    /**
262
     * Get the ID for this server
263
     *
264
     * @return string
265
     */
266 1
    public function id()
267
    {
268
269 1
        return $this->id;
270
    }
271
272
    /**
273
     * Get the IP address for this server
274
     *
275
     * @return string
276
     */
277 114
    public function ip()
278
    {
279
280 114
        return $this->ip;
281
    }
282
283
    /**
284
     * Get the client port for this server
285
     *
286
     * @return int
287
     */
288 118
    public function portClient()
289
    {
290
291 118
        return $this->port_client;
292
    }
293
294
    /**
295
     * Get the query port for this server
296
     *
297
     * @return int
298
     */
299 111
    public function portQuery()
300
    {
301
302 111
        return $this->port_query;
303
    }
304
305
    /**
306
     * Return the protocol class for this server
307
     *
308
     * @return \GameQ\Protocol
309
     */
310 127
    public function protocol()
311
    {
312
313 127
        return $this->protocol;
314
    }
315
316
    /**
317
     * Get the join link for this server
318
     *
319
     * @return string
320
     */
321 111
    public function getJoinLink()
322
    {
323
324 111
        return sprintf($this->protocol->joinLink(), $this->ip, $this->portClient());
325
    }
326
327
    /*
328
     * Socket holding
329
     */
330
331
    /**
332
     * Add a socket for this server to be reused
333
     *
334
     * @codeCoverageIgnore
335
     *
336
     * @param \GameQ\Query\Core $socket
337
     */
338
    public function socketAdd(Query\Core $socket)
339
    {
340
341
        $this->sockets[] = $socket;
342
    }
343
344
    /**
345
     * Get a socket from the list to reuse, if any are available
346
     *
347
     * @codeCoverageIgnore
348
     *
349
     * @return \GameQ\Query\Core|null
350
     */
351
    public function socketGet()
352
    {
353
354
        $socket = null;
355
356
        if (count($this->sockets) > 0) {
357
            $socket = array_pop($this->sockets);
358
        }
359
360
        return $socket;
361
    }
362
363
    /**
364
     * Clear any sockets still listed and attempt to close them
365
     *
366
     * @codeCoverageIgnore
367
     */
368
    public function socketCleanse()
369
    {
370
371
        // Close all of the sockets available
372
        foreach ($this->sockets as $socket) {
373
            /* @var $socket \GameQ\Query\Core */
374
            $socket->close();
375
        }
376
377
        // Reset the sockets list
378
        $this->sockets = [];
379
    }
380
}
381