Completed
Push — master ( 580164...80180d )
by Pierre
01:35
created

Ip::getUpdater()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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
        while (($ip = fgets($handle, self::BUFFER)) !== false) {
0 ignored issues
show
Bug introduced by
$handle of type false is incompatible with the type resource expected by parameter $handle of fgets(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
        while (($ip = fgets(/** @scrutinizer ignore-type */ $handle, self::BUFFER)) !== false) {
Loading history...
175 6
            $this->addIp($ip);
176
        }
177 6
        fclose($handle);
0 ignored issues
show
Bug introduced by
$handle of type false is incompatible with the type resource expected by parameter $handle of fclose(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
        fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
178 6
        return $this;
179
    }
180
181
    /**
182
     * returns ip list
183
     *
184
     * @return array
185
     */
186 7
    public function getIpList(): array
187
    {
188 7
        return $this->ipList;
189
    }
190
191
    /**
192
     * geo process ip list
193
     *
194
     * @return Ip
195
     */
196 7
    public function process(): Ip
197
    {
198 7
        $this->results = [];
199 7
        $reader = $this->getReader();
200 7
        $li = count($this->ipList);
201 7
        for ($c = 0; $c < $li; $c++) {
202 7
            $this->results[] = call_user_func_array(
203 7
                [$this, $this->adapter],
204 7
                [$reader, $this->ipList[$c]]
205
            );
206
        }
207 7
        return $this;
208
    }
209
210
    /**
211
     * return reader belongs to current adapter
212
     *
213
     * @return Reader
214
     */
215 17
    public function getReader(): Reader
216
    {
217 17
        switch ($this->adapter) {
218 17
            case self::ADAPTER_CITY:
219 7
                $reader = $this->readerCity;
220 7
                break;
221 11
            case self::ADAPTER_COUNTRY:
222 8
                $reader = $this->readerCountry;
223 8
                break;
224 4
            case self::ADAPTER_ASN:
225 4
                $reader = $this->readerAsn;
226 4
                break;
227
        }
228 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...
229
    }
230
231
    /**
232
     * returns Updater instance
233
     *
234
     * @return Updater
235
     */
236 2
    public function getUpdater(): Updater
237
    {
238 2
        return $this->updater->setAdapter($this->adapter);
239
    }
240
241
    /**
242
     * sort result by column number
243
     *
244
     * @param integer $col
245
     * @return Ip
246
     */
247 1
    public function sort(int $col = 1): Ip
248
    {
249 1
        $this->sortCol = $col;
250 1
        usort($this->results, [$this, 'compareArray']);
251 1
        return $this;
252
    }
253
254
    /**
255
     * compare two array for a given column
256
     *
257
     * @param array $a
258
     * @param array $b
259
     * @return integer
260
     */
261 1
    protected function compareArray(array $a, array $b): int
262
    {
263 1
        return strcmp($a[$this->sortCol], $b[$this->sortCol]);
264
    }
265
266
    /**
267
     * returns result as array
268
     *
269
     * @return array
270
     */
271 6
    public function toArray(): array
272
    {
273 6
        return $this->results;
274
    }
275
276
    /**
277
     * returns result as csv
278
     *
279
     * @param string $sep
280
     * @return void
281
     */
282 1
    public function toCsv(string $sep = ';'): string
283
    {
284 1
        $lc = count($this->results);
285 1
        $csv = '';
286 1
        for ($c = 0; $c < $lc; $c++) {
287 1
            $csv .= implode($sep, $this->results[$c]) . "\n";
288
        }
289 1
        return $csv;
290
    }
291
292
    /**
293
     * returns result as json
294
     *
295
     * @return string
296
     */
297 1
    public function toJson(): string
298
    {
299 1
        $lc = count($this->results);
300 1
        $json = [];
301 1
        $headers = $this->getHeaders();
302 1
        for ($c = 0; $c < $lc; $c++) {
303 1
            $json[] = array_combine($headers, $this->results[$c]);
304
        }
305 1
        return json_encode($json, JSON_PRETTY_PRINT);
306
    }
307
308
    /**
309
     * set readers, one per db file
310
     *
311
     * @return Ip
312
     */
313 1
    protected function setReaders(): Ip
314
    {
315 1
        $this->readerCity = new Reader(
316 1
            __DIR__ . self::DB_PATH . self::DB_CITY_FILENAME,
317 1
            $this->readerLocales
318
        );
319 1
        $this->readerCountry = new Reader(
320 1
            __DIR__ . self::DB_PATH . self::DB_COUNTRY_FILENAME,
321 1
            $this->readerLocales
322
        );
323 1
        $this->readerAsn = new Reader(
324 1
            __DIR__ . self::DB_PATH . self::DB_ASN_FILENAME,
325 1
            $this->readerLocales
326
        );
327 1
        return $this;
328
    }
329
330
    /**
331
     * returns headers
332
     *
333
     * @return array
334
     */
335 1
    protected function getHeaders(): array
336
    {
337 1
        $headers = [];
338 1
        switch ($this->adapter) {
339 1
            case self::ADAPTER_CITY:
340 1
                $headers = self::HEADERS_COMMON;
341 1
                break;
342 1
            case self::ADAPTER_COUNTRY:
343 1
                $headers = array_slice(self::HEADERS_COMMON, 0, 2);
344 1
                break;
345 1
            case self::ADAPTER_ASN:
346 1
                $headers = self::HEADERS_ASN;
347 1
                break;
348
        }
349 1
        return $headers;
350
    }
351
352
    /**
353
     * adapter to use city reader, returns record chosen fields
354
     *
355
     * @param Reader $reader
356
     * @param string $ip
357
     * @return array
358
     */
359 3
    protected function cityAdapter(Reader $reader, string $ip): array
360
    {
361 3
        $ip = trim($ip);
362
        try {
363 3
            $record = $reader->city($ip);
364
            return [
365 1
                $ip,
366 1
                $record->country->isoCode,
367 1
                $record->city->name ?: '?',
368 1
                $record->location->latitude,
369 1
                $record->location->longitude,
370 1
                $record->location->accuracyRadius,
371
            ];
372 2
        } catch (AddressNotFoundException $e) {
373 1
            return array_fill(0, 6, '?');
374 1
        } catch (\InvalidArgumentException $e) {
375 1
            return array_fill(0, 6, '?');
376
        }
377
    }
378
379
    /**
380
     * adapter to use country reader, returns record chosen fields
381
     *
382
     * @param Reader $reader
383
     * @param string $ip
384
     * @return array
385
     */
386 3
    protected function countryAdapter(Reader $reader, string $ip): array
387
    {
388 3
        $ip = trim($ip);
389
        try {
390 3
            $record = $reader->country($ip);
391 1
            return [$ip, $record->country->isoCode];
392 2
        } catch (AddressNotFoundException $e) {
393 1
            return array_fill(0, 2, '?');
394 1
        } catch (\InvalidArgumentException $e) {
395 1
            return array_fill(0, 2, '?');
396
        }
397
    }
398
399
    /**
400
     * adapter to use asn reader, returns record chosen fields
401
     *
402
     * @param Reader $reader
403
     * @param string $ip
404
     * @return array
405
     */
406 3
    protected function asnAdapter(Reader $reader, string $ip): array
407
    {
408 3
        $ip = trim($ip);
409 3
        $badRes = array_fill(0, 3, '?');
410
        try {
411 3
            $record = $reader->asn($ip);
412
            return [
413 1
                $ip,
414 1
                $record->autonomousSystemNumber,
415 1
                $record->autonomousSystemOrganization
416
            ];
417 2
        } catch (AddressNotFoundException $e) {
418 1
            return $badRes;
419 1
        } catch (\InvalidArgumentException $e) {
420 1
            return $badRes;
421
        }
422
    }
423
}
424