Skip to content

Commit 02a79e7

Browse files
authored
Merge pull request #279 from maxmind/greg/eng-3314-ip-risk-and-anonymous-plus-outputs-are-supported-by-geoip2
Add IP Risk and Anonymous Plus inputs to Insights
2 parents 444ddcb + 341119e commit 02a79e7

File tree

7 files changed

+629
-2
lines changed

7 files changed

+629
-2
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ t.php
1818
vendor/
1919
*.sw?
2020
*.old
21+
22+
# Claude Code directories
23+
.claude/
24+
.claude_cache/

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
CHANGELOG
22
=========
33

4+
3.3.0 (unreleased)
5+
------------------
6+
7+
* A new `anonymizer` property has been added to `GeoIp2\Model\Insights`.
8+
This property is an instance of `GeoIp2\Record\Anonymizer` and provides
9+
information about whether the IP address belongs to an anonymous network,
10+
VPN provider details (including `confidence`, `providerName`, and
11+
`networkLastSeen`), and various anonymity flags. This data is available
12+
from the GeoIP2 Insights web service.
13+
* A new `ipRiskSnapshot` property has been added to `GeoIp2\Record\Traits`.
14+
This property provides a risk score from 0.01 to 99.99 indicating the risk
15+
associated with the IP address. Higher values indicate higher risk. This is
16+
a static snapshot that is less dynamic than minFraud risk scoring. This
17+
attribute is only available from the GeoIP2 Insights web service.
18+
* The `isAnonymous`, `isAnonymousVpn`, `isHostingProvider`, `isPublicProxy`,
19+
`isResidentialProxy`, and `isTorExitNode` properties in
20+
`GeoIp2\Record\Traits` have been deprecated. Please use the corresponding
21+
properties in the new `anonymizer` object in the Insights response instead.
22+
423
3.2.0 (2025-05-05)
524
------------------
625

CLAUDE.md

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**GeoIP2-php** is MaxMind's official PHP client library for:
8+
- **GeoIP2/GeoLite2 Web Services**: Country, City, and Insights endpoints
9+
- **GeoIP2/GeoLite2 Databases**: Local MMDB file reading for various database types (City, Country, ASN, Anonymous IP, Anonymous Plus, ISP, etc.)
10+
11+
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.
12+
13+
**Key Technologies:**
14+
- PHP 8.1+ (uses modern PHP features like readonly properties and strict types)
15+
- MaxMind DB Reader for binary database files
16+
- MaxMind Web Service Common for HTTP client functionality
17+
- PHPUnit for testing
18+
- php-cs-fixer, phpcs, and phpstan for code quality
19+
20+
## Code Architecture
21+
22+
### Package Structure
23+
24+
```
25+
GeoIp2/
26+
├── Model/ # Response models (City, Insights, AnonymousIp, etc.)
27+
├── Record/ # Data records (City, Location, Traits, etc.)
28+
├── Exception/ # Custom exceptions for error handling
29+
├── Database/Reader # Local MMDB file reader
30+
├── WebService/Client # HTTP client for MaxMind web services
31+
└── ProviderInterface # Common interface for database and web service
32+
```
33+
34+
### Key Design Patterns
35+
36+
#### 1. **Readonly Properties for Immutable Data**
37+
All model and record classes use PHP 8.1+ `readonly` properties for immutability and performance:
38+
39+
```php
40+
class AnonymousPlus extends AnonymousIp
41+
{
42+
public readonly ?int $anonymizerConfidence;
43+
public readonly ?string $networkLastSeen;
44+
public readonly ?string $providerName;
45+
}
46+
```
47+
48+
**Key Points:**
49+
- Properties are set in the constructor and cannot be modified afterward
50+
- Use `readonly` keyword for all public properties
51+
- Nullable properties use `?Type` syntax
52+
- Non-nullable booleans typically default to `false` in constructor logic
53+
54+
#### 2. **Inheritance Hierarchies**
55+
56+
Models follow clear inheritance patterns:
57+
- `Country` → base model with country/continent data
58+
- `City` extends `Country` → adds city, location, postal, subdivisions
59+
- `Insights` extends `City` → adds additional web service fields
60+
- `Enterprise` extends `City` → adds enterprise-specific fields
61+
62+
Records have similar patterns:
63+
- `AbstractNamedRecord` → base with names/locales
64+
- `AbstractPlaceRecord` extends `AbstractNamedRecord` → adds confidence, geonameId
65+
- Specific records (`City`, `Country`, etc.) extend these abstracts
66+
67+
#### 3. **JsonSerializable Implementation**
68+
69+
All model and record classes implement `\JsonSerializable` for consistent JSON output:
70+
71+
```php
72+
public function jsonSerialize(): ?array
73+
{
74+
$js = parent::jsonSerialize();
75+
76+
if ($this->anonymizerConfidence !== null) {
77+
$js['anonymizer_confidence'] = $this->anonymizerConfidence;
78+
}
79+
80+
return $js;
81+
}
82+
```
83+
84+
- Only include non-null values in JSON output
85+
- Use snake_case for JSON keys (matching API format)
86+
- Properties use camelCase in PHP
87+
88+
#### 4. **Constructor Array Parameter Pattern**
89+
90+
Models and records are constructed from associative arrays (from JSON/DB):
91+
92+
```php
93+
public function __construct(array $raw)
94+
{
95+
parent::__construct($raw);
96+
$this->anonymizerConfidence = $raw['anonymizer_confidence'] ?? null;
97+
$this->networkLastSeen = $raw['network_last_seen'] ?? null;
98+
}
99+
```
100+
101+
- Use `$raw['snake_case_key'] ?? null` pattern for optional fields
102+
- Use `$raw['snake_case_key'] ?? false` for boolean fields
103+
- Call parent constructor first if extending another class
104+
105+
#### 5. **Web Service Only vs Database Models**
106+
107+
Some models are only used by web services and do **not** need MaxMind DB support:
108+
109+
**Web Service Only Models**:
110+
- Models that are exclusive to web service responses
111+
- Simpler implementation without database parsing logic
112+
- Example: `Insights` (extends City but used only for web service)
113+
114+
**Database-Supported Models**:
115+
- Models used by both web services and database files
116+
- Must handle MaxMind DB format data structures
117+
- Example: `City`, `Country`, `AnonymousIp`, `AnonymousPlus`
118+
119+
## Testing Conventions
120+
121+
### Running Tests
122+
123+
```bash
124+
# Install dependencies
125+
composer install
126+
127+
# Run all tests
128+
vendor/bin/phpunit
129+
130+
# Run specific test class
131+
vendor/bin/phpunit tests/GeoIp2/Test/Model/InsightsTest.php
132+
133+
# Run with coverage (if xdebug installed)
134+
vendor/bin/phpunit --coverage-html coverage/
135+
```
136+
137+
### Linting and Static Analysis
138+
139+
```bash
140+
# PHP-CS-Fixer (code style)
141+
vendor/bin/php-cs-fixer fix --verbose --diff --dry-run
142+
143+
# Apply fixes
144+
vendor/bin/php-cs-fixer fix
145+
146+
# PHPCS (PSR-2 compliance)
147+
vendor/bin/phpcs --standard=PSR2 src/
148+
149+
# PHPStan (static analysis)
150+
vendor/bin/phpstan analyze
151+
152+
# Validate composer.json
153+
composer validate
154+
```
155+
156+
### Test Structure
157+
158+
Tests are organized by model/class:
159+
- `tests/GeoIp2/Test/Database/` - Database reader tests
160+
- `tests/GeoIp2/Test/Model/` - Response model tests
161+
- `tests/GeoIp2/Test/WebService/` - Web service client tests
162+
163+
### Test Patterns
164+
165+
When adding new fields to models:
166+
1. Update the test method to include the new field in the `$raw` array
167+
2. Add assertions to verify the field is properly populated
168+
3. Test both presence and absence of the field (null handling)
169+
4. Verify JSON serialization includes the field correctly
170+
171+
Example:
172+
```php
173+
public function testFull(): void
174+
{
175+
$raw = [
176+
'anonymizer_confidence' => 99,
177+
'network_last_seen' => '2025-04-14',
178+
'provider_name' => 'FooBar VPN',
179+
// ... other fields
180+
];
181+
182+
$model = new AnonymousPlus($raw);
183+
184+
$this->assertSame(99, $model->anonymizerConfidence);
185+
$this->assertSame('2025-04-14', $model->networkLastSeen);
186+
$this->assertSame('FooBar VPN', $model->providerName);
187+
}
188+
```
189+
190+
## Working with This Codebase
191+
192+
### Adding New Fields to Existing Models
193+
194+
1. **Add the readonly property** with proper type hints and PHPDoc:
195+
```php
196+
/**
197+
* @var int|null description of the field
198+
*/
199+
public readonly ?int $fieldName;
200+
```
201+
2. **Update the constructor** to set the field from the raw array:
202+
```php
203+
$this->fieldName = $raw['field_name'] ?? null;
204+
```
205+
3. **Update `jsonSerialize()`** to include the field:
206+
```php
207+
if ($this->fieldName !== null) {
208+
$js['field_name'] = $this->fieldName;
209+
}
210+
```
211+
4. **Add comprehensive PHPDoc** describing the field, its source, and availability
212+
5. **Update tests** to include the new field in test data and assertions
213+
6. **Update CHANGELOG.md** with the change
214+
215+
### Adding New Models
216+
217+
When creating a new model class:
218+
219+
1. **Determine if web service only or database-supported**
220+
2. **Follow the pattern** from existing similar models
221+
3. **Extend the appropriate base class** (e.g., `Country`, `City`, or standalone)
222+
4. **Use `readonly` properties** for all public fields
223+
5. **Implement `\JsonSerializable`** interface
224+
6. **Provide comprehensive PHPDoc** for all properties
225+
7. **Add corresponding tests** with full coverage
226+
227+
### Deprecation Guidelines
228+
229+
When deprecating fields:
230+
231+
1. **Use `@deprecated` in PHPDoc** with version and alternative:
232+
```php
233+
/**
234+
* @var bool This field is deprecated as of version 3.2.0.
235+
* Use the anonymizer object from the Insights response instead.
236+
*
237+
* @deprecated since 3.2.0
238+
*/
239+
public readonly bool $isAnonymous;
240+
```
241+
2. **Keep deprecated fields functional** - don't break existing code
242+
3. **Update CHANGELOG.md** with deprecation notices
243+
4. **Document alternatives** in the deprecation message
244+
245+
### CHANGELOG.md Format
246+
247+
Always update `CHANGELOG.md` for user-facing changes.
248+
249+
**Important**: Do not add a date to changelog entries until release time.
250+
251+
- If there's an existing version entry without a date (e.g., `3.3.0 (unreleased)`), add your changes there
252+
- If creating a new version entry, use `(unreleased)` instead of a date
253+
- The release date will be added when the version is actually released
254+
255+
```markdown
256+
3.3.0 (unreleased)
257+
------------------
258+
259+
* A new `fieldName` property has been added to `GeoIp2\Model\ModelName`.
260+
This field provides information about...
261+
* The `oldField` property in `GeoIp2\Model\ModelName` has been deprecated.
262+
Please use `newField` instead.
263+
```
264+
265+
## Common Pitfalls and Solutions
266+
267+
### Problem: Incorrect Property Types
268+
Using wrong type hints can cause type errors or allow invalid data.
269+
270+
**Solution**: Follow these patterns:
271+
- Optional values: `?Type` (e.g., `?int`, `?string`)
272+
- Non-null booleans: `bool` (default to `false` in constructor if not present)
273+
- Arrays: `array` with PHPDoc specifying structure (e.g., `@var array<string>`)
274+
275+
### Problem: Missing JSON Serialization
276+
New fields not appearing in JSON output.
277+
278+
**Solution**: Always update `jsonSerialize()` to include new fields:
279+
- Check if the value is not null before adding to array
280+
- Use snake_case for JSON keys to match API format
281+
- Call parent's `jsonSerialize()` first if extending
282+
283+
### Problem: Test Failures After Adding Fields
284+
Tests fail because fixtures don't include new fields.
285+
286+
**Solution**: Update all related tests:
287+
1. Add field to test `$raw` array
288+
2. Add assertions for the new field
289+
3. Test null case if field is optional
290+
4. Verify JSON serialization
291+
292+
## Code Style Requirements
293+
294+
- **PSR-2 compliance** enforced by phpcs
295+
- **PHP-CS-Fixer** rules defined in `.php-cs-fixer.php`
296+
- **Strict types** (`declare(strict_types=1)`) in all files
297+
- **Yoda style disabled** - use normal comparison order (`$var === $value`)
298+
- **Strict comparison** required (`===` and `!==` instead of `==` and `!=`)
299+
- **No trailing whitespace**
300+
- **Unix line endings (LF)**
301+
302+
## Development Workflow
303+
304+
### Setup
305+
```bash
306+
composer install
307+
```
308+
309+
### Before Committing
310+
```bash
311+
# Run all checks
312+
vendor/bin/php-cs-fixer fix
313+
vendor/bin/phpcs --standard=PSR2 src/
314+
vendor/bin/phpstan analyze
315+
vendor/bin/phpunit
316+
```
317+
318+
### Version Requirements
319+
- **PHP 8.1+** required
320+
- Uses modern PHP features (readonly, union types, etc.)
321+
- Target compatibility should match current supported PHP versions (8.1-8.4)
322+
323+
## Additional Resources
324+
325+
- [API Documentation](https://maxmind.github.io/GeoIP2-php/)
326+
- [GeoIP2 Web Services Docs](https://dev.maxmind.com/geoip/docs/web-services)
327+
- [MaxMind DB Format](https://maxmind.github.io/MaxMind-DB/)
328+
- GitHub Issues: https://github.com/maxmind/GeoIP2-php/issues

0 commit comments

Comments
 (0)