Ip   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 413
Duplicated Lines 0 %

Test Coverage

Coverage 99.3%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 43
eloc 137
c 2
b 0
f 0
dl 0
loc 413
ccs 141
cts 142
cp 0.993
rs 8.96

21 Methods

Rating   Name   Duplication   Size   Complexity  
A update() 0 5 3
A setAdapter() 0 7 2
A __destruct() 0 7 1
A __construct() 0 7 1
A addIp() 0 5 1
A process() 0 12 2
A getIpList() 0 3 1
A reset() 0 5 1
A fromFile() 0 14 4
A getHeaders() 0 15 4
A cityAdapter() 0 17 4
A toJson() 0 9 2
A toArray() 0 3 1
A compareArray() 0 3 1
A countryAdapter() 0 10 3
A asnAdapter() 0 15 3
A getUpdater() 0 3 1
A sort() 0 5 1
A toCsv() 0 8 2
A getReader() 0 16 4
A setReaders() 0 15 1

How to fix   Complexity   

Complex Class

Complex classes like Ip often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Ip, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PierInfor\GeoLite;
4
5
use PierInfor\GeoLite\Updater;
6
use GeoIp2\Exception\AddressNotFoundException;
7
use GeoIp2\Database\Reader;
8
9
/**
10
 * Ip class to use free maxmind dbs in mmdb format
11
 */
12
class Ip implements Interfaces\IpInterface
13
{
14
15
    /**
16
     * locales in use
17
     *
18
     * @var Array
19
     */
20
    private $readerLocales;
21
22
    /**
23
     * Reader instance for cities
24
     *
25
     * @var Reader
26
     */
27
    private $readerCity;
28
29
    /**
30
     * Reader instance for countries
31
     *
32
     * @var Reader
33
     */
34
    private $readerCountry;
35
36
    /**
37
     * Reader instance for Asn
38
     *
39
     * @var Reader
40
     */
41
    private $readerAsn;
42
43
    /**
44
     * updater instance
45
     *
46
     * @var Updater
47
     */
48
    private $updater;
49
50
    /**
51
     * Results
52
     *
53
     * @var Array
54
     */
55
    private $results;
56
57
    /**
58
     * Ip collection
59
     *
60
     * @var Array
61
     */
62
    private $ipList;
63
64
    /**
65
     * Current adapter
66
     *
67
     * @var String
68
     */
69
    private $adapter;
70
71
    /**
72
     * Sort column
73
     *
74
     * @var Integer
75
     */
76
    private $sortCol;
77
78
    /**
79
     * Instanciate with given locales
80
     *
81
     * @param array $locales
82
     */
83 38
    public function __construct($locales = ['fr'])
84
    {
85 38
        $this->readerLocales = $locales;
86 38
        $this->reset();
87 38
        $this->setReaders();
88 38
        $this->setAdapter();
89 38
        $this->updater = new Updater();
90
    }
91
92
    /**
93
     * free when unset instance
94
     *
95
     * @return void
96
     */
97 38
    public function __destruct()
98
    {
99 38
        $this->adapter = null;
100 38
        $this->readerCity->close();
101 38
        $this->readerCountry->close();
102 38
        $this->readerAsn->close();
103 38
        $this->reset();
104
    }
105
106
    /**
107
     * reset ip list and result
108
     *
109
     * @return void
110
     */
111 38
    public function reset()
112
    {
113 38
        $this->ipList = [];
114 38
        $this->results = [];
115 38
        $this->sortCol = 1;
116
    }
117
118
    /**
119
     * set adapter
120
     *
121
     * @param string $adapter
122
     * @return Ip
123
     * @throws \Exception
124
     */
125 38
    public function setAdapter(string $adapter = self::ADAPTER_COUNTRY): Ip
126
    {
127 38
        if (!in_array($adapter, self::ADAPTERS)) {
128 1
            throw new \Exception('Error: Unkown adapter ' . $adapter);
129
        }
130 38
        $this->adapter = $adapter;
131 38
        return $this;
132
    }
133
134
    /**
135
     * update current db if required or forced
136
     *
137
     * @param boolean $force
138
     * @return Updater
139
     */
140 1
    public function update(bool $force = false): Updater
141
    {
142 1
        return ($this->getUpdater()->updateRequired() || $force)
143 1
            ? $this->updater->update()
144 1
            : $this->updater;
145
    }
146
147
    /**
148
     * addIp
149
     *
150
     * @param string $ip
151
     * @return Ip
152
     */
153 8
    public function addIp(string $ip): Ip
154
    {
155 8
        $this->ipList[] = trim($ip);
156 8
        $this->ipList = array_unique($this->ipList);
157 8
        return $this;
158
    }
159
160
    /**
161
     * load ip list from a file
162
     *
163
     * @param string $filename
164
     * @return Ip
165
     * @throws \Exception
166
     */
167 8
    public function fromFile(string $filename): Ip
168
    {
169 8
        $this->reset();
170 8
        $handle = @fopen($filename, 'r');
171 8
        if (false === is_resource($handle)) {
172 2
            throw new \Exception('Error: File open issue');
173
        }
174 6
        if (false !== $handle) {
175 6
            while (($ip = fgets($handle, self::BUFFER)) !== false) {
176 6
                $this->addIp($ip);
177
            }
178 6
            fclose($handle);
179
        }
180 6
        return $this;
181
    }
182
183
    /**
184
     * returns ip list
185
     *
186
     * @return array
187
     */
188 7
    public function getIpList(): array
189
    {
190 7
        return $this->ipList;
191
    }
192
193
    /**
194
     * geo process ip list
195
     *
196
     * @return Ip
197
     */
198 7
    public function process(): Ip
199
    {
200 7
        $this->results = [];
201 7
        $reader = $this->getReader();
202 7
        $li = count($this->ipList);
203 7
        for ($c = 0; $c < $li; $c++) {
204 7
            $this->results[] = call_user_func_array(
205 7
                [$this, $this->adapter],
206 7
                [$reader, $this->ipList[$c]]
207
            );
208
        }
209 7
        return $this;
210
    }
211
212
    /**
213
     * return reader belongs to current adapter
214
     *
215
     * @return Reader
216
     */
217 17
    public function getReader(): Reader
218
    {
219 17
        switch ($this->adapter) {
220 17
            case self::ADAPTER_CITY:
221 7
                $reader = $this->readerCity;
222 7
                break;
223 11
            case self::ADAPTER_COUNTRY:
224 8
                $reader = $this->readerCountry;
225 8
                break;
226 4
            case self::ADAPTER_ASN:
227 4
                $reader = $this->readerAsn;
228 4
                break;
229
            default:
230
                $reader = $this->readerCity;
231
        }
232 17
        return $reader;
233
    }
234
235
    /**
236
     * returns Updater instance
237
     *
238
     * @return Updater
239
     */
240 2
    public function getUpdater(): Updater
241
    {
242 2
        return $this->updater->setAdapter($this->adapter);
243
    }
244
245
    /**
246
     * sort result by column number
247
     *
248
     * @param integer $col
249
     * @return Ip
250
     */
251 1
    public function sort(int $col = 1): Ip
252
    {
253 1
        $this->sortCol = $col;
254 1
        usort($this->results, [$this, 'compareArray']);
255 1
        return $this;
256
    }
257
258
    /**
259
     * compare two array for a given column
260
     *
261
     * @param array $a
262
     * @param array $b
263
     * @return integer
264
     */
265 1
    protected function compareArray(array $a, array $b): int
266
    {
267 1
        return strcmp($a[$this->sortCol], $b[$this->sortCol]);
268
    }
269
270
    /**
271
     * returns result as array
272
     *
273
     * @return array
274
     */
275 6
    public function toArray(): array
276
    {
277 6
        return $this->results;
278
    }
279
280
    /**
281
     * returns result as csv
282
     *
283
     * @param string $sep
284
     * @return void
285
     */
286 1
    public function toCsv(string $sep = ';'): string
287
    {
288 1
        $lc = count($this->results);
289 1
        $csv = '';
290 1
        for ($c = 0; $c < $lc; $c++) {
291 1
            $csv .= implode($sep, $this->results[$c]) . "\n";
292
        }
293 1
        return $csv;
294
    }
295
296
    /**
297
     * returns result as json
298
     *
299
     * @return string
300
     */
301 1
    public function toJson(): string
302
    {
303 1
        $lc = count($this->results);
304 1
        $json = [];
305 1
        $headers = $this->getHeaders();
306 1
        for ($c = 0; $c < $lc; $c++) {
307 1
            $json[] = array_combine($headers, $this->results[$c]);
308
        }
309 1
        return json_encode($json, JSON_PRETTY_PRINT);
310
    }
311
312
    /**
313
     * set readers, one per db file
314
     *
315
     * @return Ip
316
     */
317 1
    protected function setReaders(): Ip
318
    {
319 1
        $this->readerCity = new Reader(
320 1
            __DIR__ . self::DB_PATH . self::DB_CITY_FILENAME,
321 1
            $this->readerLocales
322
        );
323 1
        $this->readerCountry = new Reader(
324 1
            __DIR__ . self::DB_PATH . self::DB_COUNTRY_FILENAME,
325 1
            $this->readerLocales
326
        );
327 1
        $this->readerAsn = new Reader(
328 1
            __DIR__ . self::DB_PATH . self::DB_ASN_FILENAME,
329 1
            $this->readerLocales
330
        );
331 1
        return $this;
332
    }
333
334
    /**
335
     * returns headers
336
     *
337
     * @return array
338
     */
339 1
    protected function getHeaders(): array
340
    {
341 1
        $headers = [];
342 1
        switch ($this->adapter) {
343 1
            case self::ADAPTER_CITY:
344 1
                $headers = self::HEADERS_COMMON;
345 1
                break;
346 1
            case self::ADAPTER_COUNTRY:
347 1
                $headers = array_slice(self::HEADERS_COMMON, 0, 2);
348 1
                break;
349 1
            case self::ADAPTER_ASN:
350 1
                $headers = self::HEADERS_ASN;
351 1
                break;
352
        }
353 1
        return $headers;
354
    }
355
356
    /**
357
     * adapter to use city reader, returns record chosen fields
358
     *
359
     * @param Reader $reader
360
     * @param string $ip
361
     * @return array
362
     */
363 3
    protected function cityAdapter(Reader $reader, string $ip): array
364
    {
365 3
        $ip = trim($ip);
366
        try {
367 3
            $record = $reader->city($ip);
368
            return [
369 1
                $ip,
370 1
                $record->country->isoCode,
371 1
                $record->city->name ?: '?',
372 1
                $record->location->latitude,
373 1
                $record->location->longitude,
374 1
                $record->location->accuracyRadius,
375
            ];
376 2
        } catch (AddressNotFoundException $e) {
377 1
            return array_fill(0, 6, '?');
378 1
        } catch (\InvalidArgumentException $e) {
379 1
            return array_fill(0, 6, '?');
380
        }
381
    }
382
383
    /**
384
     * adapter to use country reader, returns record chosen fields
385
     *
386
     * @param Reader $reader
387
     * @param string $ip
388
     * @return array
389
     */
390 3
    protected function countryAdapter(Reader $reader, string $ip): array
391
    {
392 3
        $ip = trim($ip);
393
        try {
394 3
            $record = $reader->country($ip);
395 1
            return [$ip, $record->country->isoCode];
396 2
        } catch (AddressNotFoundException $e) {
397 1
            return array_fill(0, 2, '?');
398 1
        } catch (\InvalidArgumentException $e) {
399 1
            return array_fill(0, 2, '?');
400
        }
401
    }
402
403
    /**
404
     * adapter to use asn reader, returns record chosen fields
405
     *
406
     * @param Reader $reader
407
     * @param string $ip
408
     * @return array
409
     */
410 3
    protected function asnAdapter(Reader $reader, string $ip): array
411
    {
412 3
        $ip = trim($ip);
413 3
        $badRes = array_fill(0, 3, '?');
414
        try {
415 3
            $record = $reader->asn($ip);
416
            return [
417 1
                $ip,
418 1
                $record->autonomousSystemNumber,
419 1
                $record->autonomousSystemOrganization
420
            ];
421 2
        } catch (AddressNotFoundException $e) {
422 1
            return $badRes;
423 1
        } catch (\InvalidArgumentException $e) {
424 1
            return $badRes;
425
        }
426
    }
427
}
428