oc / ip-region-detector
Framework-agnostic PHP package to resolve a client IP and detect region/geolocation via IPWhois.io
Requires
- php: ^8.2
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
Requires (Dev)
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.0
Suggests
- guzzlehttp/guzzle: PSR-18 HTTP client with configurable timeouts
- nyholm/psr7: Lightweight PSR-7 / PSR-17 implementation
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-16 14:08:52 UTC
README
Framework-agnostic PHP package that resolves a client IP from a PSR-7 request and returns normalized geolocation data via IPWhois.io.
Requirements
- PHP 8.2+
- A PSR-18 HTTP client
- A PSR-17 request factory
Installation
composer require oc/ip-region-detector
You will also need concrete HTTP implementations, for example:
composer require guzzlehttp/guzzle nyholm/psr7
Basic usage
use GuzzleHttp\Client; use Nyholm\Psr7\Factory\Psr17Factory; use Oc\IpRegionDetector\GeoLocationService; use Oc\IpRegionDetector\IpResolver; use Oc\IpRegionDetector\Providers\IpWhoisProvider; use Psr\Http\Message\ServerRequestInterface; $psr17 = new Psr17Factory(); // Configure timeout on the HTTP client (PSR-18 has no per-request timeout). $httpClient = new Client([ 'timeout' => 5, 'connect_timeout' => 5, ]); $provider = new IpWhoisProvider( httpClient: $httpClient, requestFactory: $psr17, baseUrl: 'https://ipwho.is', ); $service = new GeoLocationService( ipResolver: new IpResolver(), provider: $provider, ); /** @var ServerRequestInterface $request */ $location = $service->locate($request); echo $location->country; echo $location->city;
Returned data
GeoLocation is an immutable DTO with:
| Property | Type | Description |
|---|---|---|
ip |
string |
IP used for the lookup |
country |
?string |
Country name |
countryCode |
?string |
ISO country code |
region |
?string |
Region / state name |
regionCode |
?string |
Region / state code |
city |
?string |
City name |
latitude |
?float |
Latitude |
longitude |
?float |
Longitude |
postal |
?string |
Postal / ZIP code |
timezone |
?string |
Timezone ID (from IPWhois timezone.id) |
IP resolution
IpResolver uses the request REMOTE_ADDR only.
It does not trust X-Forwarded-For, X-Real-IP, CF-Connecting-IP, or similar headers, because clients can spoof them.
Private, reserved, and loopback addresses (for example 127.0.0.1, 10.0.0.1, 192.168.1.1, ::1) are rejected before any external API call.
IPWhois provider
The package currently ships with one provider: IpWhoisProvider.
It calls the free HTTPS endpoint:
https://ipwho.is/{IP}
No API key is required for that endpoint.
See the official documentation: https://ipwhois.io/documentation
Free API limitations
The free IPWhois endpoint is limited to 1,000 requests per day per client IP.
Exceeding the limit returns HTTP 429. Access resets after 24 hours.
Pricing details: https://ipwhois.io/pricing
This package does not cache results. Cache GeoLocation in your application if you need to reduce external calls.
Errors
| Exception | When |
|---|---|
InvalidIpException |
Missing remote IP, invalid IP, or non-public IP |
ApiException |
Network/transport failure, non-2xx HTTP, invalid JSON, success: false, or rate limit |
GeoLocationException |
Base exception for the package |
ApiException exposes:
getStatusCode()getProviderMessage()isRateLimited()
Slim example
The package has no Slim dependency. In a Slim action:
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Oc\IpRegionDetector\GeoLocationService; public function __invoke( ServerRequestInterface $request, ResponseInterface $response, GeoLocationService $geoLocation, ): ResponseInterface { $location = $geoLocation->locate($request); $payload = json_encode([ 'ip' => $location->ip, 'country' => $location->country, 'city' => $location->city, ], JSON_THROW_ON_ERROR); $response->getBody()->write($payload); return $response->withHeader('Content-Type', 'application/json'); }
Wire GeoLocationService, IpResolver, and IpWhoisProvider in your container.
Privacy note
Looking up geolocation sends the resolved IP address to the external IPWhois service. Review their terms and documentation before use in production: https://ipwhois.io/terms
Testing
composer install
composer test
Unit tests mock the HTTP client and do not call https://ipwho.is.
License
MIT