This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
GeoIP2-php is MaxMind's official PHP client library for:
- GeoIP2/GeoLite2 Web Services: Country, City, and Insights endpoints
- GeoIP2/GeoLite2 Databases: Local MMDB file reading for various database types (City, Country, ASN, Anonymous IP, Anonymous Plus, ISP, etc.)
The library provides both web service clients and database readers that return strongly-typed model objects containing geographic, ISP, anonymizer, and other IP-related data.
Key Technologies:
- PHP 8.1+ (uses modern PHP features like readonly properties and strict types)
- MaxMind DB Reader for binary database files
- MaxMind Web Service Common for HTTP client functionality
- PHPUnit for testing
- php-cs-fixer, phpcs, and phpstan for code quality
GeoIp2/
├── Model/ # Response models (City, Insights, AnonymousIp, etc.)
├── Record/ # Data records (City, Location, Traits, etc.)
├── Exception/ # Custom exceptions for error handling
├── Database/Reader # Local MMDB file reader
├── WebService/Client # HTTP client for MaxMind web services
└── ProviderInterface # Common interface for database and web service
All model and record classes use PHP 8.1+ readonly properties for immutability and performance:
class AnonymousPlus extends AnonymousIp
{
public readonly ?int $anonymizerConfidence;
public readonly ?string $networkLastSeen;
public readonly ?string $providerName;
}Key Points:
- Properties are set in the constructor and cannot be modified afterward
- Use
readonlykeyword for all public properties - Nullable properties use
?Typesyntax - Non-nullable booleans typically default to
falsein constructor logic
Models follow clear inheritance patterns:
Country→ base model with country/continent dataCityextendsCountry→ adds city, location, postal, subdivisionsInsightsextendsCity→ adds additional web service fieldsEnterpriseextendsCity→ adds enterprise-specific fields
Records have similar patterns:
AbstractNamedRecord→ base with names/localesAbstractPlaceRecordextendsAbstractNamedRecord→ adds confidence, geonameId- Specific records (
City,Country, etc.) extend these abstracts
All model and record classes implement \JsonSerializable for consistent JSON output:
public function jsonSerialize(): ?array
{
$js = parent::jsonSerialize();
if ($this->anonymizerConfidence !== null) {
$js['anonymizer_confidence'] = $this->anonymizerConfidence;
}
return $js;
}- Only include non-null values in JSON output
- Use snake_case for JSON keys (matching API format)
- Properties use camelCase in PHP
Models and records are constructed from associative arrays (from JSON/DB):
public function __construct(array $raw)
{
parent::__construct($raw);
$this->anonymizerConfidence = $raw['anonymizer_confidence'] ?? null;
$this->networkLastSeen = $raw['network_last_seen'] ?? null;
}- Use
$raw['snake_case_key'] ?? nullpattern for optional fields - Use
$raw['snake_case_key'] ?? falsefor boolean fields - Call parent constructor first if extending another class
Some models are only used by web services and do not need MaxMind DB support:
Web Service Only Models:
- Models that are exclusive to web service responses
- Simpler implementation without database parsing logic
- Example:
Insights(extends City but used only for web service)
Database-Supported Models:
- Models used by both web services and database files
- Must handle MaxMind DB format data structures
- Example:
City,Country,AnonymousIp,AnonymousPlus
# Install dependencies
composer install
# Run all tests
vendor/bin/phpunit
# Run specific test class
vendor/bin/phpunit tests/GeoIp2/Test/Model/InsightsTest.php
# Run with coverage (if xdebug installed)
vendor/bin/phpunit --coverage-html coverage/# PHP-CS-Fixer (code style)
vendor/bin/php-cs-fixer fix --verbose --diff --dry-run
# Apply fixes
vendor/bin/php-cs-fixer fix
# PHPCS (PSR-2 compliance)
vendor/bin/phpcs --standard=PSR2 src/
# PHPStan (static analysis)
vendor/bin/phpstan analyze
# Validate composer.json
composer validateTests are organized by model/class:
tests/GeoIp2/Test/Database/- Database reader teststests/GeoIp2/Test/Model/- Response model teststests/GeoIp2/Test/WebService/- Web service client tests
When adding new fields to models:
- Update the test method to include the new field in the
$rawarray - Add assertions to verify the field is properly populated
- Test both presence and absence of the field (null handling)
- Verify JSON serialization includes the field correctly
Example:
public function testFull(): void
{
$raw = [
'anonymizer_confidence' => 99,
'network_last_seen' => '2025-04-14',
'provider_name' => 'FooBar VPN',
// ... other fields
];
$model = new AnonymousPlus($raw);
$this->assertSame(99, $model->anonymizerConfidence);
$this->assertSame('2025-04-14', $model->networkLastSeen);
$this->assertSame('FooBar VPN', $model->providerName);
}- Add the readonly property with proper type hints and PHPDoc:
/** * @var int|null description of the field */ public readonly ?int $fieldName;
- Update the constructor to set the field from the raw array:
$this->fieldName = $raw['field_name'] ?? null;
- Update
jsonSerialize()to include the field:if ($this->fieldName !== null) { $js['field_name'] = $this->fieldName; }
- Add comprehensive PHPDoc describing the field, its source, and availability
- Update tests to include the new field in test data and assertions
- Update CHANGELOG.md with the change
When creating a new model class:
- Determine if web service only or database-supported
- Follow the pattern from existing similar models
- Extend the appropriate base class (e.g.,
Country,City, or standalone) - Use
readonlyproperties for all public fields - Implement
\JsonSerializableinterface - Provide comprehensive PHPDoc for all properties
- Add corresponding tests with full coverage
When deprecating fields:
- Use
@deprecatedin PHPDoc with version and alternative:/** * @var bool This field is deprecated as of version 3.2.0. * Use the anonymizer object from the Insights response instead. * * @deprecated since 3.2.0 */ public readonly bool $isAnonymous;
- Keep deprecated fields functional - don't break existing code
- Update CHANGELOG.md with deprecation notices
- Document alternatives in the deprecation message
Always update CHANGELOG.md for user-facing changes.
Important: Do not add a date to changelog entries until release time.
- If there's an existing version entry without a date (e.g.,
3.3.0 (unreleased)), add your changes there - If creating a new version entry, use
(unreleased)instead of a date - The release date will be added when the version is actually released
3.3.0 (unreleased)
------------------
* A new `fieldName` property has been added to `GeoIp2\Model\ModelName`.
This field provides information about...
* The `oldField` property in `GeoIp2\Model\ModelName` has been deprecated.
Please use `newField` instead.Using wrong type hints can cause type errors or allow invalid data.
Solution: Follow these patterns:
- Optional values:
?Type(e.g.,?int,?string) - Non-null booleans:
bool(default tofalsein constructor if not present) - Arrays:
arraywith PHPDoc specifying structure (e.g.,@var array<string>)
New fields not appearing in JSON output.
Solution: Always update jsonSerialize() to include new fields:
- Check if the value is not null before adding to array
- Use snake_case for JSON keys to match API format
- Call parent's
jsonSerialize()first if extending
Tests fail because fixtures don't include new fields.
Solution: Update all related tests:
- Add field to test
$rawarray - Add assertions for the new field
- Test null case if field is optional
- Verify JSON serialization
- PSR-2 compliance enforced by phpcs
- PHP-CS-Fixer rules defined in
.php-cs-fixer.php - Strict types (
declare(strict_types=1)) in all files - Yoda style disabled - use normal comparison order (
$var === $value) - Strict comparison required (
===and!==instead of==and!=) - No trailing whitespace
- Unix line endings (LF)
composer install# Run all checks
vendor/bin/php-cs-fixer fix
vendor/bin/phpcs --standard=PSR2 src/
vendor/bin/phpstan analyze
vendor/bin/phpunit- PHP 8.1+ required
- Uses modern PHP features (readonly, union types, etc.)
- Target compatibility should match current supported PHP versions (8.1-8.4)