Completed
Push — master ( 80180d...4ce40a )
by Pierre
01:36
created

Ip::fromFile()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 14
ccs 10
cts 10
cp 1
rs 9.9666
cc 4
nc 3
nop 1
crap 4
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
        }
230 17
        return $reader;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reader does not seem to be defined for all execution paths leading up to this point.
Loading history...
231
    }
232
233
    /**
234
     * returns Updater instance
235
     *
236
     * @return Updater
237
     */
238 2
    public function getUpdater(): Updater
239
    {
240 2
        return $this->updater->setAdapter($this->adapter);
241
    }
242
243
    /**
244
     * sort result by column number
245
     *
246
     * @param integer $col
247
     * @return Ip
248
     */
249 1
    public function sort(int $col = 1): Ip
250
    {
251 1
        $this->sortCol = $col;
252 1
        usort($this->results, [$this, 'compareArray']);
253 1
        return $this;
254
    }
255
256
    /**
257
     * compare two array for a given column
258
     *
259
     * @param array $a
260
     * @param array $b
261
     * @return integer
262
     */
263 1
    protected function compareArray(array $a, array $b): int
264
    {
265 1
        return strcmp($a[$this->sortCol], $b[$this->sortCol]);
266
    }
267
268
    /**
269
     * returns result as array
270
     *
271
     * @return array
272
     */
273 6
    public function toArray(): array
274
    {
275 6
        return $this->results;
276
    }
277
278
    /**
279
     * returns result as csv
280
     *
281
     * @param string $sep
282
     * @return void
283
     */
284 1
    public function toCsv(string $sep = ';'): string
285
    {
286 1
        $lc = count($this->results);
287 1
        $csv = '';
288 1
        for ($c = 0; $c < $lc; $c++) {
289 1
            $csv .= implode($sep, $this->results[$c]) . "\n";
290
        }
291 1
        return $csv;
292
    }
293
294
    /**
295
     * returns result as json
296
     *
297
     * @return string
298
     */
299 1
    public function toJson(): string
300
    {
301 1
        $lc = count($this->results);
302 1
        $json = [];
303 1
        $headers = $this->getHeaders();
304 1
        for ($c = 0; $c < $lc; $c++) {
305 1
            $json[] = array_combine($headers, $this->results[$c]);
306
        }
307 1
        return json_encode($json, JSON_PRETTY_PRINT);
308
    }
309
310
    /**
311
     * set readers, one per db file
312
     *
313
     * @return Ip
314
     */
315 1
    protected function setReaders(): Ip
316
    {
317 1
        $this->readerCity = new Reader(
318 1
            __DIR__ . self::DB_PATH . self::DB_CITY_FILENAME,
319 1
            $this->readerLocales
320
        );
321 1
        $this->readerCountry = new Reader(
322 1
            __DIR__ . self::DB_PATH . self::DB_COUNTRY_FILENAME,
323 1
            $this->readerLocales
324
        );
325 1
        $this->readerAsn = new Reader(
326 1
            __DIR__ . self::DB_PATH . self::DB_ASN_FILENAME,
327 1
            $this->readerLocales
328
        );
329 1
        return $this;
330
    }
331
332
    /**
333
     * returns headers
334
     *
335
     * @return array
336
     */
337 1
    protected function getHeaders(): array
338
    {
339 1
        $headers = [];
340 1
        switch ($this->adapter) {
341 1
            case self::ADAPTER_CITY:
342 1
                $headers = self::HEADERS_COMMON;
343 1
                break;
344 1
            case self::ADAPTER_COUNTRY:
345 1
                $headers = array_slice(self::HEADERS_COMMON, 0, 2);
346 1
                break;
347 1
            case self::ADAPTER_ASN:
348 1
                $headers = self::HEADERS_ASN;
349 1
                break;
350
        }
351 1
        return $headers;
352
    }
353
354
    /**
355
     * adapter to use city reader, returns record chosen fields
356
     *
357
     * @param Reader $reader
358
     * @param string $ip
359
     * @return array
360
     */
361 3
    protected function cityAdapter(Reader $reader, string $ip): array
362
    {
363 3
        $ip = trim($ip);
364
        try {
365 3
            $record = $reader->city($ip);
366
            return [
367 1
                $ip,
368 1
                $record->country->isoCode,
369 1
                $record->city->name ?: '?',
370 1
                $record->location->latitude,
371 1
                $record->location->longitude,
372 1
                $record->location->accuracyRadius,
373
            ];
374 2
        } catch (AddressNotFoundException $e) {
375 1
            return array_fill(0, 6, '?');
376 1
        } catch (\InvalidArgumentException $e) {
377 1
            return array_fill(0, 6, '?');
378
        }
379
    }
380
381
    /**
382
     * adapter to use country reader, returns record chosen fields
383
     *
384
     * @param Reader $reader
385
     * @param string $ip
386
     * @return array
387
     */
388 3
    protected function countryAdapter(Reader $reader, string $ip): array
389
    {
390 3
        $ip = trim($ip);
391
        try {
392 3
            $record = $reader->country($ip);
393 1
            return [$ip, $record->country->isoCode];
394 2
        } catch (AddressNotFoundException $e) {
395 1
            return array_fill(0, 2, '?');
396 1
        } catch (\InvalidArgumentException $e) {
397 1
            return array_fill(0, 2, '?');
398
        }
399
    }
400
401
    /**
402
     * adapter to use asn reader, returns record chosen fields
403
     *
404
     * @param Reader $reader
405
     * @param string $ip
406
     * @return array
407
     */
408 3
    protected function asnAdapter(Reader $reader, string $ip): array
409
    {
410 3
        $ip = trim($ip);
411 3
        $badRes = array_fill(0, 3, '?');
412
        try {
413 3
            $record = $reader->asn($ip);
414
            return [
415 1
                $ip,
416 1
                $record->autonomousSystemNumber,
417 1
                $record->autonomousSystemOrganization
418
            ];
419 2
        } catch (AddressNotFoundException $e) {
420 1
            return $badRes;
421 1
        } catch (\InvalidArgumentException $e) {
422 1
            return $badRes;
423
        }
424
    }
425
}
426