From c67807e60093838f3ca24c39a287e3fbc1420ce8 Mon Sep 17 00:00:00 2001 From: Renan Date: Tue, 16 Jan 2024 23:35:07 +0100 Subject: [PATCH] Introduce ip-api Provider --- .github/workflows/provider.yml | 1 + .gitignore | 1 + README.md | 1 + src/Provider/IpApi/.gitattributes | 4 + .../IpApi/.github/workflows/provider.yml | 33 ++++ src/Provider/IpApi/.gitignore | 4 + src/Provider/IpApi/CHANGELOG.md | 7 + src/Provider/IpApi/IpApi.php | 153 +++++++++++++++++ src/Provider/IpApi/LICENSE | 21 +++ src/Provider/IpApi/Model/IpApiLocation.php | 48 ++++++ src/Provider/IpApi/Readme.md | 26 +++ ...m_1f54318587bf01c920d0557a69bca490cfcc3d72 | 1 + ...m_5a72558379914adc02d2f3f40ab314d91d486518 | 1 + ...m_3b08c5f31259ba3691ff7c952027443e262eb4b5 | 1 + ...m_8eb71ce1218c8f12d16e0d62c2985d452da5933f | 1 + ...m_b3af909171a73da6fdf5482914c583600509e25a | 1 + src/Provider/IpApi/Tests/IntegrationTest.php | 45 +++++ src/Provider/IpApi/Tests/IpApiTest.php | 158 ++++++++++++++++++ src/Provider/IpApi/composer.json | 46 +++++ src/Provider/IpApi/phpunit.xml.dist | 21 +++ 20 files changed, 574 insertions(+) create mode 100644 src/Provider/IpApi/.gitattributes create mode 100644 src/Provider/IpApi/.github/workflows/provider.yml create mode 100644 src/Provider/IpApi/.gitignore create mode 100644 src/Provider/IpApi/CHANGELOG.md create mode 100644 src/Provider/IpApi/IpApi.php create mode 100644 src/Provider/IpApi/LICENSE create mode 100644 src/Provider/IpApi/Model/IpApiLocation.php create mode 100644 src/Provider/IpApi/Readme.md create mode 100644 src/Provider/IpApi/Tests/.cached_responses/ip-api.com_1f54318587bf01c920d0557a69bca490cfcc3d72 create mode 100644 src/Provider/IpApi/Tests/.cached_responses/ip-api.com_5a72558379914adc02d2f3f40ab314d91d486518 create mode 100644 src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_3b08c5f31259ba3691ff7c952027443e262eb4b5 create mode 100644 src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_8eb71ce1218c8f12d16e0d62c2985d452da5933f create mode 100644 src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_b3af909171a73da6fdf5482914c583600509e25a create mode 100644 src/Provider/IpApi/Tests/IntegrationTest.php create mode 100644 src/Provider/IpApi/Tests/IpApiTest.php create mode 100644 src/Provider/IpApi/composer.json create mode 100644 src/Provider/IpApi/phpunit.xml.dist diff --git a/.github/workflows/provider.yml b/.github/workflows/provider.yml index c7a515b24..89d78e4e3 100644 --- a/.github/workflows/provider.yml +++ b/.github/workflows/provider.yml @@ -34,6 +34,7 @@ jobs: - HostIp - IP2Location # - IP2LocationBinary + - IpApi - IpInfo - IpInfoDb - Ipstack diff --git a/.gitignore b/.gitignore index c5a5e4581..5168ef01b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.phar phpunit.xml .phpunit.result.cache .php-cs-fixer.cache +.php-cs-fixer.php .puli/ diff --git a/README.md b/README.md index 31b250196..8de9be50a 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Provider | Package | Features | Stats [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) +[ip-api](https://github.com/geocoder-php/ip-api-provider) | `geocoder-php/ip-api-provider` | IPv4, IPv6
[Website](https://ip-api.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip-api-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip-api-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/ip-api-provider/downloads)](https://packagist.org/packages/geocoder-php/ip-api-provider) [MaxMind](https://github.com/geocoder-php/maxmind-provider) | `geocoder-php/maxmind-provider` | IPv4, IPv6
[Website](https://www.maxmind.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/maxmind-provider/v/stable)](https://packagist.org/packages/geocoder-php/maxmind-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/maxmind-provider/downloads)](https://packagist.org/packages/geocoder-php/maxmind-provider) [MaxMind Binary](https://github.com/geocoder-php/maxmind-binary-provider) | `geocoder-php/maxmind-binary-provider` | IPv4, IPv6
[Website](https://www.maxmind.com/) | [![Latest Stable Version](https://poser.pugx.org/geocoder-php/maxmind-binary-provider/v/stable)](https://packagist.org/packages/geocoder-php/maxmind-binary-provider)
[![Total Downloads](https://poser.pugx.org/geocoder-php/maxmind-binary-provider/downloads)](https://packagist.org/packages/geocoder-php/maxmind-binary-provider) diff --git a/src/Provider/IpApi/.gitattributes b/src/Provider/IpApi/.gitattributes new file mode 100644 index 000000000..d04504afd --- /dev/null +++ b/src/Provider/IpApi/.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/IpApi/.github/workflows/provider.yml b/src/Provider/IpApi/.github/workflows/provider.yml new file mode 100644 index 000000000..d7fbb64c2 --- /dev/null +++ b/src/Provider/IpApi/.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'] + steps: + - uses: actions/checkout@v3 + - 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/IpApi/.gitignore b/src/Provider/IpApi/.gitignore new file mode 100644 index 000000000..76367ee5b --- /dev/null +++ b/src/Provider/IpApi/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +phpunit.xml +.phpunit.result.cache diff --git a/src/Provider/IpApi/CHANGELOG.md b/src/Provider/IpApi/CHANGELOG.md new file mode 100644 index 000000000..2a1b887f7 --- /dev/null +++ b/src/Provider/IpApi/CHANGELOG.md @@ -0,0 +1,7 @@ +# Change Log + +The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release. + +## 0.1.0 + +First release of this library. diff --git a/src/Provider/IpApi/IpApi.php b/src/Provider/IpApi/IpApi.php new file mode 100644 index 000000000..741a70578 --- /dev/null +++ b/src/Provider/IpApi/IpApi.php @@ -0,0 +1,153 @@ +apiKey = $apiKey; + parent::__construct($client); + } + + #[\Override] + public function geocodeQuery(GeocodeQuery $query): Collection + { + $ip = $query->getText(); + + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + throw new UnsupportedOperation('The ip-api provider does not support street addresses.'); + } + + if (in_array($ip, ['127.0.0.1', '::1'])) { + return new AddressCollection([$this->getLocationForLocalhost()]); + } + + $url = $this->buildUrl($ip, $query->getLocale()); + + $body = $this->getUrlContents($url); + + $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR); + if ('fail' === $data['status']) { + $this->throwError($data['message']); + } + + $location = $this->buildLocation($data); + + return new AddressCollection([$location]); + } + + #[\Override] + public function reverseQuery(ReverseQuery $query): Collection + { + throw new UnsupportedOperation('The ip-api provider is not able to do reverse geocoding.'); + } + + #[\Override] + public function getName(): string + { + return 'ip-api'; + } + + public function buildUrl(string $ip, string|null $locale): string + { + $baseUrl = strtr(self::URL, [ + '{host_prefix}' => $this->apiKey ? 'https://pro.' : 'http://', + '{ip}' => $ip, + ]); + + $query = http_build_query(array_filter([ + 'key' => $this->apiKey, + 'lang' => $locale, + 'fields' => self::FIELDS, + ])); + + return $baseUrl.'?'.$query; + } + + /** + * @param array $data + */ + private function buildLocation(array $data): IpApiLocation + { + $data = array_map( + static fn ($value) => '' === $value ? null : $value, + $data, + ); + + $builder = new AddressBuilder($this->getName()); + $builder->setCoordinates($data['lat'], $data['lon']); + $builder->setLocality($data['city']); + $builder->setSubLocality($data['district']); + $builder->setPostalCode($data['zip']); + $builder->setCountry($data['country']); + $builder->setCountryCode($data['countryCode']); + $builder->setTimezone($data['timezone']); + + if ($data['regionName']) { + $builder->addAdminLevel(1, $data['regionName'], $data['region']); + } + + /** @var IpApiLocation $location */ + $location = $builder->build(IpApiLocation::class); + + return $location + ->withIsProxy($data['proxy']) + ->withIsHosting($data['hosting']); + } + + /** + * @see https://members.ip-api.com/faq#errors + * + * @return never + */ + private function throwError(string $message) + { + if ( + in_array($message, ['private range', 'reserved range', 'invalid query'], true) + || str_contains('Origin restriction', $message) + || str_contains('IP range restriction', $message) + || str_contains('Calling IP restriction', $message) + ) { + throw new InvalidArgument($message); + } + + if ( + str_contains('invalid/expired ke', $message) + || str_contains('no API key supplied', $message) + ) { + throw new InvalidCredentials($message); + } + + throw new InvalidServerResponse($message); + } +} diff --git a/src/Provider/IpApi/LICENSE b/src/Provider/IpApi/LICENSE new file mode 100644 index 000000000..8aa8246ef --- /dev/null +++ b/src/Provider/IpApi/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/IpApi/Model/IpApiLocation.php b/src/Provider/IpApi/Model/IpApiLocation.php new file mode 100644 index 000000000..7fa960e0f --- /dev/null +++ b/src/Provider/IpApi/Model/IpApiLocation.php @@ -0,0 +1,48 @@ +isProxy; + } + + public function withIsProxy(bool $isProxy): self + { + $new = clone $this; + $new->isProxy = $isProxy; + + return $new; + } + + public function isHosting(): bool + { + return $this->isHosting; + } + + public function withIsHosting(bool $isHosting): self + { + $new = clone $this; + $new->isHosting = $isHosting; + + return $new; + } +} diff --git a/src/Provider/IpApi/Readme.md b/src/Provider/IpApi/Readme.md new file mode 100644 index 000000000..bce0886b0 --- /dev/null +++ b/src/Provider/IpApi/Readme.md @@ -0,0 +1,26 @@ +# ip-api Geocoder provider +[![Build Status](https://travis-ci.org/geocoder-php/ip-api-provider.svg?branch=master)](http://travis-ci.org/geocoder-php/ip-api-provider) +[![Latest Stable Version](https://poser.pugx.org/geocoder-php/ip-api-provider/v/stable)](https://packagist.org/packages/geocoder-php/ip-api-provider) +[![Total Downloads](https://poser.pugx.org/geocoder-php/ip-api-provider/downloads)](https://packagist.org/packages/geocoder-php/ip-api-provider) +[![Monthly Downloads](https://poser.pugx.org/geocoder-php/ip-api-provider/d/monthly.png)](https://packagist.org/packages/geocoder-php/ip-api-provider) +[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/geocoder-php/ip-api-provider.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/ip-api-provider) +[![Quality Score](https://img.shields.io/scrutinizer/g/geocoder-php/ip-api-provider.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/ip-api-provider) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) + +This is the IpApi 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/ip-api-provider +``` + +### Note + +The default language-locale is `en`, you can choose between `de`, `es`, `pt-BR`, `fr`, `ja`, `zh-CN`, `ru`. + +### 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/IpApi/Tests/.cached_responses/ip-api.com_1f54318587bf01c920d0557a69bca490cfcc3d72 b/src/Provider/IpApi/Tests/.cached_responses/ip-api.com_1f54318587bf01c920d0557a69bca490cfcc3d72 new file mode 100644 index 000000000..474cfa68f --- /dev/null +++ b/src/Provider/IpApi/Tests/.cached_responses/ip-api.com_1f54318587bf01c920d0557a69bca490cfcc3d72 @@ -0,0 +1 @@ +s:230:"{"status":"success","country":"United States","countryCode":"US","region":"OK","regionName":"Oklahoma","city":"Tulsa","district":"","zip":"","lat":36.15398,"lon":-95.99277,"timezone":"America/Chicago","proxy":false,"hosting":true}"; \ No newline at end of file diff --git a/src/Provider/IpApi/Tests/.cached_responses/ip-api.com_5a72558379914adc02d2f3f40ab314d91d486518 b/src/Provider/IpApi/Tests/.cached_responses/ip-api.com_5a72558379914adc02d2f3f40ab314d91d486518 new file mode 100644 index 000000000..474cfa68f --- /dev/null +++ b/src/Provider/IpApi/Tests/.cached_responses/ip-api.com_5a72558379914adc02d2f3f40ab314d91d486518 @@ -0,0 +1 @@ +s:230:"{"status":"success","country":"United States","countryCode":"US","region":"OK","regionName":"Oklahoma","city":"Tulsa","district":"","zip":"","lat":36.15398,"lon":-95.99277,"timezone":"America/Chicago","proxy":false,"hosting":true}"; \ No newline at end of file diff --git a/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_3b08c5f31259ba3691ff7c952027443e262eb4b5 b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_3b08c5f31259ba3691ff7c952027443e262eb4b5 new file mode 100644 index 000000000..dde9cbcf4 --- /dev/null +++ b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_3b08c5f31259ba3691ff7c952027443e262eb4b5 @@ -0,0 +1 @@ +s:230:"{"city":"Tulsa","country":"United States","countryCode":"US","district":"","hosting":true,"lat":36.15398,"lon":-95.99277,"proxy":false,"region":"OK","regionName":"Oklahoma","status":"success","timezone":"America/Chicago","zip":""}"; \ No newline at end of file diff --git a/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_8eb71ce1218c8f12d16e0d62c2985d452da5933f b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_8eb71ce1218c8f12d16e0d62c2985d452da5933f new file mode 100644 index 000000000..dde9cbcf4 --- /dev/null +++ b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_8eb71ce1218c8f12d16e0d62c2985d452da5933f @@ -0,0 +1 @@ +s:230:"{"city":"Tulsa","country":"United States","countryCode":"US","district":"","hosting":true,"lat":36.15398,"lon":-95.99277,"proxy":false,"region":"OK","regionName":"Oklahoma","status":"success","timezone":"America/Chicago","zip":""}"; \ No newline at end of file diff --git a/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_b3af909171a73da6fdf5482914c583600509e25a b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_b3af909171a73da6fdf5482914c583600509e25a new file mode 100644 index 000000000..0fafe5b49 --- /dev/null +++ b/src/Provider/IpApi/Tests/.cached_responses/pro.ip-api.com_b3af909171a73da6fdf5482914c583600509e25a @@ -0,0 +1 @@ +s:239:"{"city":"Karlskrona","country":"Sweden","countryCode":"SE","district":"","hosting":false,"lat":56.1625,"lon":15.5801,"proxy":false,"region":"K","regionName":"Blekinge County","status":"success","timezone":"Europe/Stockholm","zip":"371 37"}"; \ No newline at end of file diff --git a/src/Provider/IpApi/Tests/IntegrationTest.php b/src/Provider/IpApi/Tests/IntegrationTest.php new file mode 100644 index 000000000..ba45b63f1 --- /dev/null +++ b/src/Provider/IpApi/Tests/IntegrationTest.php @@ -0,0 +1,45 @@ +getApiKey()); + } + + protected function getCacheDir(): string + { + return __DIR__.'/.cached_responses'; + } + + protected function getApiKey(): string + { + if (!isset($_SERVER['IP_API_KEY'])) { + $this->markTestSkipped('No ip-api API key'); + } + + return $_SERVER['IP_API_KEY']; + } +} diff --git a/src/Provider/IpApi/Tests/IpApiTest.php b/src/Provider/IpApi/Tests/IpApiTest.php new file mode 100644 index 000000000..bf003c1e5 --- /dev/null +++ b/src/Provider/IpApi/Tests/IpApiTest.php @@ -0,0 +1,158 @@ +getMockedHttpClient()); + $this->assertEquals('ip-api', $provider->getName()); + } + + public function testInvalidApiKey(): void + { + $provider = new IpApi($this->getMockedHttpClient(), 'InVaLiDkEy'); + + $this->expectException(InvalidServerResponse::class); + $provider->geocodeQuery(GeocodeQuery::create('74.125.45.100')); + } + + public function testGeocodeWithAddress(): void + { + $this->expectException(UnsupportedOperation::class); + $this->expectExceptionMessage('The ip-api provider does not support street addresses.'); + + $provider = new IpApi($this->getMockedHttpClient()); + $provider->geocodeQuery(GeocodeQuery::create('this is not an IP address')); + } + + public function testGeocodeWithLocalhostIPv4(): void + { + $provider = new IpApi($this->getMockedHttpClient()); + $results = $provider->geocodeQuery(GeocodeQuery::create('127.0.0.1')); + + $this->assertInstanceOf(AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertInstanceOf(Address::class, $result); + $this->assertNotInstanceOf(IpApiLocation::class, $result); + $this->assertEquals('localhost', $result->getLocality()); + $this->assertEquals('localhost', $result->getCountry()->getName()); + } + + public function testGeocodeWithLocalhostIPv6(): void + { + $provider = new IpApi($this->getMockedHttpClient()); + $results = $provider->geocodeQuery(GeocodeQuery::create('::1')); + + $this->assertInstanceOf(AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertInstanceOf(Address::class, $result); + $this->assertNotInstanceOf(IpApiLocation::class, $result); + $this->assertEquals('localhost', $result->getLocality()); + $this->assertEquals('localhost', $result->getCountry()->getName()); + } + + /** + * @dataProvider apiKeyProvider + */ + public function testGeocodeWithRealIPv4(string|null $apiKey): void + { + $provider = new IpApi($this->getHttpClient($apiKey), $apiKey); + $results = $provider->geocodeQuery(GeocodeQuery::create('74.125.45.100')); + + $this->assertInstanceOf(AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertInstanceOf(Address::class, $result); + $this->assertInstanceOf(IpApiLocation::class, $result); + $this->assertEqualsWithDelta(36.154, $result->getCoordinates()->getLatitude(), 0.001); + $this->assertEqualsWithDelta(-95.9928, $result->getCoordinates()->getLongitude(), 0.001); + $this->assertEquals(null, $result->getPostalCode()); + $this->assertEquals('Tulsa', $result->getLocality()); + $this->assertCount(1, $result->getAdminLevels()); + $this->assertEquals('Oklahoma', $result->getAdminLevels()->get(1)->getName()); + $this->assertEquals('United States', $result->getCountry()->getName()); + $this->assertEquals('US', $result->getCountry()->getCode()); + } + + /** + * @dataProvider apiKeyProvider + */ + public function testGeocodeWithRealIPv6(string|null $apiKey): void + { + $provider = new IpApi($this->getHttpClient($apiKey), $apiKey); + $results = $provider->geocodeQuery(GeocodeQuery::create('::ffff:74.125.45.100')); + + $this->assertInstanceOf(AddressCollection::class, $results); + $this->assertCount(1, $results); + + /** @var Location $result */ + $result = $results->first(); + $this->assertInstanceOf(Address::class, $result); + $this->assertInstanceOf(IpApiLocation::class, $result); + $this->assertEqualsWithDelta(36.154, $result->getCoordinates()->getLatitude(), 0.001); + $this->assertEqualsWithDelta(-95.9928, $result->getCoordinates()->getLongitude(), 0.001); + $this->assertEquals(null, $result->getPostalCode()); + $this->assertEquals('Tulsa', $result->getLocality()); + $this->assertCount(1, $result->getAdminLevels()); + $this->assertEquals('Oklahoma', $result->getAdminLevels()->get(1)->getName()); + $this->assertEquals('United States', $result->getCountry()->getName()); + $this->assertEquals('US', $result->getCountry()->getCode()); + } + + public function testReverse(): void + { + $this->expectException(UnsupportedOperation::class); + $this->expectExceptionMessage('The ip-api provider is not able to do reverse geocoding.'); + + $provider = new IpApi($this->getMockedHttpClient()); + $provider->reverseQuery(ReverseQuery::fromCoordinates(1, 2)); + } + + /** + * @return iterable> + */ + public static function apiKeyProvider(): iterable + { + yield 'no api key' => [null]; + + if (isset($_SERVER['IP_API_KEY'])) { + yield 'with api key' => [$_SERVER['IP_API_KEY']]; + } + } +} diff --git a/src/Provider/IpApi/composer.json b/src/Provider/IpApi/composer.json new file mode 100644 index 000000000..82daf86dc --- /dev/null +++ b/src/Provider/IpApi/composer.json @@ -0,0 +1,46 @@ +{ + "name": "geocoder-php/ip-api-provider", + "type": "library", + "description": "Geocoder ip-api 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" + }, + "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.5" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Geocoder\\Provider\\IpApi\\": "" + }, + "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/IpApi/phpunit.xml.dist b/src/Provider/IpApi/phpunit.xml.dist new file mode 100644 index 000000000..69ee95036 --- /dev/null +++ b/src/Provider/IpApi/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + ./ + + + ./Tests + ./vendor + + + + + + + + + ./Tests/ + + +