setechsolutions/php-whois
A PHP package for performing WHOIS lookups on domains, retrieving registration and ownership details from WHOIS servers. Useful for domain availability checks, expiry information, and DNS-related tooling in Laravel or plain PHP applications.
Install with composer require setechsolutions/php-whois. There is no service provider to register — Laravel's package discovery picks up the provider and the Whois facade, so the fastest first check is a tinker session:
Whois::lookup('example.com'); // DomainInfo, or null when the domain is not registered
Three places to look first, in this order:
DomainInfo — the only result type. Everything is a method call, not a property: domain(), registrar(), owner(), createdAt(), expiresAt(), updatedAt(), statuses(), nameServers(). Dates are DateTimeImmutable or null; the object is immutable and JSON-serializable, which makes it safe to pass around and cache.config/whois.php — publish it (php artisan vendor:publish) only when you need to change defaults. WHOIS_MODE and WHOIS_TIMEOUT are the two knobs you will realistically touch, and both are read through config, so config:cache applies.WhoisException, with InvalidDomainException, UnsupportedTldException, ConnectionException and RateLimitException underneath. Catching the base class is enough to start; catching the subclasses is what makes the UX good later.First real use case: an availability check on the domain-search field of a signup or checkout flow. Validate the string is a domain (the package throws InvalidDomainException before any request, which is a useful free validation layer), call isAvailable(), and render "taken" only when you get a definitive answer — see the gotchas below.
Availability check with a three-way outcome. null from lookup() means "not registered"; exceptions mean "we could not answer". Collapse those two into "available" and you will hand out domains you don't own.
use SETechSolutions\Whois\Whois;
use SETechSolutions\Whois\Exceptions\WhoisException;
use SETechSolutions\Whois\Exceptions\InvalidDomainException;
use SETechSolutions\Whois\Exceptions\UnsupportedTldException;
try {
$info = Whois::lookup($domain);
} catch (InvalidDomainException $e) {
return back()->withErrors(['domain' => 'Not a valid domain name.']);
} catch (UnsupportedTldException $e) {
return back()->withErrors(['domain' => 'We cannot check that TLD automatically.']);
} catch (WhoisException $e) {
report($e);
return back()->withErrors(['domain' => 'Lookup failed, try again shortly.']);
}
$available = $info === null;
Renewal / expiry dashboard. The package is at its best as a scheduled enrichment job rather than a request-time call. Store domain, registrar, expires_at and refresh nightly; expiresAt() being null is normal for registries that don't publish dates in a machine-readable way, so treat it as "unknown", not "expired".
$info->expiresAt()?->format('Y-m-d') ?? 'unknown';
Cache aggressively, but cache the object. MemcachedLoader is gone in 5.0, so caching is your job. DomainInfo is immutable and JSON-serializable, so Cache::remember("whois:{$domain}", now()->addHours(12), fn () => Whois::lookup($domain)) works, and toArray() gives you a flat payload when you only need fields for a table or an API response. Registration data changes on the order of days, so a 6–24 hour TTL removes almost all of your lookup volume.
Bulk work goes on the queue, with per-domain failure isolation.
foreach ($domains->chunk(50) as $chunk) {
foreach ($chunk as $domain) {
CheckDomainJob::dispatch($domain);
}
}
Catch RateLimitException inside the job and release() with backoff rather than letting the whole batch fail — RDAP endpoints in particular have published rate limits, and the package raises the same exception for both protocols.
Mode and servers as configuration, not code. Set WHOIS_MODE per environment (rdap_first is the sensible default; whois_only locally if you're behind a proxy that blocks RDAP). rdap_servers and whois_servers passed in the options array are tried before the bundled servers for that zone, which is the clean way to point at a private or regional registry, or at a stub during manual testing.
new Whois(['mode' => Mode::WHOIS_ONLY, 'timeout' => 10]);
raw() is your debugging tool, not your production path. When a parser returns something surprising for a specific TLD, drop into raw($domain)->text() in a one-off artisan command or tinker to see what the registry actually sent. Calling it in a live request doubles your network calls.
null is not an error, and an exception is not availability. The single most common bug with this package is $available = Whois::lookup($d) === null without a try/catch, which turns a timeout into a "domain is free" message.$info->expirationDate returning 0 is gone; every date accessor can return null. Any ->format() without ?-> is a latent fatal error. The README example is the happy path only.timeout to 5–10 in the config for synchronous use, or push lookups to the queue. curl_options is available if you want a separate connect timeout.InvalidDomainException fires for anything that isn't a bare domain name — https://example.com, example.com/path, an email address. Normalise and strip before calling. Unidata/IDN domains should go through idn_to_ascii() first, since punycode conversion is not something to assume.replace entry any more. The Iodev\Whois namespace, Factory, the loaders and ASN lookups are all gone. If another dependency still requires io-developer/php-whois, Composer will now install both packages side by side rather than treating this one as a substitute — expect duplicated WHOIS logic and confusing class names. Projects relying on the drop-in replacement should stay on ^4.2 (the 4.x branch), and the migration table in the release notes is worth working through line by line rather than searching for Iodev\ imports.follow_registrar costs a second round trip. Enabling it gets you richer registrant data at the price of latency and another chance to be rate-limited. Enable it in the queued enrichment job, not in the checkout path.whois -h shell-out; caching needs Cache::remember around toArray() or the object itself.WHOIS_MODE and WHOIS_TIMEOUT are read from config/whois.php, so changing .env on a server with a cached config does nothing until php artisan config:cache is re-run. If the facade isn't found, check that package discovery hasn't been disabled in composer.json.Whois::shouldReceive('lookup')->andReturn(...)), but the cleanest long-term move is a thin DomainLookup interface of your own that returns DomainInfo/null, bound to the real Whois in production and a fake in tests. That also gives you one place to attach the cache.How can I help you explore Laravel packages today?