Completed
Push — master ( 1bdb70...c01c5d )
by Tobias
02:40
created

GeoIPs   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 174
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 76.27%

Importance

Changes 0
Metric Value
wmc 30
lcom 1
cbo 9
dl 0
loc 174
ccs 45
cts 59
cp 0.7627
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A geocodeQuery() 0 19 4
A reverseQuery() 0 4 1
A getName() 0 4 1
D executeQuery() 0 89 22
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Geocoder package.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT License
11
 */
12
13
namespace Geocoder\Provider\GeoIPs;
14
15
use Geocoder\Collection;
16
use Geocoder\Exception\InvalidArgument;
17
use Geocoder\Exception\InvalidCredentials;
18
use Geocoder\Exception\InvalidServerResponse;
19
use Geocoder\Exception\QuotaExceeded;
20
use Geocoder\Exception\UnsupportedOperation;
21
use Geocoder\Model\Address;
22
use Geocoder\Model\AddressCollection;
23
use Geocoder\Query\GeocodeQuery;
24
use Geocoder\Query\ReverseQuery;
25
use Geocoder\Http\Provider\AbstractHttpProvider;
26
use Geocoder\Provider\Provider;
27
use Http\Client\HttpClient;
28
29
/**
30
 * @author Andrea Cristaudo <[email protected]>
31
 * @author Arthur Bodera <[email protected]>
32
 *
33
 * @see http://www.geoips.com/en/developer/api-guide
34
 */
35
final class GeoIPs extends AbstractHttpProvider implements Provider
36
{
37
    /**
38
     * @var string
39
     */
40
    const GEOCODE_ENDPOINT_URL = 'https://api.geoips.com/ip/%s/key/%s/output/json/timezone/true/';
41
42
    const CODE_SUCCESS = '200_1'; // The following results has been returned.
43
44
    const CODE_NOT_FOUND = '200_2'; // No result set has been returned.
45
46
    const CODE_BAD_KEY = '400_1'; // Error in the URI - The API call should include a API key parameter.
47
48
    const CODE_BAD_IP = '400_2'; // Error in the URI - The API call should include a valid IP address.
49
50
    const CODE_NOT_AUTHORIZED = '403_1'; // The API key associated with your request was not recognized.
51
52
    const CODE_ACCOUNT_INACTIVE = '403_2'; // The API key has not been approved or has been disabled.
53
54
    const CODE_LIMIT_EXCEEDED = '403_3'; // The service you have requested is over capacity.
55
56
    /**
57
     * @var string
58
     */
59
    private $apiKey;
60
61
    /**
62
     * @param HttpClient $client An HTTP adapter
63
     * @param string     $apiKey An API key
64
     */
65 22
    public function __construct(HttpClient $client, string $apiKey)
66
    {
67 22
        if (empty($apiKey)) {
68
            throw new InvalidCredentials('No API key provided.');
69
        }
70
71 22
        $this->apiKey = $apiKey;
72 22
        parent::__construct($client);
73 22
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 20
    public function geocodeQuery(GeocodeQuery $query): Collection
79
    {
80 20
        $address = $query->getText();
81 20
        if (!filter_var($address, FILTER_VALIDATE_IP)) {
82 1
            throw new UnsupportedOperation('The GeoIPs provider does not support street addresses, only IPv4 addresses.');
83
        }
84
85 19
        if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
86 1
            throw new UnsupportedOperation('The GeoIPs provider does not support IPv6 addresses, only IPv4 addresses.');
87
        }
88
89 18
        if ('127.0.0.1' === $address) {
90 1
            return new AddressCollection([$this->getLocationForLocalhost()]);
91
        }
92
93 17
        $query = sprintf(self::GEOCODE_ENDPOINT_URL, $address, $this->apiKey);
94
95 17
        return $this->executeQuery($query);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101 1
    public function reverseQuery(ReverseQuery $query): Collection
102
    {
103 1
        throw new UnsupportedOperation('The GeoIPs provider is not able to do reverse geocoding.');
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 6
    public function getName(): string
110
    {
111 6
        return 'geo_ips';
112
    }
113
114
    /**
115
     * @param string $url
116
     *
117
     * @return AddressCollection
118
     */
119 17
    private function executeQuery(string $url): AddressCollection
120
    {
121 17
        $content = $this->getUrlContents($url);
122 10
        $json = json_decode($content, true);
123
124 10
        if (isset($json['error'])) {
125 5
            switch ($json['error']['code']) {
126 5
                case static::CODE_BAD_IP:
127 1
                    throw new InvalidArgument('The API call should include a valid IP address.');
128 4
                case static::CODE_BAD_KEY:
129 1
                    throw new InvalidCredentials('The API call should include a API key parameter.');
130 3
                case static::CODE_NOT_AUTHORIZED:
131 1
                    throw new InvalidCredentials('The API key associated with your request was not recognized.');
132 2
                case static::CODE_ACCOUNT_INACTIVE:
133 1
                    throw new InvalidCredentials('The API key has not been approved or has been disabled.');
134 1
                case static::CODE_LIMIT_EXCEEDED:
135 1
                    throw new QuotaExceeded('The service you have requested is over capacity.');
136
                default:
137
                    throw new InvalidServerResponse(sprintf(
138
                        'GeoIPs error %s%s%s%s - query: %s',
139
                        $json['error']['code'],
140
                        isset($json['error']['status']) ? ', '.$json['error']['status'] : '',
141
                        isset($json['error']['message']) ? ', '.$json['error']['message'] : '',
142
                        isset($json['error']['notes']) ? ', '.$json['error']['notes'] : '',
143
                        $url
144
                    ));
145
            }
146
        }
147
148 5
        if (!is_array($json) || empty($json) || empty($json['response']) || empty($json['response']['code'])) {
149
            throw InvalidServerResponse::create($url);
150
        }
151
152 5
        $response = $json['response'];
153
154
        // Check response code
155 5
        switch ($response['code']) {
156 5
            case static::CODE_SUCCESS:
157
                // everything is ok
158 4
                break;
159 1
            case static::CODE_NOT_FOUND:
160 1
                return new AddressCollection([]);
161
            default:
162
                throw new InvalidServerResponse(sprintf(
163
                    'The GeoIPs API returned unknown result code "%s" for query: "%s".',
164
                    $response['code'],
165
                    $url
166
                ));
167
        }
168
169
        // Make sure that we do have proper result array
170 4
        if (empty($response['location']) || !is_array($response['location'])) {
171
            return new AddressCollection([]);
172
        }
173
174 4
        $location = array_map(function ($value) {
175 4
            return '' === $value ? null : $value;
176 4
        }, $response['location']);
177
178
        $adminLevels = [];
179
180
        if (null !== $location['region_name'] || null !== $location['region_code']) {
181
            $adminLevels[] = [
182
                'name' => $location['region_name'],
183
                'code' => $location['region_code'],
184
                'level' => 1,
185
            ];
186
        }
187
188
        if (null !== $location['county_name']) {
189
            $adminLevels[] = [
190
                'name' => $location['county_name'],
191
                'level' => 2,
192
            ];
193
        }
194
195
        $results = Address::createFromArray([
196
            'providedBy' => $this->getName(),
197
            'country' => $location['country_name'],
198
            'countryCode' => $location['country_code'],
199
            'adminLevels' => $adminLevels,
200
            'locality' => $location['city_name'],
201
            'latitude' => $location['latitude'],
202
            'longitude' => $location['longitude'],
203
            'timezone' => $location['timezone'],
204
        ]);
205
206
        return new AddressCollection([$results]);
207
    }
208
}
209