davidepastore/ipinfo
davidepastore/ipinfo is a PHP/Laravel wrapper for the ipinfo.io API, making it easy to look up geolocation, ISP, hostname, and other network details for IP addresses through a simple, framework-friendly interface. Ideal for enriching requests with location and network context.
Install and sanity-check the source first. composer require davidepastore/ipinfo — then open vendor/davidepastore/ipinfo/src/ before writing any code. The package is small (an Ipinfo client, an IpinfoFactory, a Host value object, and a handful of exceptions), and the exact factory/method names are the only thing you need to confirm. Ten minutes of reading saves an hour of guessing.
It is not a Laravel package. There is no service provider, no auto-discovery, no publishable config, no facade. You wire it yourself once:
// config/services.php
'ipinfo' => [
'token' => env('IPINFO_TOKEN'),
],
// app/Providers/AppServiceProvider.php
use DavidePastore\Ipinfo\IpinfoFactory;
public function register(): void
{
$this->app->singleton(IpinfoFactory::class, function () {
$factory = new IpinfoFactory();
$factory->setToken(config('services.ipinfo.token'));
return $factory;
});
$this->app->bind(\DavidePastore\Ipinfo\Ipinfo::class, fn ($app) =>
$app->make(IpinfoFactory::class)->create()
);
}
First use case: geo-context on a request. Look up the visitor's IP once, then use city, country, timezone, and org to pick a currency, format dates, or flag datacenter/VPN traffic:
use DavidePastore\Ipinfo\Ipinfo;
public function show(Request $request, Ipinfo $ipinfo)
{
$host = $ipinfo->getFullIpInfo($request->ip());
return view('welcome', [
'city' => $host->getCity(),
'country' => $host->getCountry(),
'timezone' => $host->getTimezone(),
'org' => $host->getOrg(), // ASN + provider, good VPN heuristic
]);
}
Get this working end-to-end with a real public IP (e.g. 8.8.8.8) before adding caching, middleware, or abstraction — those are the next section.
Wrap it in your own GeoLocator contract. This is the single highest-leverage move. The package is a thin HTTP wrapper, and its GPL license plus lack of maintenance (last release Dec 2022) mean you want it swappable:
interface GeoLocator
{
public function forIp(string $ip): ?GeoResult;
}
Bind IpinfoLocator implements GeoLocator and have it translate Host into your own DTO. Everything downstream (controllers, jobs, Blade) type-hints GeoLocator, never Host. If you later drop the dependency for two lines of Http::get(), only one class changes.
Cache aggressively by IP, not by user. ipinfo's free tier is rate-limited, and the same IPs recur constantly:
public function forIp(string $ip): ?GeoResult
{
return Cache::remember("geo:{$ip}", now()->addDays(30), function () use ($ip) {
try {
return GeoResult::fromHost($this->ipinfo->getFullIpInfo($ip));
} catch (\Throwable $e) {
report($e);
return null; // fail open — geo is never worth a 500
}
});
}
Cache null results too (or cache a short-TTL sentinel), otherwise a broken API key turns into one outbound call per request.
Use a middleware for ambient request context. Resolve the GeoLocator, attach the result to the request or a container singleton, and let controllers read it lazily. Keep the lookup out of the middleware for unauthenticated or bot traffic, and always short-circuit for private IPs (see below).
Guard every call site. Private, loopback, and link-local addresses are not resolvable and will either throw or return a bogon flag:
$isPublic = filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) !== false;
Also respect TrustProxies; $request->ip() behind a load balancer returns the proxy address unless configured, and you'll silently geolocate your own datacenter.
Use it for enrichment jobs, not hot paths. Queued jobs that backfill country/org onto leads, orders, or audit logs are the natural fit. For bulk historical data, check whether the client exposes a batch method on the factory/client; if not, chunk the IPs into jobs with a delay to stay inside the rate limit rather than looping synchronously.
Testing. Because there's no Laravel HTTP client integration, Http::fake() will not intercept this package's calls — it uses its own transport. Test by binding a fake GeoLocator into the container (clean, always works) or by injecting a stubbed HTTP client into the factory if the factory exposes a setter. Never let tests hit ipinfo.io in CI.
Http::get(), and the wrapper buys you very little.Http::withToken(...)->get("https://ipinfo.io/{$ip}/json"). Consider that trade honestly before installing.php artisan vendor:publish for this package is wrong. Binding in AppServiceProvider is the whole setup.?token=..., so it lands in HTTP logs, tracing spans, and exception messages. Scrub it in your handler and never log the full request URI on outbound calls.postal, some ranges have no hostname. Write getters defensively ($host->getCity() ?? 'Unknown') and don't assume loc is split — it arrives as a "lat,lon" string.\Exception at the top level, so you can distinguish a bad token (InvalidParameter-style) from a transient network error. Fail open on lookups; geo is decoration, not business logic.composer.json, add one integration test that exercises a real call path, and be ready to fork.setToken, plus a config array or HTTP-client setter depending on version. If you need retries, timeouts, or custom headers, you'll be wrapping rather than configuring, which is another argument for your own GeoLocator layer.How can I help you explore Laravel packages today?