diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0444df4e..73928851 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.3 - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14ea6937..b507122e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: operating-system: [ubuntu-latest, windows-latest, macos-latest] - php-versions: ['8.1', '8.2'] + php-versions: ['8.1', '8.2', '8.3', '8.4'] name: "PHP ${{ matrix.php-versions }} test on ${{ matrix.operating-system }}" steps: - name: Setup PHP diff --git a/.gitignore b/.gitignore index 41e0b75e..b4839906 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ _site .gh-pages .idea .php_cs.cache +.php-cs-fixer.cache .phpunit.cache/ .phpunit.result.cache GeoLite2-City.mmdb diff --git a/CHANGELOG.md b/CHANGELOG.md index fe872d0f..7f01bd2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ CHANGELOG ========= +3.1.0 (2024-11-15) +------------------ + +* This library no longer uses implicitly nullable parameter types. This + will fix deprecation warning in PHP 8.4. Reported by Steven Lewis. + GitHub #230. +* The PHPDoc type hints have been improved for use with PHPStan. + 3.0.0 (2023-12-04) ------------------ diff --git a/README.md b/README.md index 9b3c6856..9121b725 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,6 @@ The GeoIP2 PHP API uses [Semantic Versioning](https://semver.org/). ## Copyright and License ## -This software is Copyright (c) 2013-2023 by MaxMind, Inc. +This software is Copyright (c) 2013-2024 by MaxMind, Inc. This is free software, licensed under the Apache License, Version 2.0. diff --git a/composer.json b/composer.json index 60908c7d..6d1f07bf 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ } ], "require": { - "maxmind-db/reader": "^1.11.1", - "maxmind/web-service-common": "~0.8", + "maxmind-db/reader": "^1.12.0", + "maxmind/web-service-common": "~0.10", "php": ">=8.1", "ext-json": "*" }, diff --git a/dev-bin/release.sh b/dev-bin/release.sh index 7760fb7c..d58a102f 100755 --- a/dev-bin/release.sh +++ b/dev-bin/release.sh @@ -45,10 +45,10 @@ php composer.phar update --no-dev perl -pi -e "s/(?<=const VERSION = ').+?(?=';)/$tag/g" src/WebService/Client.php -box_phar_hash='c24c400c424a68041d7af146c71943bf1acc0c5abafa45297c503b832b9c6b16 box.phar' +box_phar_hash='8d12a7d69a5003a80bd603ea95a8f3dcea30b9a2ad84cd7cb15b8193929def9e box.phar' if ! echo "$box_phar_hash" | sha256sum -c; then - wget -O box.phar "https://github.com/box-project/box/releases/download/4.5.1/box.phar" + wget -O box.phar "https://github.com/box-project/box/releases/download/4.6.1/box.phar" fi echo "$box_phar_hash" | sha256sum -c @@ -93,10 +93,10 @@ if [ -n "$(git status --porcelain)" ]; then fi # Using Composer is possible, but they don't recommend it. -phpdocumentor_phar_hash='bad7e4b8c99e73391bb3183a127593ecd1cd66ae42b4a33efe495d193e257f04 phpDocumentor.phar' +phpdocumentor_phar_hash='9760ac280a10041928a8743354f68692c22f14cd5d05135dfc15e11d3b3c25ea phpDocumentor.phar' if ! echo "$phpdocumentor_phar_hash" | sha256sum -c; then - wget -O phpDocumentor.phar https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.4.3/phpDocumentor.phar + wget -O phpDocumentor.phar https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.5.3/phpDocumentor.phar fi echo "$phpdocumentor_phar_hash" | sha256sum -c diff --git a/phpstan.neon b/phpstan.neon index ee1616db..63aadbac 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,5 +3,3 @@ parameters: paths: - src - tests - checkMissingIterableValueType: false - diff --git a/src/Database/Reader.php b/src/Database/Reader.php index 74d1897a..9e3a128a 100644 --- a/src/Database/Reader.php +++ b/src/Database/Reader.php @@ -56,9 +56,9 @@ class Reader implements ProviderInterface /** * Constructor. * - * @param string $filename the path to the GeoIP2 database file - * @param array $locales list of locale codes to use in name property - * from most preferred to least preferred + * @param string $filename the path to the GeoIP2 database file + * @param array $locales list of locale codes to use in name property + * from most preferred to least preferred * * @throws InvalidDatabaseException if the database is corrupt or invalid */ @@ -215,6 +215,9 @@ private function flatModelFor(string $class, string $type, string $ipAddress): o return new $class($record); } + /** + * @return array{0:array, 1:int} + */ private function getRecord(string $class, string $type, string $ipAddress): array { if (!str_contains($this->dbType, $type)) { diff --git a/src/Model/AnonymousIp.php b/src/Model/AnonymousIp.php index 7e3b9b6e..e8a99dc5 100644 --- a/src/Model/AnonymousIp.php +++ b/src/Model/AnonymousIp.php @@ -64,6 +64,8 @@ class AnonymousIp implements \JsonSerializable /** * @ignore + * + * @param array $raw */ public function __construct(array $raw) { @@ -78,27 +80,18 @@ public function __construct(array $raw) $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; - if ($this->isAnonymous !== null) { - $js['is_anonymous'] = $this->isAnonymous; - } - if ($this->isAnonymousVpn !== null) { - $js['is_anonymous_vpn'] = $this->isAnonymousVpn; - } - if ($this->isHostingProvider !== null) { - $js['is_hosting_provider'] = $this->isHostingProvider; - } - if ($this->isPublicProxy !== null) { - $js['is_public_proxy'] = $this->isPublicProxy; - } - if ($this->isResidentialProxy !== null) { - $js['is_residential_proxy'] = $this->isResidentialProxy; - } - if ($this->isTorExitNode !== null) { - $js['is_tor_exit_node'] = $this->isTorExitNode; - } + $js['is_anonymous'] = $this->isAnonymous; + $js['is_anonymous_vpn'] = $this->isAnonymousVpn; + $js['is_hosting_provider'] = $this->isHostingProvider; + $js['is_public_proxy'] = $this->isPublicProxy; + $js['is_residential_proxy'] = $this->isResidentialProxy; + $js['is_tor_exit_node'] = $this->isTorExitNode; $js['ip_address'] = $this->ipAddress; $js['network'] = $this->network; diff --git a/src/Model/Asn.php b/src/Model/Asn.php index 55b85c5e..5feae2f0 100644 --- a/src/Model/Asn.php +++ b/src/Model/Asn.php @@ -39,6 +39,8 @@ class Asn implements \JsonSerializable /** * @ignore + * + * @param array $raw */ public function __construct(array $raw) { @@ -50,6 +52,9 @@ public function __construct(array $raw) $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; diff --git a/src/Model/City.php b/src/Model/City.php index 05f9494f..bf2323ed 100644 --- a/src/Model/City.php +++ b/src/Model/City.php @@ -60,6 +60,9 @@ class City extends Country /** * @ignore + * + * @param array $raw + * @param list $locales */ public function __construct(array $raw, array $locales = ['en']) { @@ -90,6 +93,9 @@ public function __construct(array $raw, array $locales = ['en']) $this->subdivisions = $subdivisions; } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = parent::jsonSerialize(); diff --git a/src/Model/ConnectionType.php b/src/Model/ConnectionType.php index 6facb5e8..47e24cc4 100644 --- a/src/Model/ConnectionType.php +++ b/src/Model/ConnectionType.php @@ -33,6 +33,8 @@ class ConnectionType implements \JsonSerializable /** * @ignore + * + * @param array $raw */ public function __construct(array $raw) { @@ -42,6 +44,9 @@ public function __construct(array $raw) $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; diff --git a/src/Model/Country.php b/src/Model/Country.php index e2a4f812..c41af4a2 100644 --- a/src/Model/Country.php +++ b/src/Model/Country.php @@ -58,6 +58,9 @@ class Country implements \JsonSerializable /** * @ignore + * + * @param array $raw + * @param list $locales */ public function __construct(array $raw, array $locales = ['en']) { @@ -81,6 +84,9 @@ public function __construct(array $raw, array $locales = ['en']) $this->traits = new Traits($raw['traits'] ?? []); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; diff --git a/src/Model/Domain.php b/src/Model/Domain.php index 1ce83fdb..ed401c8c 100644 --- a/src/Model/Domain.php +++ b/src/Model/Domain.php @@ -33,6 +33,8 @@ class Domain implements \JsonSerializable /** * @ignore + * + * @param array $raw */ public function __construct(array $raw) { @@ -42,6 +44,9 @@ public function __construct(array $raw) $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; diff --git a/src/Model/Isp.php b/src/Model/Isp.php index f329063e..6b913051 100644 --- a/src/Model/Isp.php +++ b/src/Model/Isp.php @@ -65,6 +65,8 @@ class Isp implements \JsonSerializable /** * @ignore + * + * @param array $raw */ public function __construct(array $raw) { @@ -81,6 +83,9 @@ public function __construct(array $raw) $this->network = Util::cidr($ipAddress, $raw['prefix_len']); } + /** + * @return array|null + */ public function jsonSerialize(): ?array { $js = []; diff --git a/src/Record/AbstractNamedRecord.php b/src/Record/AbstractNamedRecord.php index 414841d5..b28162d6 100644 --- a/src/Record/AbstractNamedRecord.php +++ b/src/Record/AbstractNamedRecord.php @@ -14,14 +14,17 @@ abstract class AbstractNamedRecord implements \JsonSerializable public readonly ?string $name; /** - * @var array An array map where the keys are locale codes - * and the values are names. This attribute is returned by all location - * services and databases. + * @var array An array map where the keys are locale codes + * and the values are names. This attribute is returned by all location + * services and databases. */ public readonly array $names; /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -37,6 +40,9 @@ public function __construct(array $record, array $locales = ['en']) $this->name = null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = []; diff --git a/src/Record/AbstractPlaceRecord.php b/src/Record/AbstractPlaceRecord.php index 364d7bb8..71b68abd 100644 --- a/src/Record/AbstractPlaceRecord.php +++ b/src/Record/AbstractPlaceRecord.php @@ -21,6 +21,9 @@ abstract class AbstractPlaceRecord extends AbstractNamedRecord /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -30,6 +33,9 @@ public function __construct(array $record, array $locales = ['en']) $this->geonameId = $record['geoname_id'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); diff --git a/src/Record/Continent.php b/src/Record/Continent.php index 873014ae..380c423c 100644 --- a/src/Record/Continent.php +++ b/src/Record/Continent.php @@ -26,6 +26,9 @@ class Continent extends AbstractNamedRecord /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -35,6 +38,9 @@ public function __construct(array $record, array $locales = ['en']) $this->geonameId = $record['geoname_id'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); diff --git a/src/Record/Country.php b/src/Record/Country.php index 438ebd89..60fd1f90 100644 --- a/src/Record/Country.php +++ b/src/Record/Country.php @@ -27,6 +27,9 @@ class Country extends AbstractPlaceRecord /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -36,6 +39,9 @@ public function __construct(array $record, array $locales = ['en']) $this->isoCode = $record['iso_code'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); diff --git a/src/Record/Location.php b/src/Record/Location.php index ca13b52c..6172ac83 100644 --- a/src/Record/Location.php +++ b/src/Record/Location.php @@ -64,6 +64,11 @@ class Location implements \JsonSerializable */ public readonly ?string $timeZone; + /** + * @ignore + * + * @param array $record + */ public function __construct(array $record) { $this->averageIncome = $record['average_income'] ?? null; @@ -75,6 +80,9 @@ public function __construct(array $record) $this->timeZone = $record['time_zone'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = []; diff --git a/src/Record/MaxMind.php b/src/Record/MaxMind.php index e80334d5..b2c33d9c 100644 --- a/src/Record/MaxMind.php +++ b/src/Record/MaxMind.php @@ -17,11 +17,19 @@ class MaxMind implements \JsonSerializable */ public readonly ?int $queriesRemaining; + /** + * @ignore + * + * @param array $record + */ public function __construct(array $record) { $this->queriesRemaining = $record['queries_remaining'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = []; diff --git a/src/Record/Postal.php b/src/Record/Postal.php index d3adac6f..cea2f2ae 100644 --- a/src/Record/Postal.php +++ b/src/Record/Postal.php @@ -30,6 +30,8 @@ class Postal implements \JsonSerializable /** * @ignore + * + * @param array $record */ public function __construct(array $record) { @@ -37,6 +39,9 @@ public function __construct(array $record) $this->confidence = $record['confidence'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = []; diff --git a/src/Record/RepresentedCountry.php b/src/Record/RepresentedCountry.php index a810fc63..67f24b08 100644 --- a/src/Record/RepresentedCountry.php +++ b/src/Record/RepresentedCountry.php @@ -22,6 +22,9 @@ class RepresentedCountry extends Country /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -30,6 +33,9 @@ public function __construct(array $record, array $locales = ['en']) $this->type = $record['type'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); diff --git a/src/Record/Subdivision.php b/src/Record/Subdivision.php index cd11e58b..3719a6c3 100644 --- a/src/Record/Subdivision.php +++ b/src/Record/Subdivision.php @@ -22,6 +22,9 @@ class Subdivision extends AbstractPlaceRecord /** * @ignore + * + * @param array $record + * @param list $locales */ public function __construct(array $record, array $locales = ['en']) { @@ -30,6 +33,9 @@ public function __construct(array $record, array $locales = ['en']) $this->isoCode = $record['iso_code'] ?? null; } + /** + * @return array + */ public function jsonSerialize(): array { $js = parent::jsonSerialize(); diff --git a/src/Record/Traits.php b/src/Record/Traits.php index 493f86ae..d75a8f71 100644 --- a/src/Record/Traits.php +++ b/src/Record/Traits.php @@ -196,6 +196,11 @@ class Traits implements \JsonSerializable */ public readonly ?string $userType; + /** + * @ignore + * + * @param array $record + */ public function __construct(array $record) { $this->autonomousSystemNumber = $record['autonomous_system_number'] ?? null; @@ -226,6 +231,9 @@ public function __construct(array $record) } } + /** + * @return array + */ public function jsonSerialize(): array { $js = []; diff --git a/src/WebService/Client.php b/src/WebService/Client.php index f74bb513..fb3966ef 100644 --- a/src/WebService/Client.php +++ b/src/WebService/Client.php @@ -58,30 +58,30 @@ class Client implements ProviderInterface private WsClient $client; private static string $basePath = '/geoip/v2.1'; - public const VERSION = 'v3.0.0'; + public const VERSION = 'v3.1.0'; /** * Constructor. * - * @param int $accountId your MaxMind account ID - * @param string $licenseKey your MaxMind license key - * @param array $locales list of locale codes to use in name property - * from most preferred to least preferred - * @param array $options array of options. Valid options include: - * * `host` - The host to use when querying the web - * service. To query the GeoLite2 web service - * instead of the GeoIP2 web service, set the - * host to `geolite.info`. To query the Sandbox - * GeoIP2 web service instead of the production - * GeoIP2 web service, set the host to - * `sandbox.maxmind.com`. The sandbox allows you to - * experiment with the API without affecting your - * production data. - * * `timeout` - Timeout in seconds. - * * `connectTimeout` - Initial connection timeout in seconds. - * * `proxy` - The HTTP proxy to use. May include a schema, port, - * username, and password, e.g., - * `http://username:password@127.0.0.1:10`. + * @param int $accountId your MaxMind account ID + * @param string $licenseKey your MaxMind license key + * @param list $locales list of locale codes to use in name property + * from most preferred to least preferred + * @param array $options array of options. Valid options include: + * * `host` - The host to use when querying the web + * service. To query the GeoLite2 web service + * instead of the GeoIP2 web service, set the + * host to `geolite.info`. To query the Sandbox + * GeoIP2 web service instead of the production + * GeoIP2 web service, set the host to + * `sandbox.maxmind.com`. The sandbox allows you to + * experiment with the API without affecting your + * production data. + * * `timeout` - Timeout in seconds. + * * `connectTimeout` - Initial connection timeout in seconds. + * * `proxy` - The HTTP proxy to use. May include a schema, port, + * username, and password, e.g., + * `http://username:password@127.0.0.1:10`. */ public function __construct( int $accountId, diff --git a/tests/GeoIp2/Test/Database/ReaderTest.php b/tests/GeoIp2/Test/Database/ReaderTest.php index eea3a849..e1cdbe53 100644 --- a/tests/GeoIp2/Test/Database/ReaderTest.php +++ b/tests/GeoIp2/Test/Database/ReaderTest.php @@ -15,6 +15,9 @@ */ class ReaderTest extends TestCase { + /** + * @return array> + */ public static function databaseTypes(): array { return [['City', 'city'], ['Country', 'country']]; diff --git a/tests/GeoIp2/Test/Model/CountryTest.php b/tests/GeoIp2/Test/Model/CountryTest.php index 9c7c67d5..063f1dda 100644 --- a/tests/GeoIp2/Test/Model/CountryTest.php +++ b/tests/GeoIp2/Test/Model/CountryTest.php @@ -41,6 +41,7 @@ class CountryTest extends TestCase ], ]; + // @phpstan-ignore-next-line private ?Country $model; protected function setUp(): void @@ -205,10 +206,6 @@ public function testJsonSerialize(): void 'jsonSerialize returns initial array for the record' ); - if (version_compare(\PHP_VERSION, '5.4.0', '<')) { - $this->markTestSkipped('Requires PHP 5.4+.'); - } - $this->assertSame( json_encode($js), json_encode($this->model), diff --git a/tests/GeoIp2/Test/WebService/ClientTest.php b/tests/GeoIp2/Test/WebService/ClientTest.php index 71ec3fb1..b790b7a2 100644 --- a/tests/GeoIp2/Test/WebService/ClientTest.php +++ b/tests/GeoIp2/Test/WebService/ClientTest.php @@ -44,6 +44,9 @@ class ClientTest extends TestCase ], ]; + /** + * @return list> + */ private function getResponse(string $service, string $ipAddress): array { if ($service === 'Insights') { @@ -492,6 +495,10 @@ private function response( return [$status, $headers, $body]; } + /** + * @param list $locales + * @param array $options + */ private function makeRequest( string $service, string $ipAddress,