rasuvaeff / domain-monitor
Domain monitoring toolkit for HTTP, SSL, WHOIS, DNS, ports, security headers, robots.txt, and sitemaps.
Requires
- php: ^8.3
- ext-libxml: *
- ext-openssl: *
- ext-simplexml: *
- io-developer/php-whois: ^4.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
- psr/log: ^3.0
Requires (Dev)
- ergebnis/composer-normalize: ^2.51
- friendsofphp/php-cs-fixer: ^3.95
- guzzlehttp/guzzle: ^7.10
- infection/infection: ^0.29
- maglnet/composer-require-checker: ^4.17
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.5
- psalm/plugin-phpunit: ^0.19
- rector/rector: ^2.4
- vimeo/psalm: ^6.16
Suggests
- ext-intl: Enables IDN (internationalized domain name) normalization in HostNormalizer
- guzzlehttp/guzzle: PSR-18 HTTP client implementation
- nyholm/psr7: PSR-17 request factory implementation
- symfony/http-client: Alternative PSR-18 HTTP client
This package is auto-updated.
Last update: 2026-06-01 08:33:15 UTC
README
A modular domain monitoring toolkit for PHP 8.3+. Zero-framework, PSR-compatible, with small immutable DTOs and focused stateless services. Each checker does one thing — you compose them as needed.
Checks: HTTP probing · SSL certificates · WHOIS · DNS · TCP ports · security headers · robots.txt · sitemaps.
Does not include: scheduling, persistence, caching, async runners, or a "check all" orchestrator. The package provides the building blocks; your application provides the workflow.
Using an AI coding assistant? llms.txt contains a compact API reference.
Requirements
- PHP 8.3+
ext-openssl,ext-simplexml- A PSR-18 client and PSR-17 request factory for HTTP-based checks
io-developer/php-whois(pullsext-curl,ext-mbstring)ext-intlis optional (IDN normalization only)ext-socketsis optional (DNS resolution only)
Installation
composer require rasuvaeff/domain-monitor
For HTTP checks you'll also need a PSR-18/PSR-17 implementation:
composer require symfony/http-client nyholm/psr7
Quick start: a full domain check
use DateTimeImmutable; use Iodev\Whois\Factory; use Nyholm\Psr7\Factory\Psr17Factory; use Rasuvaeff\DomainMonitor\{ DnsService, DomainHealthReport, HttpContentCheckService, HttpProbeService, PortService, RobotsTxtService, SecurityHeadersService, SitemapService, SslCertificateService, WhoisService, }; use Symfony\Component\HttpClient\Psr18Client; $host = 'example.com'; $client = new Psr18Client(); $requestFactory = new Psr17Factory(); $report = new DomainHealthReport( host: $host, probe: (new HttpProbeService(httpClient: $client, requestFactory: $requestFactory)) ->check(url: "https://{$host}"), ssl: (new SslCertificateService())->check(host: $host), whois: (new WhoisService(whois: Factory::get()->createWhois()))->check(host: $host), dns: (new DnsService())->check(host: $host), port: (new PortService())->check(host: $host, port: 443), ); // Aggregate status: worst among checks (OK → WARNING → CRITICAL → UNKNOWN) echo $report->getStatus()->value;
Services
HTTP probing
use Rasuvaeff\DomainMonitor\HttpProbeOptions; use Rasuvaeff\DomainMonitor\HttpProbeService; $probe = (new HttpProbeService(httpClient: $client, requestFactory: $requestFactory)) ->check( url: 'https://example.com', options: new HttpProbeOptions(method: 'HEAD', timeoutSeconds: 10.0), ); // ProbeResult { status: 200, totalTime: 0.12 } var_dump($probe->status, $probe->totalTime);
timeoutSeconds is best-effort only — PSR-18 has no standard timeout API. Clients like Symfony's honor it; clients like raw Guzzle may not.
SSL
use Rasuvaeff\DomainMonitor\SslCertificateService; $cert = (new SslCertificateService())->check( host: 'example.com', expectedOrg: 'Example Inc.', // optional org filter ); if ($cert !== null) { echo $cert->daysUntilExpiry(); // e.g. 45 echo $cert->isExpiringWithin(days: 30); // false echo $cert->subjectCn; // "example.com" echo $cert->issuer; // "Example CA" }
Note: SSL check reads the peer certificate without trust chain verification — it's a monitoring tool, not a PKI validator.
WHOIS
use Iodev\Whois\Factory; use Rasuvaeff\DomainMonitor\WhoisService; $info = (new WhoisService(whois: Factory::get()->createWhois())) ->check(host: 'example.com'); // TldInfo { domain, ?registrar, ?expirationDate, states } echo $info->daysUntilExpiry(); // null if expirationDate missing
Fallback: if www.example.com fails, the service retries with example.com automatically.
DNS
use Rasuvaeff\DomainMonitor\DnsService; $records = (new DnsService())->check(host: 'example.com'); // DnsRecords { a: ['93.184.216.34'], mx: ['...'], ns: ['...'], ... } var_dump($records->a, $records->mx);
Port check (TCP)
use Rasuvaeff\DomainMonitor\PortService; $check = (new PortService())->check(host: 'example.com', port: 443, timeoutSeconds: 5.0); // PortCheck { status: OK, connectTime: 0.04, error: null }
Security headers
use Rasuvaeff\DomainMonitor\SecurityHeadersService; // Pass a PSR-7 ResponseInterface (from a prior HTTP probe) $headers = (new SecurityHeadersService())->check(response: $response); // SecurityHeadersCheck { hasHsts: true, hasContentSecurityPolicy: false, ... }
robots.txt
use Rasuvaeff\DomainMonitor\RobotsTxtService; $robots = (new RobotsTxtService(httpClient: $client, requestFactory: $requestFactory)) ->check(baseUrl: 'https://example.com'); // RobotsTxtCheck { exists: true, sitemaps: ['https://example.com/sitemap.xml'] }
Sitemap
use Rasuvaeff\DomainMonitor\SitemapService; $sitemap = (new SitemapService(httpClient: $client, requestFactory: $requestFactory)) ->check(sitemapUrl: 'https://example.com/sitemap.xml'); // SitemapCheck { exists: true, urlCount: 42 }
Content check
use Rasuvaeff\DomainMonitor\HttpContentCheckService; $content = (new HttpContentCheckService(httpClient: $client, requestFactory: $requestFactory)) ->check( url: 'https://example.com', expectedStatus: 200, requiredText: 'Example Domain', // must be present forbiddenText: 'Internal Error', // must NOT be present ); // HttpContentCheck { status: OK, requiredTextFound: true, forbiddenTextFound: false }
Build a report
use Rasuvaeff\DomainMonitor\{DomainHealthReport, CheckStatus}; use Rasuvaeff\DomainMonitor\ProbeResult; use Rasuvaeff\DomainMonitor\SslCertificate; $report = new DomainHealthReport( host: 'example.com', probe: new ProbeResult(status: 200, totalTime: 0.13), ssl: new SslCertificate(/* ... */), whois: $tldInfo, dns: $dnsRecords, ); echo $report->getStatus()->value; // 'ok' | 'warning' | 'critical' | 'unknown'
Full API reference
| Class | What it does |
|---|---|
HostNormalizer |
Normalize hosts/URLs (lowercase, strip scheme/port/path, optional IDN) |
HttpProbeService |
PSR-18 GET/HEAD probe with measured time → ProbeResult |
HttpProbeOptions |
Configure method, headers, timeout, user-agent for HTTP probes |
ProbeResult |
DTO: status, totalTime |
SslCertificateService |
Read remote SSL cert; optional org filter → SslCertificate |
SslCertificate |
DTO: validFrom, validUntil, subjectCn, issuer + expiry helpers |
WhoisService |
Load & map WHOIS vendor data → TldInfo |
TldInfo |
DTO: domain, ?registrar, ?expirationDate, states |
DnsService |
dns_get_record() wrapper → DnsRecords |
DnsRecords |
DTO: a, aaaa, mx, ns, txt, cname |
PortService |
TCP reachability via stream_socket_client() → PortCheck |
PortCheck |
DTO: status, host, port, connectTime, ?error |
SecurityHeadersService |
Check HSTS/CSP/XFO/XCTO on a PSR-7 response → SecurityHeadersCheck |
SecurityHeadersCheck |
DTO: flags for each header + present/missing lists |
RobotsTxtService |
Fetch /robots.txt + extract Sitemap hints → RobotsTxtCheck |
RobotsTxtCheck |
DTO: exists, httpStatus, sitemaps[] |
SitemapService |
Fetch sitemap + count <url> entries → SitemapCheck |
SitemapCheck |
DTO: exists, httpStatus, urlCount |
HttpContentCheckService |
Status code + required/forbidden keyword check → HttpContentCheck |
HttpContentCheck |
DTO: status, httpStatus, ?finalUrl, text flags |
DomainHealthReport |
Composite DTO for all check results → CheckStatus |
CheckStatus |
Enum: OK, WARNING, CRITICAL, UNKNOWN |
Security
- HTTP checks accept only
httpandhttpsURLs. - Host inputs are normalized and validated before use.
SslCertificateServicereads peer certificates in monitoring mode (verify_peer: false) — it does not validate the PKI trust chain.- The package does not make any network requests on its own: it relies on user-provided PSR-18 clients and WHOIS instances.
Examples
See examples/ for runnable scripts.
| Script | Shows | Network? |
|---|---|---|
http-probe.php |
HTTP probe + content check | Yes |
ssl-whois-dns.php |
SSL, WHOIS, and DNS | Yes |
port.php |
TCP port check with custom host/port | Yes |
security-headers.php |
Check security headers on a live URL | Yes |
robots.php |
Fetch /robots.txt and extract sitemaps |
Yes |
sitemap.php |
Fetch sitemap and count URLs | Yes |
report.php |
Build a DomainHealthReport from DTOs |
No |
Run examples:
php examples/port.php example.com 443 php examples/security-headers.php https://example.com
Development
No PHP/Composer on the host — run in Docker via the composer:2 image:
docker run --rm -v "$PWD":/app -w /app composer:2 composer install docker run --rm -v "$PWD":/app -w /app composer:2 composer build
Or with Make:
make install
make build
make cs-fix
make test
Integration tests (marked @coversNothing) skip unless DOMAIN_MONITOR_NET=1 is set:
DOMAIN_MONITOR_NET=1 make test