Search by

oc / ip-region-detector

octacrafts

Framework-agnostic PHP package to resolve a client IP and detect region/geolocation via IPWhois.io

dev-main 2026-09-16 13:53 UTC

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