Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ip Location Laravel Package

gai871013/ip-location

A Laravel package for resolving IP addresses to geographic locations. Supports multiple data sources like QQWry, ip2region, and MaxMind, with a simple unified API for looking up country, province, city, and ISP details. Configurable drivers and caching make integration easy.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package (Laravel 5.5+ will auto-discover the service provider and IpLocation facade):

    composer require gai871013/ip-location
    
  2. Publish the config — the package ships a config/ip-location.php that points at the .dat file:

    php artisan vendor:publish --provider="Gai871013\IpLocation\IpLocationServiceProvider"
    
  3. Supply the QQWry.dat file yourself. This is the single most important setup step — the package does not bundle the database. Download the latest qqwry.dat from a mirror of 纯真 (e.g. https://www.cz88.net/) and place it at the path defined by the config (commonly storage/app/qqwry.dat), then set the path explicitly in .env or config:

    // config/ip-location.php
    'dat_path' => storage_path('app/qqwry.dat'),
    
  4. First lookup — the primary daily use case is resolving a visitor's IP to a country/area string:

    use Gai871013\IpLocation\Facades\IpLocation;
    
    $info = IpLocation::getLocation('114.114.114.114');
    // ['country' => '中国', 'area' => '江苏 南京']
    

    From here, the natural first integration is a login/audit middleware that records $request->ip() alongside the resolved location.

Implementation Patterns

Middleware for visitor tagging. This is where the package earns its keep — attaching geolocation to every request is more useful than ad-hoc lookups:

class RecordIpLocation
{
    public function handle($request, Closure $next)
    {
        $ip = $request->ip();
        if ($ip && !IpLocation::isPrivate($ip)) {
            $request->attributes->set('geo', IpLocation::getLocation($ip));
        }
        return $next($request);
    }
}

Do the lookup lazily when possible — resolving on every request (including assets, health checks) burns CPU on the QQWry binary search.

Wrap lookups in a cache. The .dat file is a ~10MB binary with sequential seeks; without caching you'll re-read it on every request:

$geo = Cache::remember("geo:{$ip}", now()->addDays(30), fn() =>
    IpLocation::getLocation($ip)
);

Key by IP, not by session — locations are stable for weeks.

Batch/backfill jobs. When importing historical logs, resolve inside a queued job that chunks IPs and writes to the model. Keep the IpLocation facade out of controllers for anything beyond a single request.

Model accessor or cast. If you store raw IPs, add an accessor so Blade views stay clean:

public function getCountryAttribute(): ?string
{
    return $this->ip ? IpLocation::getLocation($this->ip)['country'] ?? null : null;
}

Analytics / dashboards. Because the package only returns country/area strings, group by those two fields directly. Store them as columns on your users/logs table (denormalize) rather than re-resolving in every report query.

Fallback chain. Wrap in a small resolver service that tries IpLocation first, then falls back to an online API or geo-header for IPs that resolve to 未知 (unknown) — see gotchas.

Testing. Bind a fake into the container so tests don't depend on the .dat file:

$this->app->bind(IpLocation::class, fn() => new FakeIpLocation());

or simply stub the facade with IpLocation::shouldReceive('getLocation')->andReturn([...]).

Gotchas and Tips

  • Encoding is GBK, not UTF-8. QQWry stores Chinese strings in GBK. If your app is UTF-8 (mine always is), the returned country/area may look garbled or trigger json_encode failures. Wrap results with mb_convert_encoding($text, 'UTF-8', 'GBK') if the package version you install doesn't normalise it — verify on a first lookup before trusting the output.

  • The .dat file is not shipped and must be refreshed manually. 纯真 updates weekly; stale files mean wrong locations for reassigned IP blocks. Schedule a job that downloads a fresh qqwry.dat and swaps it atomically (write to qqwry.dat.tmp, then rename()), because a mid-read file replacement can throw off offsets.

  • Private and reserved IPs return empty or 未知. 127.0.0.1, 10.*, 192.168.*, Docker bridge networks, etc. Guard with a check — the package has no reliable isPrivate helper across versions, so keep your own:

    filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
    
  • Non-Chinese IPs resolve poorly. This is a Chinanet-oriented database — Google, AWS, EU residential traffic will frequently show 美国 (US) or a generic label. If you serve a global audience, treat this as a China-focused tool and pair it with a second source for the rest of the world.

  • File handle / memory pressure. Each getLocation() call opens the .dat file. In loops over thousands of rows you'll thrash the filesystem. Always cache + chunk; in queue workers, resolve once per unique IP rather than per row.

  • Config caching pitfall. After changing dat_path, run php artisan config:clear. Forgetting this is the #1 "file not found" debugging step — the service provider bakes the path into the singleton at boot.

  • Verify facade name before coding. Recent versions expose Gai871013\IpLocation\Facades\IpLocation, but older ones register the alias differently. Run php artisan about or check vendor/gai871013/ip-location/src/ for the actual class name and public methods (getLocation, getArea, getCountry are common but not identical between releases).

  • Return shape is a plain array, not a value object, and the keys/format vary by version. Type-hint nothing; instead normalise in your own GeoResult DTO if you call the facade from more than a couple of places — this makes future package swaps (to stevebauman/location, e.g.) a one-file change.

  • Extension point: swap the driver. Because everything goes through the facade, you can decorate it with your own IpLocationService that layers caching, private-IP handling, and an online fallback — then inject that everywhere instead of the facade, keeping the package a low-level detail.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle