1<?php
2
3namespace GeoIp2\WebService;
4
5use GeoIp2\Exception\AddressNotFoundException;
6use GeoIp2\Exception\AuthenticationException;
7use GeoIp2\Exception\GeoIp2Exception;
8use GeoIp2\Exception\HttpException;
9use GeoIp2\Exception\InvalidRequestException;
10use GeoIp2\Exception\OutOfQueriesException;
11use GeoIp2\ProviderInterface;
12use MaxMind\WebService\Client as WsClient;
13
14/**
15 * This class provides a client API for all the GeoIP2 Precision web services.
16 * The services are Country, City, and Insights. Each service returns a
17 * different set of data about an IP address, with Country returning the
18 * least data and Insights the most.
19 *
20 * Each web service is represented by a different model class, and these model
21 * classes in turn contain multiple record classes. The record classes have
22 * attributes which contain data about the IP address.
23 *
24 * If the web service does not return a particular piece of data for an IP
25 * address, the associated attribute is not populated.
26 *
27 * The web service may not return any information for an entire record, in
28 * which case all of the attributes for that record class will be empty.
29 *
30 * ## Usage ##
31 *
32 * The basic API for this class is the same for all of the web service end
33 * points. First you create a web service object with your MaxMind `$accountId`
34 * and `$licenseKey`, then you call the method corresponding to a specific end
35 * point, passing it the IP address you want to look up.
36 *
37 * If the request succeeds, the method call will return a model class for
38 * the service you called. This model in turn contains multiple record
39 * classes, each of which represents part of the data returned by the web
40 * service.
41 *
42 * If the request fails, the client class throws an exception.
43 */
44class Client implements ProviderInterface
45{
46    private $locales;
47    private $client;
48    private static $basePath = '/geoip/v2.1';
49
50    const VERSION = 'v2.9.0';
51
52    /**
53     * Constructor.
54     *
55     * @param int    $accountId  your MaxMind account ID
56     * @param string $licenseKey your MaxMind license key
57     * @param array  $locales    list of locale codes to use in name property
58     *                           from most preferred to least preferred
59     * @param array  $options    array of options. Valid options include:
60     *                           * `host` - The host to use when querying the web service.
61     *                           * `timeout` - Timeout in seconds.
62     *                           * `connectTimeout` - Initial connection timeout in seconds.
63     *                           * `proxy` - The HTTP proxy to use. May include a schema, port,
64     *                           username, and password, e.g.,
65     *                           `http://username:password@127.0.0.1:10`.
66     */
67    public function __construct(
68        $accountId,
69        $licenseKey,
70        $locales = ['en'],
71        $options = []
72    ) {
73        $this->locales = $locales;
74
75        // This is for backwards compatibility. Do not remove except for a
76        // major version bump.
77        if (is_string($options)) {
78            $options = ['host' => $options];
79        }
80
81        if (!isset($options['host'])) {
82            $options['host'] = 'geoip.maxmind.com';
83        }
84
85        $options['userAgent'] = $this->userAgent();
86
87        $this->client = new WsClient($accountId, $licenseKey, $options);
88    }
89
90    private function userAgent()
91    {
92        return 'GeoIP2-API/' . self::VERSION;
93    }
94
95    /**
96     * This method calls the GeoIP2 Precision: City service.
97     *
98     * @param string $ipAddress IPv4 or IPv6 address as a string. If no
99     *                          address is provided, the address that the web service is called
100     *                          from will be used.
101     *
102     * @throws \GeoIp2\Exception\AddressNotFoundException if the address you
103     *                                                    provided is not in our database (e.g., a private address).
104     * @throws \GeoIp2\Exception\AuthenticationException  if there is a problem
105     *                                                    with the account ID or license key that you provided
106     * @throws \GeoIp2\Exception\OutOfQueriesException    if your account is out
107     *                                                    of queries
108     * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
109     *                                                    invalid for some other reason.  This may indicate an issue
110     *                                                    with this API. Please report the error to MaxMind.
111     * @throws \GeoIp2\Exception\HttpException            if an unexpected HTTP error code or message was returned.
112     *                                                    This could indicate a problem with the connection between
113     *                                                    your server and the web service or that the web service
114     *                                                    returned an invalid document or 500 error code.
115     * @throws \GeoIp2\Exception\GeoIp2Exception          This serves as the parent
116     *                                                    class to the above exceptions. It will be thrown directly
117     *                                                    if a 200 status code is returned but the body is invalid.
118     *
119     * @return \GeoIp2\Model\City
120     */
121    public function city($ipAddress = 'me')
122    {
123        return $this->responseFor('city', 'City', $ipAddress);
124    }
125
126    /**
127     * This method calls the GeoIP2 Precision: Country service.
128     *
129     * @param string $ipAddress IPv4 or IPv6 address as a string. If no
130     *                          address is provided, the address that the web service is called
131     *                          from will be used.
132     *
133     * @throws \GeoIp2\Exception\AddressNotFoundException if the address you provided is not in our database (e.g.,
134     *                                                    a private address).
135     * @throws \GeoIp2\Exception\AuthenticationException  if there is a problem
136     *                                                    with the account ID or license key that you provided
137     * @throws \GeoIp2\Exception\OutOfQueriesException    if your account is out of queries
138     * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
139     *                                                    invalid for some other reason.  This may indicate an
140     *                                                    issue with this API. Please report the error to MaxMind.
141     * @throws \GeoIp2\Exception\HttpException            if an unexpected HTTP error
142     *                                                    code or message was returned. This could indicate a problem
143     *                                                    with the connection between your server and the web service
144     *                                                    or that the web service returned an invalid document or 500
145     *                                                    error code.
146     * @throws \GeoIp2\Exception\GeoIp2Exception          This serves as the parent class to the above exceptions. It
147     *                                                    will be thrown directly if a 200 status code is returned but
148     *                                                    the body is invalid.
149     *
150     * @return \GeoIp2\Model\Country
151     */
152    public function country($ipAddress = 'me')
153    {
154        return $this->responseFor('country', 'Country', $ipAddress);
155    }
156
157    /**
158     * This method calls the GeoIP2 Precision: Insights service.
159     *
160     * @param string $ipAddress IPv4 or IPv6 address as a string. If no
161     *                          address is provided, the address that the web service is called
162     *                          from will be used.
163     *
164     * @throws \GeoIp2\Exception\AddressNotFoundException if the address you
165     *                                                    provided is not in our database (e.g., a private address).
166     * @throws \GeoIp2\Exception\AuthenticationException  if there is a problem
167     *                                                    with the account ID or license key that you provided
168     * @throws \GeoIp2\Exception\OutOfQueriesException    if your account is out
169     *                                                    of queries
170     * @throws \GeoIp2\Exception\InvalidRequestException} if your request was received by the web service but is
171     *                                                    invalid for some other reason.  This may indicate an
172     *                                                    issue with this API. Please report the error to MaxMind.
173     * @throws \GeoIp2\Exception\HttpException            if an unexpected HTTP error code or message was returned.
174     *                                                    This could indicate a problem with the connection between
175     *                                                    your server and the web service or that the web service
176     *                                                    returned an invalid document or 500 error code.
177     * @throws \GeoIp2\Exception\GeoIp2Exception          This serves as the parent
178     *                                                    class to the above exceptions. It will be thrown directly
179     *                                                    if a 200 status code is returned but the body is invalid.
180     *
181     * @return \GeoIp2\Model\Insights
182     */
183    public function insights($ipAddress = 'me')
184    {
185        return $this->responseFor('insights', 'Insights', $ipAddress);
186    }
187
188    private function responseFor($endpoint, $class, $ipAddress)
189    {
190        $path = implode('/', [self::$basePath, $endpoint, $ipAddress]);
191
192        try {
193            $body = $this->client->get('GeoIP2 ' . $class, $path);
194        } catch (\MaxMind\Exception\IpAddressNotFoundException $ex) {
195            throw new AddressNotFoundException(
196                $ex->getMessage(),
197                $ex->getStatusCode(),
198                $ex
199            );
200        } catch (\MaxMind\Exception\AuthenticationException $ex) {
201            throw new AuthenticationException(
202                $ex->getMessage(),
203                $ex->getStatusCode(),
204                $ex
205            );
206        } catch (\MaxMind\Exception\InsufficientFundsException $ex) {
207            throw new OutOfQueriesException(
208                $ex->getMessage(),
209                $ex->getStatusCode(),
210                $ex
211            );
212        } catch (\MaxMind\Exception\InvalidRequestException $ex) {
213            throw new InvalidRequestException(
214                $ex->getMessage(),
215                $ex->getErrorCode(),
216                $ex->getStatusCode(),
217                $ex->getUri(),
218                $ex
219            );
220        } catch (\MaxMind\Exception\HttpException $ex) {
221            throw new HttpException(
222                $ex->getMessage(),
223                $ex->getStatusCode(),
224                $ex->getUri(),
225                $ex
226            );
227        } catch (\MaxMind\Exception\WebServiceException $ex) {
228            throw new GeoIp2Exception(
229                $ex->getMessage(),
230                $ex->getCode(),
231                $ex
232            );
233        }
234
235        $class = 'GeoIp2\\Model\\' . $class;
236
237        return new $class($body, $this->locales);
238    }
239}
240