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.
Architecture fit
DomainInfo value object as the primary return type. It slots into the service layer of a Laravel app rather than dictating application structure — there is no persistence, queue, or event surface, which keeps it low-coupling.Whois facade, plus a publishable config/whois.php driven by WHOIS_MODE and WHOIS_TIMEOUT. Critically, the package has no dependency on Laravel, so the same code path is usable in CLI tools, jobs, and non-Laravel services. That is a good fit for teams running mixed workloads.lookup() returns null when a domain is not registered, and individual accessors (createdAt(), expiresAt(), updatedAt()) return null when a registry does not publish that field. isAvailable() is the boolean counterpart to the first case. This is a cleaner model than 4.x, but it moves null handling into every call site.Iodev\Whois namespace and the dropped replace entry for io-developer/php-whois is the single most important architectural fact. 5.0 is a greenfield API, not a drop-in successor. Any project that relied on the package as a transparent substitute must stay pinned to ^4.2 on the 4.x branch.Integration feasibility
composer require setechsolutions/php-whois, then Whois::lookup('example.com'). No setup, no manual provider registration.InvalidDomainException), which removes a class of defensive coding from calling code.WhoisException with meaningful subtypes (InvalidDomainException, UnsupportedTldException, ConnectionException, RateLimitException). This maps cleanly onto HTTP response codes and retry logic — a meaningful improvement over 4.x's ServerMismatchException and mixed failure semantics.bin/docker-test, bin/test-laravel-app). That materially reduces the cost of validating compatibility ourselves.Technical risk
replace removal all land at once. There is no deprecation bridge or compatibility shim. A migration table is provided, but every call site and every test touching lookup data must change.$info->expiresAt()->format('Y-m-d'), is unsafe on two counts: $info is null for unregistered domains, and expiresAt() is null when the registry omits the field. Both fail as a fatal error rather than a catchable WhoisException. Copy-pasted snippets will reach production.MemcachedLoader is removed and the release explicitly tells consumers to cache DomainInfo::toArray() themselves. Every team upgrading inherits an obligation to design and operate a cache layer, or accept the full latency and rate-limit cost on every lookup. DomainInfo being JSON-serializable softens this — the cache payload is a plain array, no serialization design needed — but the policy layer is still ours.DateTimeImmutable/null instead of unix time/0/''). Any code doing truthiness, numeric comparison, or arithmetic on expiry will break — sometimes loudly (type error), sometimes silently (a null treated as falsy). This is the highest-risk mechanical translation.Key questions
composer require or a multi-ticket migration with a defined freeze on the 4.x branch.rdap_first (default), whois_first, or a hard-pinned single-protocol mode for compliance or determinism reasons?WHOIS_TIMEOUT.MemcachedLoader is gone — TTL policy, key scheme, negative caching for null/available results, and cache stampede protection?rdap_servers / whois_servers for private or regional zones? Note that any server we supply takes precedence over the bundled one, so overrides cannot be used purely as a safety net.null returns from the wrapper, or a Result/Optional type that forces callers to handle unregistered domains and missing dates explicitly?io-developer/php-whois transitively elsewhere in the dependency graph? Dropping replace means Composer may now install both, with namespace collisions on the old side.RateLimitException handled — retry with backoff, degrade gracefully, or surface to the caller?Stack fit
Whois is globally available; the provider is discovered. config/whois.php is publishable when we need per-environment overrides.WHOIS_MODE and WHOIS_TIMEOUT via .env, with the richer options (follow_registrar, curl_options, rdap_servers, whois_servers) passed per-invocation as an array. This is the right split — environment-level defaults in config, request-level overrides in code.Whois instance is usable from artisan commands, queued jobs, and scheduled tasks. We should standardise on one wrapper service in our own codebase so the facade is not scattered across the application.Migration path
4.x branch is the correct home for anything that needs the drop-in io-developer/php-whois replacement behavior; treat 5.0 as a separate adoption decision.Factory::get()->createWhois() → new Whois(); loadDomainInfo() → lookup(); isDomainAvailable() → isAvailable(); lookupDomain()->text → raw()->text(); property access → accessor methods (domain(), registrar(), owner(), createdAt(), expiresAt(), updatedAt(), statuses(), nameServers()); setLookupMode(LookupMode::WHOIS_ONLY) → new Whois(['mode' => Mode::WHOIS_ONLY]); new CurlLoader() + replaceOptions() → new Whois(['curl_options' => [...]]); ServerMismatchException → UnsupportedTldException.MemcachedLoader, (b) the removal of ASN lookups, which needs either a replacement source or an explicit feature de-scope, and (c) null-handling policy for both lookup()'s null return and the nullable date accessors.0/'' → DateTimeImmutable/null) is the highest-risk mechanical translation because it is the one most likely to fail quietly. Grep for comparisons, arithmetic, and truthiness checks on the old date fields before merging, and add tests on the expiry path.Compatibility
laravel/framework pin — a meaningful advantage over packages that chase framework majors.replace entry for io-developer/php-whois is gone. Audit composer.lock for any transitive dependency on that package; if present, both will install and the old Iodev\ namespace will exist alongside the new one.ext-* requirements beyond what cURL implies are stated, but curl_options implies ext-curl is in play — verify the extension is present in every runtime image, including queue workers and CI.Sequencing
^4.2, plan separately). Do not attempt a partial upgrade.bin/docker-test / bin/test-laravel-app or our own harness to run lookup(), isAvailable(), and raw() against a representative TLD set on our exact PHP and Laravel pins before touching application code. Include at least one unregistered domain and one registry that omits a date field — these are the paths the examples do not cover.null/unregistered results, and stampede protection should exist before the first production lookup, since 5.0 provides none.DomainLookupService that owns the Whois instance, applies our options array, wraps exceptions into our domain errors, reads/writes the cache, and encapsulates the null contract (either a Result/Optional type or a documented null-safe accessor set). This confines the blast radius of the breaking API and makes any future major cheaper.0/'' sentinels.config/whois.php, set WHOIS_MODE and WHOIS_TIMEOUT per environment, defaulting the timeout well below 30s on any request-adjacent path.Maintenance
laravel/framework constraint.4.x and 5.x branches diverge permanently. If any part of the estate stays on 4.x, we own two integration patterns and two upgrade narratives indefinitely — a standing maintenance tax worth naming explicitly.Support
InvalidDomainException is a caller bug (and is raised pre-network, so it is cheap), UnsupportedTldException is a coverage gap, ConnectionException is infrastructure/protocol, RateLimitException is throttling. Each maps to a distinct runbook entry.RateLimitException being protocol-agnostic is the most operationally valuable change in this release: it converts a previously ambiguous failure into a signal we can alert and retry on.raw() provides an escape hatch for inspecting the underlying registry response when structured parsing yields surprises — valuable for support cases and worth documenting for the on-call rotation.null domain, a null expiry, or a missing name server are all normal returns and will not appear in error tracking; they will appear as wrong business outcomes. Support runbooks must cover "correct response, unexpected null" as a distinct class from "lookup failed".bin/docker-test, bin/test-laravel-app) reduce the cost of verifying a suspected version-specific bug and make upstream issue reports far more credible.Scaling
WHOIS_TIMEOUT (default 30s). Throughput scales with worker count and outbound connection capacity, not with application CPU. Unbatched lookups on a hot path will saturate PHP-FPM workers before they saturate anything else.follow_registrar can multiply the number of outbound requests per logical lookup. Enable it deliberately, and measure the amplification factor before turning it on at scale.RateLimitException regardless of our own capacity, so backoff and jitter are required, not optional.Failure modes
WHOIS_TIMEOUT, queue offload, circuit breaking.RateLimitException under concurrency. Mitigation: backoff with jitter, per-registry concurrency caps, aggressive caching.lookup() returning null for an unregistered domain, or a nullable expiresAt(), chained into a method call produces a fatal error rather than a handled exception. Mitigation: null-safe wrapper, static analysis at the strictest level our toolchain allows, and tests covering unregistered domains and date-less registries.0/'' now receives DateTimeImmutable or null, producing wrong expiry logic rather than an exception. Mitigation: explicit null handling, type-aware comparisons, and test coverage on expiry paths.UnsupportedTldException for zones with neither RDAP nor a bundled WHOIS server. Mitigation: enumerate our required TLDs up front, supply rdap_servers / whois_servers overrides where needed (accepting that they take precedence over bundled servers), and treat this as a known-gap class rather than an incident.replace dropped, a transitive io-developer/php-whois can coexist. Mitigation: lock-file audit in CI.null) rather than errors. Mitigation: monitor field-population rates and alert on sudden shifts.Ramp-up
RateLimitException backoff, ConnectionException triage, timeout tuning per environment, and how to use raw() to diagnose a parsing anomaly. Budget roughly a day of on-call documentation work.How can I help you explore Laravel packages today?