geocoder-php/ip-info-provider
IP-Info provider for the Geocoder PHP library. Looks up IP addresses using the ipinfo.io service and returns structured location data (country, region, city, coordinates, timezone, etc.) via a simple provider adapter.
composer require geocoder-php/ip-info-provider
use Geocoder\ProviderManager;
use Geocoder\Geocoder;
use Geocoder\Provider\IpInfoProvider;
$provider = new IpInfoProvider('YOUR_IPINFO_TOKEN');
$providerManager = new ProviderManager();
$providerManager->addProvider($provider);
$geocoder = new Geocoder($providerManager);
$ip = request()->ip();
$result = $geocoder->geocode($ip);
$location = $result->first()->getCoordinates(); // [lat, lng]
$city = $result->first()->getCity();
src/Provider/IpInfoProvider.php (core logic)tests/ (for edge cases and examples)Attach location data to incoming requests:
namespace App\Http\Middleware;
use Closure;
use Geocoder\Geocoder;
class GeocodeRequestMiddleware
{
protected $geocoder;
public function __construct(Geocoder $geocoder)
{
$this->geocoder = $geocoder;
}
public function handle($request, Closure $next)
{
$ip = $request->ip();
$result = $this->geocoder->geocode($ip);
if ($result->first()) {
$request->merge([
'location' => [
'city' => $result->first()->getCity(),
'country' => $result->first()->getCountry(),
'coordinates' => $result->first()->getCoordinates(),
]
]);
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\GeocodeRequestMiddleware::class,
];
Combine with other providers (e.g., Google Maps) for robustness:
$providerManager = new ProviderManager();
$providerManager->addProvider(new IpInfoProvider('TOKEN'));
$providerManager->addProvider(new \Geocoder\Provider\GoogleMapsProvider('GOOGLE_KEY'));
$geocoder = new Geocoder($providerManager);
IpInfoProvider will be tried first; fall back to Google if needed.
Reduce API calls for static IPs (e.g., in a service):
use Illuminate\Support\Facades\Cache;
public function getLocationForIp(string $ip)
{
return Cache::remember("ipinfo_{$ip}", now()->addHours(1), function() use ($ip) {
return $this->geocoder->geocode($ip)->first();
});
}
Normalize responses for analytics or storage:
$location = $geocoder->geocode($ip)->first();
$data = [
'city' => $location->getCity(),
'region' => $location->getRegion(),
'country' => $location->getCountry(),
'coordinates' => $location->getCoordinates(),
'timezone' => $location->getTimezone() ?? null,
'postal' => $location->getPostalCode() ?? null,
];
Rate Limits
IpInfoProvider::doesSupport() for API errors.IPv6 Support
$ip = filter_var($request->ip(), FILTER_VALIDATE_IP);
Missing Fields
city/region (e.g., corporate networks or proxies).getExtraAttributes() to access raw data:
$rawData = $location->getExtraAttributes();
// Fallback: $rawData['city'] ?? $rawData['region'] ?? $rawData['country']
Timezone Ambiguity
overtrue/laravel-timezone.Enable HTTP Client Logging:
$provider = new IpInfoProvider('TOKEN', new \GuzzleHttp\Client([
'debug' => true,
]));
Check Laravel logs for raw API responses.
Validate the IP:
if (!$geocoder->geocode($ip)->valid()) {
// Handle invalid IP or API failure
}
Token Storage
.env:
IPINFO_TOKEN=your_token_here
AppServiceProvider:
$this->app->bind(IpInfoProvider::class, function ($app) {
return new IpInfoProvider(config('services.ipinfo.token'));
});
Custom HTTP Client
$client = new \GuzzleHttp\Client([
'timeout' => 5,
'headers' => ['Accept' => 'application/json'],
]);
$provider = new IpInfoProvider('TOKEN', $client);
Custom Response Mapping Extend the provider to map ipinfo.io fields to your schema:
class CustomIpInfoProvider extends IpInfoProvider
{
protected function parseResponse($data)
{
$data['custom_field'] = $data['company'] ?? null;
return parent::parseResponse($data);
}
}
Batch Processing Use the ipinfo.io bulk API for large datasets:
public function bulkGeocode(array $ips)
{
$client = new \GuzzleHttp\Client();
$response = $client->post('https://ipinfo.io/bulk', [
'json' => ['tokens' => ['TOKEN'], 'ips' => $ips],
]);
return json_decode($response->getBody(), true);
}
Fallback Logic
Implement a custom doesSupport() to skip invalid IPs:
public function doesSupport($query)
{
if (strpos($query, 'private') === 0 || strpos($query, '10.') === 0) {
return false; // Skip private IPs
}
return parent::doesSupport($query);
}
Testing Mock the provider for unit tests:
$provider = $this->createMock(IpInfoProvider::class);
$provider->method('geocode')->willReturn([$mockLocation]);
$providerManager->addProvider($provider);
How can I help you explore Laravel packages today?