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

Ip2Proxy Php Laravel Package

ip2location/ip2proxy-php

Official IP2Proxy PHP library for detecting proxy, VPN, Tor, and other anonymous IP addresses. Install via Composer and query IP2Proxy BIN databases for IP type, country, ISP, domain, and threat data in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a plain PHP SDK, not a Laravel package — there is no auto-discovery, facade, or config. Expect to wire it in yourself in about four steps.

  1. Install the SDK:

    composer require ip2location/ip2proxy-php
    
  2. Get a database file. The SDK is useless without a .BIN file from ip2location.com. The free LITE DB is enough to detect proxy/VPN/TOR; the commercial PX1–PX11 tiers add ISP, ASN, threat score, provider, and residential proxy fields. Download it once and store it outside the web root, e.g. storage/app/ip2proxy/IP2PROXY-LITE-PX11.BIN. Add it to your deploy script and .gitignore.

  3. Register a singleton. The Database object opens the file on open(), so bind it once and resolve it per request:

    // app/Providers/Ip2ProxyServiceProvider.php
    public function register(): void
    {
        $this->app->singleton(\IP2Proxy\Database::class, function () {
            $db = new \IP2Proxy\Database();
            $db->open(config('services.ip2proxy.bin'), \IP2Proxy\Database::FILE_IO);
            return $db;
        });
    }
    
    // config/services.php
    'ip2proxy' => [
        'bin' => env('IP2PROXY_BIN', storage_path('app/ip2proxy/IP2PROXY-LITE-PX11.BIN')),
    ],
    
  4. First use case — flag proxied signups. The lowest-friction integration is a form request rule or a controller check:

    $db = app(\IP2Proxy\Database::class);
    if ($db->isProxy($request->ip()) === 1) {
        // log, require extra verification, or reject
    }
    

Look at the repository README first — it lists the method names (isProxy, getProxyType, getAll, getCountry, etc.) and the field set for each DB tier. Match the tier you downloaded to the fields you call; calling getUsageType() on a DB that doesn't have it returns an empty string, not an error.

Implementation Patterns

Service provider + config. Bind Database as a singleton and read the BIN path from config so each environment (local, staging, production) can point at its own file. Open the file in the resolver, not in a helper, so a missing/typo'd path fails loudly at resolution time rather than silently returning -1 from a lookup.

Custom validation rule. This is the pattern most teams land on:

class NotProxyRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $result = app(\IP2Proxy\Database::class)->isProxy($value);
        if ($result === 1) {
            $fail('Registrations from VPNs or proxies are not allowed.');
        }
    }
}

Keep the === 1 comparison — isProxy returns 1, 0, or -1 for an invalid/unsupported IP.

Middleware for route groups. Wrap high-risk routes (login, checkout, signup) in a BlockProxies middleware:

public function handle(Request $request, Closure $next)
{
    $ip = $request->ip();
    $cacheKey = "ip2proxy:{$ip}";
    $type = Cache::remember($cacheKey, now()->addDay(), fn () =>
        app(\IP2Proxy\Database::class)->getProxyType($ip)
    );

    if ($type !== '-') {
        Log::channel('security')->info('proxy_blocked', ['ip' => $ip, 'type' => $type]);
        abort(403);
    }
    return $next($request);
}

Cache by IP for a day or a week. Proxy classifications change slower than you might think, and a per-request BIN lookup plus Redis is a lot cheaper than hitting the DB on every route.

Granular decisions, not binary blocks. getProxyType() returns codes like VPN, TOR, PUB, WEB, DCH, SES, RES. Treat TOR and VPN differently from DCH (data center). A SaaS product often allows data center IPs (CI/CD, crawlers) but blocks VPN and TOR, while a consumer app might block all three at checkout but allow DCH signups. Encode this policy in a single small class so it lives in one place:

class ProxyPolicy
{
    public function __construct(private array $blockedTypes) {}

    public function blocks(string $ip): bool
    {
        $type = app(\IP2Proxy\Database::class)->getProxyType($ip);
        return in_array($type, $this->blockedTypes, true);
    }
}

Combine with geo. The same BIN returns countryCode, regionName, cityName. If you already need geo in the same request, call getAll() once and read both fields, rather than making two lookups.

Testable fakes. Because the SDK class is concrete, wrap it in a tiny interface (ProxyLookup) or at minimum swap it in the container in tests:

$this->app->instance(\IP2Proxy\Database::class, Mockery::mock(\IP2Proxy\Database::class, [
    'isProxy' => 1,
]));

Pin a few known TOR exit IPs into a feature test so the middleware and rule stay honest after a refactor.

Async / batch enrichment. getAll() is fast (microseconds to low milliseconds), but for bulk jobs (auditing an existing user base) run it in chunks and write results to a table. Don't call it in a loop over 100k users in a single request.

Gotchas and Tips

  • isProxy is a tri-state integer. 1 = proxy, 0 = not, -1 = invalid IP or lookup failure. A truthy check will treat -1 correctly, but === true or if ($db->isProxy($ip)) reads badly and hides failures. Use strict comparisons.
  • BIN file paths are the #1 failure point. Use an absolute path from storage_path() or base_path(). Relative paths resolve against the working directory, which differs between Artisan, queue workers, and FPM. Also verify the file exists in your deploy pipeline — a missing file is a runtime exception, not a graceful "unknown IP".
  • FILE_IO vs in-memory. FILE_IO reads segments off disk per lookup and is the right default for FPM. Memory-cached mode is a big win under Laravel Octane (or Swoole/RoadRunner) where the singleton actually survives between requests — but under stock PHP-FPM it just eats RAM per worker for no benefit.
  • You're looking up the wrong IP behind a proxy. $request->ip() returns the load balancer or Cloudflare edge unless TrustProxies is configured. If you're behind Cloudflare, trust the CF headers in App\Http\Middleware\TrustProxies; otherwise every request will look like your LB's IP and look identical in the DB.
  • IPv6 coverage varies by tier. Some databases are IPv4-only. Test with a known IPv6 address and check getProxyType() returns a code rather than -. If you support IPv6 users, verify the specific BIN you downloaded covers them.
  • LITE vs commercial field differences. usageType, threat, provider, and as are populated only on higher tiers. Calling getUsageType() on a LITE DB silently returns empty. If your business logic branches on these, add a config('services.ip2proxy.tier') guard or a feature test that asserts the field is non-empty on a known IP.
  • Database staleness is the silent bug. Proxy lists go stale in weeks, not months. Add a scheduled job (php artisan ip2proxy:refresh) that downloads the latest .BIN and swaps it in atomically (download to .tmp, rename()). Without this, your accuracy quietly degrades and nobody notices until customers complain.
  • Don't resolve the singleton in config/*.php or service providers that run on every request. Opening the BIN file on php artisan config:cache or on every queue-worker boot is a small but real startup cost; keep it lazy.
  • Extension points are limited by design. The Database class is final-ish in spirit — you can't subclass cleanly to add caching or metrics. The practical pattern is a thin wrapper (interface + adapter) around the SDK so you can mock it in tests, add Redis caching, and swap for a different provider later without touching call sites. This is also your path to an HTTP fallback (IP2Location's API) if you ever run on serverless where shipping a 50 MB BIN is impractical.
  • Debugging -1. When isProxy returns -1, it's either a malformed IP, an IP type (v6) the DB doesn't support, or a corrupt BIN. Log the IP, the DB path, and PHP_INT_SIZE — the SDK's error handling is minimal, so your wrapper should be the thing generating actionable logs.
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.
users-au/socialite-provider
twidpay/twid-logger
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