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

Whip Laravel Package

vectorface/whip

Whip is a lightweight PHP library for reliably determining a client’s real IP address, even behind proxies, load balancers, and CDNs. It supports IPv4/IPv6, trusted proxy headers, Cloudflare, and multi-proxy setups, helping PHP and Laravel apps log, rate-limit, and secure requests accurately.

View on GitHub
Deep Wiki
Context7

Getting Started

vectorface/whip is a small, framework-agnostic client-IP resolver. It answers the question "who is actually calling me?" when your app sits behind a CDN, load balancer, or reverse proxy that rewrites/forwards client IPs via headers like X-Forwarded-For, CF-Connecting-IP, or X-Real-IP. Laravel's own TrustProxies middleware handles the simple single-proxy case; Whip is the tool you reach for when you need multiple header fallbacks, CIDR-based trusted-proxy matching, and safe validation without trusting spoofable headers outright.

composer require vectorface/whip

Then check the repo README first — it carries the canonical list of HEADER_* constants, which is the part you'll reference most. There is no Laravel service provider shipped; this is a plain library you wire up yourself.

First use case — resolving a real IP behind Cloudflare + a load balancer:

use Vectorface\Whip\Whip;

$whip = new Whip();

// Only trust these upstreams, using CIDR notation
$whip->addTrustedProxy('173.245.48.0/20')       // Cloudflare range
     ->addTrustedProxy('10.0.0.0/8');          // internal LB

// Only trust headers your infrastructure actually sets
$whip->addTrustedHeader(Whip::HEADER_CF_CONNECTING_IP)
     ->addTrustedHeader(Whip::HEADER_X_FORWARDED_FOR);

$ip = $whip->getValidIpAddress(); // string|false — validated with filter_var

Wrap it in a tiny Laravel binding so controllers/services can inject it, and register a Request::macro or a small ClientIp value object rather than calling new Whip() all over the codebase.

Implementation Patterns

Bind as a singleton/service in a provider. Configure the trusted proxies and headers once from config/whip.php, then inject:

// AppServiceProvider::register()
$this->app->singleton(Whip::class, fn () => tap(new Whip(), function (Whip $whip) {
    foreach (config('whip.trusted_proxies', []) as $proxy) {
        $whip->addTrustedProxy($proxy);
    }
    foreach (config('whip.trusted_headers', []) as $header) {
        $whip->addTrustedHeader($header);
    }
    $whip->setCache($this->app->make(CacheInterface::class));
});

Use the PSR-16 cache for hostname-based proxies. Trusted proxies may be hostnames; Whip resolves them (gethostbyname) and uses the cache to avoid a DNS lookup per request. Pass your Laravel cache store (which implements PSR-16) via setCache() — this matters under load.

Prefer getValidIpAddress() over getIpAddress(). getIpAddress() returns whatever the header contained, unvalidated and spoofable. getValidIpAddress() walks the chain, discards entries that fail filter_var, and returns false when nothing trustworthy is found. Always have a fallback:

$ip = $whip->getValidIpAddress() ?: $request->server('REMOTE_ADDR');

Middleware pattern. Resolve once, stash it, and let the rest of the app read it:

class ResolveClientIp
{
    public function handle($request, Closure $next)
    {
        $request->attributes->set('client_ip', app(Whip::class)->getValidIpAddress() ?: $request->ip());
        return $next($request);
    }
}

Integration tip — pick one source of truth. If you keep Laravel's TrustProxies middleware active, have it trust the same upstreams Whip does, or disable one. Two independent trusted-proxy configurations drifting apart is a classic source of "wrong IP in logs but right IP in Stripe webhook" bugs.

Useful beyond HTTP. Whip is genuinely handy in queue workers, CLI cron scripts, and webhook verifiers where you have a raw header bag but not a Request — its only input is $_SERVER, which you can control.

Gotchas and Tips

  • v0.5.0 drops PHP 7.x. This release bumps the floor to PHP 8.0 and modernizes the code. If you're still on PHP 7.4 (Laravel 8 and below), pin vectorface/whip:^0.4. Laravel 9+ projects on PHP 8.0+ can upgrade with no code changes — the public API is unchanged in this release.
  • v0.x means no BC promise. There is no semver stability guarantee here; v0.5.0 is the first release with CI and modernized code, which is good news for maintenance, but budget for review on each minor bump and pin exactly in composer.json if the IP logic is security-sensitive.
  • Trusted headers default is closed, not open. If you construct Whip and forget addTrustedHeader(), you get no header parsing at all — some developers find this silently confusing. Conversely, calling setTrustedHeaders() with the full list of constants effectively trusts every header, which is exactly the spoofing hole the library exists to prevent. Enumerate only what your edge actually injects.
  • CIDR syntax and bits. Trusted proxies accept CIDR (10.0.0.0/8, 2001:db8::/32). A bare hostname is also accepted (with DNS resolution + cache). Malformed entries are where things fail silently — log/validate your proxy list at boot rather than discovering it in production.
  • X-Forwarded-For chain semantics. The header is append-only left-to-right; the rightmost entry is the one your own proxy added, the leftmost is the claimed original client. Whip walks from the right past trusted proxies, discarding untrusted/unvalidated entries. Do not naively explode(',') and take [0] anywhere else in the app — that's the classic spoof.
  • IPv4 vs IPv6. filter_var validation is correct for both, but if you're storing the result in a VARCHAR(15) column or using ip2long, you'll silently truncate IPv6 addresses. Use VARCHAR(45) + inet_pton.
  • Debugging: dump $whip->getValidIpAddress() alongside $request->ip() and $request->server('REMOTE_ADDR') when they disagree. A mismatch almost always means a trusted-proxy entry is missing (so Whip never steps past the LB) or a trusted header is missing (so Whip returns false).
  • Extension points are thin by design. There's no plugin system; customize via the constructor/config and by wrapping the class. Treat it as a leaf dependency, not something to subclass.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource