From f200d4dc3975a43fd4d674c022d19215c3c0e8ce Mon Sep 17 00:00:00 2001 From: mateen993 Date: Mon, 5 Oct 2026 16:26:52 +0500 Subject: [PATCH] Add IPGeolocation.io provider --- .github/workflows/provider.yml | 1 + .github/workflows/subtree.yml | 1 + README.md | 1 + phpunit.xml.dist | 1 + src/Provider/IPGeolocation/.gitattributes | 4 + .../.github/workflows/provider.yml | 33 +++ src/Provider/IPGeolocation/.gitignore | 3 + src/Provider/IPGeolocation/CHANGELOG.md | 7 + src/Provider/IPGeolocation/IPGeolocation.php | 152 ++++++++++++ src/Provider/IPGeolocation/LICENSE | 21 ++ src/Provider/IPGeolocation/Readme.md | 39 +++ ...o_3b1b489ab7d6998edb0e17ed3cf573a42f1d8761 | 1 + ...o_a328c24e92ee2cf0b70e454939e54f43d844a141 | 1 + .../IPGeolocation/Tests/IPGeolocationTest.php | 234 ++++++++++++++++++ .../IPGeolocation/Tests/IntegrationTest.php | 39 +++ src/Provider/IPGeolocation/composer.json | 46 ++++ src/Provider/IPGeolocation/phpunit.xml.dist | 21 ++ 17 files changed, 605 insertions(+) create mode 100644 src/Provider/IPGeolocation/.gitattributes create mode 100644 src/Provider/IPGeolocation/.github/workflows/provider.yml create mode 100644 src/Provider/IPGeolocation/.gitignore create mode 100644 src/Provider/IPGeolocation/CHANGELOG.md create mode 100644 src/Provider/IPGeolocation/IPGeolocation.php create mode 100644 src/Provider/IPGeolocation/LICENSE create mode 100644 src/Provider/IPGeolocation/Readme.md create mode 100644 src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_3b1b489ab7d6998edb0e17ed3cf573a42f1d8761 create mode 100644 src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_a328c24e92ee2cf0b70e454939e54f43d844a141 create mode 100644 src/Provider/IPGeolocation/Tests/IPGeolocationTest.php create mode 100644 src/Provider/IPGeolocation/Tests/IntegrationTest.php create mode 100644 src/Provider/IPGeolocation/composer.json create mode 100644 src/Provider/IPGeolocation/phpunit.xml.dist diff --git a/.github/workflows/provider.yml b/.github/workflows/provider.yml index ea7b27fa7..82c747902 100644 --- a/.github/workflows/provider.yml +++ b/.github/workflows/provider.yml @@ -34,6 +34,7 @@ jobs: - HostIp - IP2Location # - IP2LocationBinary + - IPGeolocation - IpInfo - IpInfoDb - Ipstack diff --git a/.github/workflows/subtree.yml b/.github/workflows/subtree.yml index 577069128..322d09b49 100644 --- a/.github/workflows/subtree.yml +++ b/.github/workflows/subtree.yml @@ -101,6 +101,7 @@ jobs: - { folder: HostIp, repository: host-ip-provider } - { folder: IP2Location, repository: ip2location-provider } - { folder: IP2LocationBinary, repository: ip2location-binary-provider } + - { folder: IPGeolocation, repository: ipgeolocation-provider } - { folder: IpInfo, repository: ip-info-provider } - { folder: IpInfoDb, repository: ip-info-db-provider } - { folder: Ipstack, repository: ipstack-provider } diff --git a/README.md b/README.md index 31b250196..fe6212fee 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Provider | Package | Features | Stats [HostIp](https://github.com/geocoder-php/host-ip-provider) | `geocoder-php/host-ip-provider` | IPv4
[Website](http://www.hostip.info/use.html) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/host-ip-provider/v/stable)](https://packagist.org/packages/geocoder-php/host-ip-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/host-ip-provider/downloads)](https://packagist.org/packages/geocoder-php/host-ip-provider) [IP2Location](https://github.com/geocoder-php/ip2location-provider) | `geocoder-php/ip2location-provider` | IPv4, IPv6
[Website](https://www.ip2location.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip2location-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip2location-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ip2location-provider/downloads)](https://packagist.org/packages/geocoder-php/ip2location-provider) [IP2Location Binary](https://github.com/geocoder-php/ip2location-binary-provider) | `geocoder-php/ip2location-binary-provider` | IPv4, IPv6
[Website](https://www.ip2location.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip2location-binary-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip2location-binary-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ip2location-binary-provider/downloads)](https://packagist.org/packages/geocoder-php/ip2location-binary-provider) +[IPGeolocation](https://github.com/geocoder-php/ipgeolocation-provider) | `geocoder-php/ipgeolocation-provider` | IPv4, IPv6
[Website](https://ipgeolocation.io/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ipgeolocation-provider/v/stable)](https://packagist.org/packages/geocoder-php/ipgeolocation-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ipgeolocation-provider/downloads)](https://packagist.org/packages/geocoder-php/ipgeolocation-provider) [IpInfo](https://github.com/geocoder-php/ip-info-provider) | `geocoder-php/ip-info-provider` | IPv4, IPv6
[Website](https://ipinfo.io/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip-info-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip-info-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ip-info-provider/downloads)](https://packagist.org/packages/geocoder-php/ip-info-provider) [IpInfoDB](https://github.com/geocoder-php/ip-info-db-provider) | `geocoder-php/ip-info-db-provider` | IPv4
[Website](http://ipinfodb.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip-info-db-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip-info-db-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ip-info-db-provider/downloads)](https://packagist.org/packages/geocoder-php/ip-info-db-provider) [ipstack](https://github.com/geocoder-php/ipstack-provider) | `geocoder-php/ipstack-provider` | IPv4, IPv6
[Website](https://ipstack.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ipstack-provider/v/stable)](https://packagist.org/packages/geocoder-php/ipstack-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ipstack-provider/downloads)](https://packagist.org/packages/geocoder-php/ipstack-provider) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d01fae0f5..a886184a5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -42,6 +42,7 @@ + diff --git a/src/Provider/IPGeolocation/.gitattributes b/src/Provider/IPGeolocation/.gitattributes new file mode 100644 index 000000000..d04504afd --- /dev/null +++ b/src/Provider/IPGeolocation/.gitattributes @@ -0,0 +1,4 @@ +.gitattributes export-ignore +.travis.yml export-ignore +phpunit.xml.dist export-ignore +Tests/ export-ignore diff --git a/src/Provider/IPGeolocation/.github/workflows/provider.yml b/src/Provider/IPGeolocation/.github/workflows/provider.yml new file mode 100644 index 000000000..7c6069ea1 --- /dev/null +++ b/src/Provider/IPGeolocation/.github/workflows/provider.yml @@ -0,0 +1,33 @@ +name: Provider + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + name: PHP ${{ matrix.php-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php-version: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] + steps: + - uses: actions/checkout@v4 + - name: Use PHP ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: curl + - name: Validate composer.json and composer.lock + run: composer validate --strict + - name: Install dependencies + run: composer update --prefer-stable --prefer-dist --no-progress + - name: Run test suite + run: composer run-script test-ci + - name: Upload Coverage report + run: | + wget https://scrutinizer-ci.com/ocular.phar + php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml diff --git a/src/Provider/IPGeolocation/.gitignore b/src/Provider/IPGeolocation/.gitignore new file mode 100644 index 000000000..c49a5d8df --- /dev/null +++ b/src/Provider/IPGeolocation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Provider/IPGeolocation/CHANGELOG.md b/src/Provider/IPGeolocation/CHANGELOG.md new file mode 100644 index 000000000..746b7151f --- /dev/null +++ b/src/Provider/IPGeolocation/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 1.0.0 + +First release of this library. diff --git a/src/Provider/IPGeolocation/IPGeolocation.php b/src/Provider/IPGeolocation/IPGeolocation.php new file mode 100644 index 000000000..eb8183300 --- /dev/null +++ b/src/Provider/IPGeolocation/IPGeolocation.php @@ -0,0 +1,152 @@ +apiKey = $apiKey; + parent::__construct($client); + } + + public function geocodeQuery(GeocodeQuery $query): Collection + { + $address = $query->getText(); + + if (!filter_var($address, FILTER_VALIDATE_IP)) { + throw new UnsupportedOperation('The IPGeolocation provider does not support street addresses, only IP addresses.'); + } + + if (in_array($address, ['127.0.0.1', '::1'], true)) { + return new AddressCollection([$this->getLocationForLocalhost()]); + } + + $params = [ + 'apiKey' => $this->apiKey, + 'ip' => $address, + 'fields' => 'location,time_zone.name', + ]; + + if (null !== $query->getLocale()) { + $params['lang'] = $query->getLocale(); + } + + return $this->executeQuery(self::GEOCODE_ENDPOINT_URL.'?'.http_build_query($params)); + } + + public function reverseQuery(ReverseQuery $query): Collection + { + throw new UnsupportedOperation('The IPGeolocation provider is not able to do reverse geocoding.'); + } + + public function getName(): string + { + return 'ipgeolocation'; + } + + private function executeQuery(string $url): AddressCollection + { + $response = $this->getHttpClient()->sendRequest($this->getRequest($url)); + $statusCode = $response->getStatusCode(); + + // 404: the IP is not in the database, 423: the IP is private or reserved + if (404 === $statusCode || 423 === $statusCode) { + return new AddressCollection([]); + } + + if (401 === $statusCode || 403 === $statusCode) { + throw new InvalidCredentials(); + } elseif (429 === $statusCode) { + throw new QuotaExceeded(); + } elseif ($statusCode >= 300) { + throw InvalidServerResponse::create($url, $statusCode); + } + + $body = (string) $response->getBody(); + if ('' === $body) { + throw InvalidServerResponse::emptyResponse($url); + } + + $data = json_decode($body, true); + if (!is_array($data)) { + throw InvalidServerResponse::create($url, $statusCode); + } + + if (!isset($data['location']) || !is_array($data['location'])) { + return new AddressCollection([]); + } + + $location = array_map(fn ($value) => '' === $value ? null : $value, $data['location']); + + $adminLevels = []; + if (isset($location['state_prov'])) { + $adminLevels[] = [ + 'name' => $location['state_prov'], + 'code' => $location['state_code'] ?? null, + 'level' => 1, + ]; + } + if (isset($location['district'])) { + $adminLevels[] = ['name' => $location['district'], 'level' => 2]; + } + + return new AddressCollection([ + Address::createFromArray([ + 'providedBy' => $this->getName(), + 'latitude' => isset($location['latitude']) ? (float) $location['latitude'] : null, + 'longitude' => isset($location['longitude']) ? (float) $location['longitude'] : null, + 'locality' => $location['city'] ?? null, + 'postalCode' => $location['zipcode'] ?? null, + 'adminLevels' => $adminLevels, + 'country' => $location['country_name'] ?? null, + 'countryCode' => $location['country_code2'] ?? null, + 'timezone' => $data['time_zone']['name'] ?? null, + ]), + ]); + } +} diff --git a/src/Provider/IPGeolocation/LICENSE b/src/Provider/IPGeolocation/LICENSE new file mode 100644 index 000000000..8aa8246ef --- /dev/null +++ b/src/Provider/IPGeolocation/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011 β€” William Durand + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/Provider/IPGeolocation/Readme.md b/src/Provider/IPGeolocation/Readme.md new file mode 100644 index 000000000..d658fa84b --- /dev/null +++ b/src/Provider/IPGeolocation/Readme.md @@ -0,0 +1,39 @@ +# IPGeolocation.io Geocoder provider +[![Latest Stable Version](https://poser.pugx.org/geocoder-php/ipgeolocation-provider/v/stable)](https://packagist.org/packages/geocoder-php/ipgeolocation-provider) +[![Total Downloads](https://poser.pugx.org/geocoder-php/ipgeolocation-provider/downloads)](https://packagist.org/packages/geocoder-php/ipgeolocation-provider) +[![Monthly Downloads](https://poser.pugx.org/geocoder-php/ipgeolocation-provider/d/monthly.png)](https://packagist.org/packages/geocoder-php/ipgeolocation-provider) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is the [IPGeolocation.io](https://ipgeolocation.io/) provider from the PHP Geocoder. This is a **READ ONLY** repository. See the +[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation. + +### Install + +```bash +composer require geocoder-php/ipgeolocation-provider +``` + +### Usage + +The provider geocodes IPv4 and IPv6 addresses. It does not support street addresses or reverse geocoding. + +```php +$httpClient = new \Http\Discovery\Psr18Client(); +$provider = new \Geocoder\Provider\IPGeolocation\IPGeolocation($httpClient, 'your-api-key'); + +$result = $provider->geocodeQuery(\Geocoder\Query\GeocodeQuery::create('8.8.8.8')); +``` + +Sign up for a free API key at [app.ipgeolocation.io](https://app.ipgeolocation.io/login). + +### Note + +Results are returned in English by default. Pass a locale with `GeocodeQuery::withLocale()` to request another +language; languages other than English need a paid plan. + +IP addresses that are private, reserved or missing from the IPGeolocation.io database return an empty collection. + +### Contribute + +Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or +report any issues you find on the [issue tracker](https://github.com/geocoder-php/Geocoder/issues). diff --git a/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_3b1b489ab7d6998edb0e17ed3cf573a42f1d8761 b/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_3b1b489ab7d6998edb0e17ed3cf573a42f1d8761 new file mode 100644 index 000000000..65d9142d1 --- /dev/null +++ b/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_3b1b489ab7d6998edb0e17ed3cf573a42f1d8761 @@ -0,0 +1 @@ +s:562:"{"ip":"83.227.123.8","location":{"continent_code":"EU","continent_name":"Europe","country_code2":"SE","country_code3":"SWE","country_name":"Sweden","country_name_official":"Kingdom of Sweden","country_capital":"Stockholm","state_prov":"Stockholm County","state_code":"SE-AB","district":"Stockholm","city":"Solna","zipcode":"169 03","latitude":"59.36888","longitude":"18.00843","is_eu":true,"country_flag":"https://ipgeolocation.io/static/flags/se_64.png","geoname_id":"8128160","country_emoji":"\uD83C\uDDF8\uD83C\uDDEA"},"time_zone":{"name":"Europe/Stockholm"}}"; \ No newline at end of file diff --git a/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_a328c24e92ee2cf0b70e454939e54f43d844a141 b/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_a328c24e92ee2cf0b70e454939e54f43d844a141 new file mode 100644 index 000000000..ad3f6c741 --- /dev/null +++ b/src/Provider/IPGeolocation/Tests/.cached_responses/api.ipgeolocation.io_a328c24e92ee2cf0b70e454939e54f43d844a141 @@ -0,0 +1 @@ +s:68:"{"message":"'2001:db8:0:42:0:8a2e:370:7334' is a bogon IP address."}"; \ No newline at end of file diff --git a/src/Provider/IPGeolocation/Tests/IPGeolocationTest.php b/src/Provider/IPGeolocation/Tests/IPGeolocationTest.php new file mode 100644 index 000000000..3831198d9 --- /dev/null +++ b/src/Provider/IPGeolocation/Tests/IPGeolocationTest.php @@ -0,0 +1,234 @@ +getMockedHttpClient(), 'api_key'); + $this->assertEquals('ipgeolocation', $provider->getName()); + } + + public function testMissingApiKey(): void + { + $this->expectException(\Geocoder\Exception\InvalidCredentials::class); + $this->expectExceptionMessage('No API key provided.'); + + new IPGeolocation($this->getMockedHttpClient(), ''); + } + + public function testGeocodeWithAddress(): void + { + $this->expectException(\Geocoder\Exception\UnsupportedOperation::class); + $this->expectExceptionMessage('The IPGeolocation provider does not support street addresses, only IP addresses.'); + + $provider = new IPGeolocation($this->getMockedHttpClient(), 'api_key'); + $provider->geocodeQuery(GeocodeQuery::create('10 avenue Gambetta, Paris, France')); + } + + /** @dataProvider provideLocalhostIps */ + public function testGeocodeWithLocalhost(string $localhostIp): void + { + $provider = new IPGeolocation($this->getMockedHttpClient(), 'api_key'); + $results = $provider->geocodeQuery(GeocodeQuery::create($localhostIp)); + + $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertEquals('localhost', $result->getLocality()); + $this->assertEquals('localhost', $result->getCountry()->getName()); + } + + /** + * @return iterable + */ + public function provideLocalhostIps(): iterable + { + yield ['127.0.0.1']; + yield ['::1']; + } + + public function testRequestUrl(): void + { + $provider = new IPGeolocation($this->getMockedHttpClientCallback( + function (RequestInterface $request): ResponseInterface { + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals( + 'https://api.ipgeolocation.io/v3/ipgeo?apiKey=api_key&ip=165.227.0.0&fields=location%2Ctime_zone.name&lang=fr', + (string) $request->getUri() + ); + + return $this->getResponse(200, $this->getFreePlanBody()); + } + ), 'api_key'); + + $provider->geocodeQuery(GeocodeQuery::create('165.227.0.0')->withLocale('fr')); + } + + public function testGeocodeWithIPv4(): void + { + $provider = new IPGeolocation($this->getMockedHttpClient($this->getFreePlanBody()), 'api_key'); + $results = $provider->geocodeQuery(GeocodeQuery::create('165.227.0.0')); + + $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertInstanceOf(\Geocoder\Model\Address::class, $result); + $this->assertEqualsWithDelta(37.35983, $result->getCoordinates()->getLatitude(), 0.00001); + $this->assertEqualsWithDelta(-121.98144, $result->getCoordinates()->getLongitude(), 0.00001); + $this->assertEquals('Santa Clara', $result->getLocality()); + $this->assertEquals('95051', $result->getPostalCode()); + $this->assertCount(2, $result->getAdminLevels()); + $this->assertEquals('California', $result->getAdminLevels()->get(1)->getName()); + $this->assertEquals('US-CA', $result->getAdminLevels()->get(1)->getCode()); + $this->assertEquals('Santa Clara County', $result->getAdminLevels()->get(2)->getName()); + $this->assertEquals('United States', $result->getCountry()->getName()); + $this->assertEquals('US', $result->getCountry()->getCode()); + $this->assertEquals('America/Los_Angeles', $result->getTimezone()); + $this->assertEquals('ipgeolocation', $result->getProvidedBy()); + } + + public function testGeocodeWithEmptyFields(): void + { + $body = json_encode([ + 'ip' => '2001:4860:4860::8888', + 'location' => [ + 'country_code2' => 'US', + 'country_name' => 'United States', + 'state_prov' => '', + 'state_code' => '', + 'district' => '', + 'city' => '', + 'zipcode' => '', + 'latitude' => '37.75100', + 'longitude' => '-97.82200', + ], + ]); + + $provider = new IPGeolocation($this->getMockedHttpClient($body), 'api_key'); + $results = $provider->geocodeQuery(GeocodeQuery::create('2001:4860:4860::8888')); + + /** @var Location $result */ + $result = $results->first(); + $this->assertNull($result->getLocality()); + $this->assertNull($result->getPostalCode()); + $this->assertNull($result->getTimezone()); + $this->assertCount(0, $result->getAdminLevels()); + $this->assertEquals('US', $result->getCountry()->getCode()); + } + + /** @dataProvider provideNoResultStatusCodes */ + public function testGeocodeWithNoResult(int $statusCode, string $message): void + { + $provider = new IPGeolocation( + $this->getMockedHttpClient(json_encode(['message' => $message]), $statusCode), + 'api_key' + ); + + $results = $provider->geocodeQuery(GeocodeQuery::create('10.0.0.1')); + + $this->assertInstanceOf(\Geocoder\Model\AddressCollection::class, $results); + $this->assertCount(0, $results); + } + + /** + * @return iterable + */ + public function provideNoResultStatusCodes(): iterable + { + yield [404, 'Provided IPv4 or IPv6 address does not exist in our database.']; + yield [423, "'10.0.0.1' is a bogon IP address."]; + } + + public function testInvalidApiKey(): void + { + $this->expectException(\Geocoder\Exception\InvalidCredentials::class); + + $body = json_encode(['message' => 'Provided API key is not valid. Contact technical support for assistance at support@ipgeolocation.io']); + $provider = new IPGeolocation($this->getMockedHttpClient($body, 401), 'api_key'); + $provider->geocodeQuery(GeocodeQuery::create('165.227.0.0')); + } + + public function testQuotaExceeded(): void + { + $this->expectException(\Geocoder\Exception\QuotaExceeded::class); + + $provider = new IPGeolocation($this->getMockedHttpClient('{"message":"You have exceeded the limit"}', 429), 'api_key'); + $provider->geocodeQuery(GeocodeQuery::create('165.227.0.0')); + } + + public function testReverse(): void + { + $this->expectException(\Geocoder\Exception\UnsupportedOperation::class); + $this->expectExceptionMessage('The IPGeolocation provider is not able to do reverse geocoding.'); + + $provider = new IPGeolocation($this->getMockedHttpClient(), 'api_key'); + $provider->reverseQuery(ReverseQuery::fromCoordinates(0, 0)); + } + + private function getResponse(int $statusCode, string $body): ResponseInterface + { + return new \Nyholm\Psr7\Response($statusCode, [], $body); + } + + /** + * The free plan example from the IPGeolocation.io API reference, filtered by the provider's `fields` parameter. + */ + private function getFreePlanBody(): string + { + return json_encode([ + 'ip' => '165.227.0.0', + 'location' => [ + 'continent_code' => 'NA', + 'continent_name' => 'North America', + 'country_code2' => 'US', + 'country_code3' => 'USA', + 'country_name' => 'United States', + 'country_name_official' => 'United States of America', + 'country_capital' => 'Washington, D.C.', + 'state_prov' => 'California', + 'state_code' => 'US-CA', + 'district' => 'Santa Clara County', + 'city' => 'Santa Clara', + 'zipcode' => '95051', + 'latitude' => '37.35983', + 'longitude' => '-121.98144', + 'is_eu' => false, + 'country_flag' => 'https://ipgeolocation.io/static/flags/us_64.png', + 'geoname_id' => '5346804', + 'country_emoji' => 'πŸ‡ΊπŸ‡Έ', + ], + 'time_zone' => [ + 'name' => 'America/Los_Angeles', + ], + ]); + } +} diff --git a/src/Provider/IPGeolocation/Tests/IntegrationTest.php b/src/Provider/IPGeolocation/Tests/IntegrationTest.php new file mode 100644 index 000000000..df90b22da --- /dev/null +++ b/src/Provider/IPGeolocation/Tests/IntegrationTest.php @@ -0,0 +1,39 @@ +getApiKey()); + } + + protected function getCacheDir(): string + { + return __DIR__.'/.cached_responses'; + } + + protected function getApiKey(): string + { + return $_SERVER['IPGEOLOCATION_API_KEY']; + } +} diff --git a/src/Provider/IPGeolocation/composer.json b/src/Provider/IPGeolocation/composer.json new file mode 100644 index 000000000..3912dcb93 --- /dev/null +++ b/src/Provider/IPGeolocation/composer.json @@ -0,0 +1,46 @@ +{ + "name": "geocoder-php/ipgeolocation-provider", + "type": "library", + "description": "Geocoder IPGeolocation.io adapter", + "keywords": [], + "homepage": "http://geocoder-php.org/Geocoder/", + "license": "MIT", + "authors": [ + { + "name": "William Durand", + "email": "william.durand1@gmail.com" + } + ], + "require": { + "php": "^8.0", + "geocoder-php/common-http": "^4.0", + "willdurand/geocoder": "^4.0|^5.0" + }, + "provide": { + "geocoder-php/provider-implementation": "1.0" + }, + "require-dev": { + "geocoder-php/provider-integration-tests": "^1.6.3", + "php-http/message": "^1.0", + "phpunit/phpunit": "^9.6.11" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Geocoder\\Provider\\IPGeolocation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml" + } +} diff --git a/src/Provider/IPGeolocation/phpunit.xml.dist b/src/Provider/IPGeolocation/phpunit.xml.dist new file mode 100644 index 000000000..36cc18c08 --- /dev/null +++ b/src/Provider/IPGeolocation/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + ./ + + + ./Tests + ./vendor + + + + + + + + + ./Tests/ + + +