Completed
Push — master ( f46bec...dd2aaa )
by Yasir
01:21
created

GeoIPLocation   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 198
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 19
lcom 1
cbo 4
dl 0
loc 198
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A getIpAddress() 0 14 5
A isIpAddressValid() 0 8 2
A getGeoLocation() 0 20 3
A getRequestUrl() 0 9 1
A getRequestOptions() 0 8 1
A getMappedLocation() 0 17 1
A getStatus() 0 4 2
A getResponsePhrase() 0 8 1
A handleError() 0 6 1
A mapProperty() 0 8 2
1
<?php
2
3
namespace ipGeolocation;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Exception\RequestException;
7
use Psr\Http\Message\ResponseInterface;
8
9
/**
10
 * This class gets geo location information from ip address
11
 *
12
 * PHP version 7
13
 *
14
 * @author yasir khurshid <[email protected]>
15
 */
16
class GeoIPLocation
17
{
18
    /**
19
     * Indices for getting user IP Address
20
     *
21
     * @var array
22
     */
23
    private $remotes = array(
24
        'REMOTE_ADDR',
25
        'HTTP_X_FORWARDED_FOR',
26
        'HTTP_CLIENT_IP',
27
        'HTTP_X_FORWARDED',
28
        'HTTP_FORWARDED_FOR',
29
        'HTTP_FORWARDED',
30
        'HTTP_X_CLUSTER_CLIENT_IP',
31
    );
32
33
    /**
34
     * Return geo location data based on the user's Ip Address
35
     *
36
     * Example: ip-address => {"status": "success", "country": "COUNTRY", "countryCode": "COUNTRY CODE", "region": "REGION CODE"}
37
     *
38
     * @return Location
39
     */
40 5
    public function getGeoLocation()
41
    {
42
        try {
43 5
            $response = (new Client())->get(
44 5
                $this->getRequestUrl($this->getIpAddress()),
45 5
                $this->getRequestOptions()
46
            );
47
48 4
            if (\in_array($response->getStatusCode(), range(200, 299))) {
49
50 3
                return $this->getMappedLocation($response);
51
            }
52
53 1
            return $this->handleError($this->getResponsePhrase($response));
54
55 1
        } catch (RequestException $e) {
56
57 1
            return $this->handleError($e->getMessage());
58
        }
59
    }
60
61
    /**
62
     * Return Ip address of the user
63
     *
64
     * @return string
65
     */
66 5
    public function getIpAddress(): string
67
    {
68 5
        foreach ($this->remotes as $remote) {
69 5
            if (isset($_SERVER[$remote])) {
70 5
                foreach (explode(',', $_SERVER[$remote]) as $ipAddress) {
71 5
                    if ($this->isIpAddressValid($ipAddress)) {
72 5
                        return $ipAddress;
73
                    }
74
                }
75
            }
76
        }
77
78 4
        return ApiConfig::DEFAULT_IP_ADDRESS;
79
    }
80
81
    /**
82
     * Check if ip address is valid or not
83
     *
84
     * @param string $ipAddress Ip Address
85
     *
86
     * @return bool
87
     */
88 5
    public function isIpAddressValid(string $ipAddress): bool
89
    {
90 5
        if (\filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
91 1
            return true;
92
        }
93
94 4
        return false;
95
    }
96
97
    /**
98
     * Return request Url
99
     *
100
     * Important: access modifier is public for unit testing purpose.
101
     *
102
     * @param string $ipAddress Ip Address
103
     *
104
     * @return string
105
     */
106 3
    public function getRequestUrl(string $ipAddress): string
107
    {
108 3
        return \sprintf(
109 3
            '%s/%s/%s',
110 3
            ApiConfig::IP_API_BASE_URL,
111 3
            ApiConfig::RESPONSE_FORMAT,
112 3
            $ipAddress
113
        );
114
    }
115
116
    /**
117
     * Return options array for http request
118
     *
119
     * @return array
120
     */
121 5
    private function getRequestOptions(): array
122
    {
123
        return array(
124 5
            'timeout' => ApiConfig::TIMEOUT,
125 5
            'connect_timeout' => ApiConfig::CONNECTION_TIMEOUT,
126
            'headers' => null,
127
        );
128
    }
129
130
    /**
131
     * Return mapped Location Object
132
     *
133
     * @param ResponseInterface $response Response
134
     *
135
     * @return Location
136
     */
137 3
    private function getMappedLocation(ResponseInterface $response): Location
138
    {
139 3
        $data =  \json_decode($response->getBody());
140
141 3
        return (new Location())
142 3
            ->setStatus($this->getStatus($this->mapProperty($data, 'status')))
143 3
            ->setMessage($this->mapProperty($data, 'message'))
144 3
            ->setCity($this->mapProperty($data, 'city'))
145 3
            ->setCountry($this->mapProperty($data, 'country'))
146 3
            ->setCountryCode($this->mapProperty($data, 'countryCode'))
147 3
            ->setLatitude($this->mapProperty($data, 'lat'))
148 3
            ->setLongitude($this->mapProperty($data, 'lon'))
149 3
            ->setRegionCode($this->mapProperty($data, 'region'))
150 3
            ->setRegionName($this->mapProperty($data, 'regionName'))
151 3
            ->setTimezone($this->mapProperty($data, 'timezone'))
152 3
            ->setPostalCode($this->mapProperty($data, 'zip'));
153
    }
154
155
    /**
156
     * Return status
157
     *
158
     * @param string $status Status string
159
     *
160
     * @return bool
161
     */
162 3
    private function getStatus(string $status): bool
163
    {
164 3
        return ('success' === $status ? true : false);
165
    }
166
167
    /**
168
     * Return response phrase with corresponding http status code
169
     *
170
     * @param ResponseInterface $response Response
171
     *
172
     * @return string
173
     */
174 1
    private function getResponsePhrase(ResponseInterface $response): string
175
    {
176 1
        return \sprintf(
177 1
            'Request failed with response code: %d and response: %s',
178 1
            $response->getStatusCode(),
179 1
            $response->getReasonPhrase()
180
        );
181
    }
182
183
    /**
184
     * Handle error
185
     *
186
     * @param string $message Message
187
     *
188
     * @return Location
189
     */
190 2
    private function handleError(string $message): Location
191
    {
192 2
        return (new Location())
193 2
            ->setStatus(false)
194 2
            ->setMessage($message);
195
    }
196
197
    /**
198
     * Map object property
199
     *
200
     * @param \stdClass $object   Object
201
     * @param string $property Property
202
     *
203
     * @return null|string
204
     */
205 3
    private function mapProperty(\stdClass $object, string $property): ?string
206
    {
207 3
        if (isset($object->$property)) {
208 3
            return $object->$property;
209
        }
210
211 3
        return null;
212
    }
213
}
214