PHPackages                             rasuvaeff/domain-monitor - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. rasuvaeff/domain-monitor

ActiveLibrary[HTTP &amp; Networking](/categories/http)

rasuvaeff/domain-monitor
========================

Domain monitoring toolkit for HTTP, SSL, WHOIS, DNS, ports, security headers, robots.txt, and sitemaps.

v1.2.0(2w ago)051BSD-3-ClausePHPPHP 8.3 - 8.5CI passing

Since Jun 1Pushed 2w agoCompare

[ Source](https://github.com/rasuvaeff/domain-monitor)[ Packagist](https://packagist.org/packages/rasuvaeff/domain-monitor)[ Docs](https://github.com/rasuvaeff/domain-monitor)[ RSS](/packages/rasuvaeff-domain-monitor/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (48)Versions (6)Used By (0)

rasuvaeff/domain-monitor
========================

[](#rasuvaeffdomain-monitor)

[![Latest Stable Version](https://camo.githubusercontent.com/be6e8857d10e84b296fa21b89ebd0ae9adbedd5d4dd60cfdfe2fe30a390b5657/68747470733a2f2f706f7365722e707567782e6f72672f7261737576616566662f646f6d61696e2d6d6f6e69746f722f76)](https://packagist.org/packages/rasuvaeff/domain-monitor)[![Total Downloads](https://camo.githubusercontent.com/4d99c5a9879fbc728a7759a98b1552124cf8a464463daf7108274909a144006c/68747470733a2f2f706f7365722e707567782e6f72672f7261737576616566662f646f6d61696e2d6d6f6e69746f722f646f776e6c6f616473)](https://packagist.org/packages/rasuvaeff/domain-monitor)[![Build](https://github.com/rasuvaeff/domain-monitor/actions/workflows/build.yml/badge.svg)](https://github.com/rasuvaeff/domain-monitor/actions/workflows/build.yml)[![Static analysis](https://github.com/rasuvaeff/domain-monitor/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/rasuvaeff/domain-monitor/actions/workflows/static-analysis.yml)[![Psalm level](https://camo.githubusercontent.com/68f7f31799f2b93c710b14ba3877072e7fe07ec9d7cee3fdf67e14beab3e1b6f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7073616c6d2d6c6576656c5f312d626c75652e737667)](https://github.com/rasuvaeff/domain-monitor/actions/workflows/static-analysis.yml)[![PHP](https://camo.githubusercontent.com/8b85e7c6d325097d9795699bfefd80b3e3ae6101e45b60c7484760ba5e9dfa0f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f7261737576616566662f646f6d61696e2d6d6f6e69746f722f706870)](https://packagist.org/packages/rasuvaeff/domain-monitor)[![License](https://camo.githubusercontent.com/6cb285b57819f8de0acfb34923298f4f569f962544e8fe35331da2d163f4e485/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4253442d2d332d2d436c617573652d626c75652e737667)](LICENSE.md)

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, or async runners. The package provides building blocks and a `DomainMonitor` orchestrator; your application provides the workflow.

> Using an AI coding assistant? [llms.txt](llms.txt) contains a compact API reference.

Requirements
------------

[](#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` (pulls `ext-curl`, `ext-mbstring`)
- `ext-intl` is optional (IDN normalization only)
- `ext-sockets` is optional (DNS resolution only)

Installation
------------

[](#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
--------------------------------

[](#quick-start-a-full-domain-check)

### Simplest: the factory

[](#simplest-the-factory)

`DomainMonitor::create()` wires every check from a single PSR-18 client + PSR-17 factory (WHOIS optional):

```
use Iodev\Whois\Factory;
use Nyholm\Psr7\Factory\Psr17Factory;
use Rasuvaeff\DomainMonitor\DomainMonitor;
use Symfony\Component\HttpClient\Psr18Client;

$monitor = DomainMonitor::create(
    httpClient: new Psr18Client(),
    requestFactory: new Psr17Factory(),
    whois: Factory::get()->createWhois(), // omit to disable the WHOIS check
);

$report = $monitor->check(host: 'example.com');

echo $report->getStatus()->value; // 'ok' | 'warning' | 'critical' | 'unknown'
```

For granular control over which checks run, use `DomainMonitorBuilder`:

```
use Rasuvaeff\DomainMonitor\DomainMonitorBuilder;

$monitor = DomainMonitorBuilder::create()
    ->withHttp(client: new Psr18Client(), requestFactory: new Psr17Factory())
    ->withWhois(Factory::get()->createWhois())
    ->withoutPort()
    ->build();
```

### Using the orchestrator (recommended)

[](#using-the-orchestrator-recommended)

```
use Iodev\Whois\Factory;
use Nyholm\Psr7\Factory\Psr17Factory;
use Rasuvaeff\DomainMonitor\{
    DnsService,
    DomainMonitor,
    DomainMonitorOptions,
    HttpContentCheckService,
    HttpProbeService,
    PortService,
    RobotsTxtService,
    SecurityHeadersService,
    SitemapService,
    SslCertificateService,
    WhoisService,
};
use Symfony\Component\HttpClient\Psr18Client;

$client = new Psr18Client();
$requestFactory = new Psr17Factory();

$monitor = new DomainMonitor(
    httpProbe: new HttpProbeService(httpClient: $client, requestFactory: $requestFactory),
    ssl: new SslCertificateService(),
    whois: new WhoisService(whois: Factory::get()->createWhois()),
    dns: new DnsService(),
    port: new PortService(),
    securityHeaders: new SecurityHeadersService(),
    robotsTxt: new RobotsTxtService(httpClient: $client, requestFactory: $requestFactory),
    sitemap: new SitemapService(httpClient: $client, requestFactory: $requestFactory),
    content: new HttpContentCheckService(httpClient: $client, requestFactory: $requestFactory),
);

$report = $monitor->check(
    host: 'example.com',
    options: new DomainMonitorOptions(timeoutSeconds: 10.0),
);

echo $report->getStatus()->value; // 'ok' | 'warning' | 'critical' | 'unknown'
```

Services are optional — pass `null` (or omit) to disable a check. The orchestrator reuses a single HTTP response for probe + security headers + content check. Failed checks are caught, logged via PSR-3, and omitted from the report.

### Manual composition

[](#manual-composition)

```
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;
```

Reading the report
------------------

[](#reading-the-report)

`getStatus()` is the aggregate (worst of all checks). For the *why*, iterate per-check results — each carries a `CheckName`, a `CheckStatus`, and a human-readable `reason`:

```
foreach ($report->getChecks() as $result) {
    printf("%-16s %-8s %s\n", $result->check->value, $result->status->value, $result->reason);
}
// probe            ok       HTTP 200
// ssl              ok       Certificate valid, expires in 61 day(s)
// whois            warning  Domain expires in 12 day(s)

$ssl = $report->getCheck(name: CheckName::Ssl); // ?CheckResult
```

### Errors vs disabled checks

[](#errors-vs-disabled-checks)

A check that was **not configured** is `null`. A check that **ran but threw** is recorded separately — it appears in `getChecks()` as `UNKNOWN` (never inflating the aggregate) and in `getErrors()`:

```
if ($report->hasErrors()) {
    foreach ($report->getErrors() as $error) {
        // CheckError { check: CheckName, message: string }
        echo "{$error->check->value}: {$error->message}\n";
    }
}
```

Treat `getStatus() === CheckStatus::OK` together with `hasErrors() === true` as "OK but incomplete".

### Thresholds

[](#thresholds)

By default SSL is `CRITICAL` only once expired, and WHOIS warns within 30 days. Opt in to "SSL expiring soon = warning" (and tune the WHOIS window) with `ReportThresholds`:

```
use Rasuvaeff\DomainMonitor\DomainMonitorOptions;
use Rasuvaeff\DomainMonitor\ReportThresholds;

$report = $monitor->check(
    host: 'example.com',
    options: new DomainMonitorOptions(
        thresholds: ReportThresholds::strict(), // SSL warns 14 days before expiry
        // or: new ReportThresholds(sslWarnDays: 30, whoisWarnDays: 45)
    ),
);
```

`ReportThresholds::default()` reproduces pre-1.2.0 behaviour exactly.

### Serialization

[](#serialization)

Every result DTO implements `JsonSerializable`, so the whole report encodes in one call — dates as ISO-8601, enums as their values, disabled checks as `null`:

```
$json = json_encode($report, JSON_THROW_ON_ERROR);
```

The `checks` array is the evaluated snapshot (frozen `reason` strings); nested raw DTOs (`ssl.validUntil`, `whois.expirationDate`) stay absolute so a stored blob is a faithful record.

Services
--------

[](#services)

### HTTP probing

[](#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

[](#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

[](#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

[](#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)

[](#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

[](#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

[](#robotstxt)

```
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

[](#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

[](#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

[](#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
------------------

[](#full-api-reference)

ClassWhat it does`DomainMonitor`Orchestrator: runs all configured services, reuses HTTP response for probe + security headers + content → `DomainHealthReport`; `create()` factory + implements `DomainMonitorInterface``DomainMonitorInterface`Contract for `DomainMonitor` — mock/decorate it`DomainMonitorBuilder`Fluent, granular composition of the orchestrator (`withHttp`, `withWhois`, `withoutPort`, …)`DomainMonitorOptions`VO for orchestrator: port, timeout, method, userAgent, expectedOrg, expectedStatus, requiredText, forbiddenText, thresholds`ReportThresholds`VO: SSL expiry-warning window (`sslWarnDays`) + WHOIS warning window (`whoisWarnDays`); `default()` / `strict()``HostNormalizer`Normalize hosts/URLs (lowercase, strip scheme/port/path, optional IDN)`HttpProbeService`PSR-18 GET/HEAD probe with measured time → `ProbeResult`; `probeWithResponse()` for response reuse`HttpProbeWithResponse`DTO: `ProbeResult` + `ResponseInterface` (for response reuse)`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 &amp; 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 `` entries → `SitemapCheck``SitemapCheck`DTO: `exists`, `httpStatus`, `urlCount``HttpContentCheckService`Status code + required/forbidden keyword check → `HttpContentCheck`; `checkFromResponse()` for response reuse`HttpContentCheck`DTO: `status`, `httpStatus`, `?finalUrl`, text flags`DomainHealthReport`Composite DTO for all check results; `getStatus()` aggregate, `getChecks()`/`getCheck()` per-check, `getErrors()`/`hasErrors()`, `JsonSerializable``CheckResult`DTO: `check` (`CheckName`), `status` (`CheckStatus`), `reason` (human-readable)`CheckError`DTO: `check` (`CheckName`), `message` — a check that ran but threw`CheckName`Enum: `Probe`, `Ssl`, `Whois`, `Dns`, `Content`, `Port`, `SecurityHeaders`, `RobotsTxt`, `Sitemap``CheckStatus`Enum: `OK`, `WARNING`, `CRITICAL`, `UNKNOWN`Security
--------

[](#security)

- HTTP checks accept only `http` and `https` URLs.
- Host inputs are normalized and validated before use.
- `SslCertificateService` reads 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
--------

[](#examples)

See [examples/](examples/) for runnable scripts.

ScriptShowsNetwork?`full-check.php`Full domain check via `DomainMonitor` orchestratorYes`http-probe.php`HTTP probe + content checkYes`ssl-whois-dns.php`SSL, WHOIS, and DNSYes`port.php`TCP port check with custom host/portYes`security-headers.php`Check security headers on a live URLYes`robots.php`Fetch `/robots.txt` and extract sitemapsYes`sitemap.php`Fetch sitemap and count URLsYes`report.php`Build a `DomainHealthReport` from DTOsNoRun examples:

```
php examples/port.php example.com 443
php examples/security-headers.php https://example.com
```

Development
-----------

[](#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
```

License
-------

[](#license)

[BSD-3-Clause](LICENSE.md)

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance96

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~8 days

Total

5

Last Release

19d ago

PHP version history (2 changes)v1.0.0PHP ^8.3

v1.1.0PHP 8.3 - 8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/b0812d5572a7041dfe36e222d295b2e6dc55833a605350fcde58a51a5965ed30?d=identicon)[rasuvaeff](/maintainers/rasuvaeff)

---

Top Contributors

[![rasuvaeff](https://avatars.githubusercontent.com/u/1352718?v=4)](https://github.com/rasuvaeff "rasuvaeff (19 commits)")

---

Tags

dnsmonitoringphpportssitemapsslwhoismonitoringpsr-18dnsssldomainwhois

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/rasuvaeff-domain-monitor/health.svg)

```
[![Health](https://phpackages.com/badges/rasuvaeff-domain-monitor/health.svg)](https://phpackages.com/packages/rasuvaeff-domain-monitor)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k16](/packages/tempest-framework)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[cakephp/cakephp

The CakePHP framework

8.8k19.5M1.8k](/packages/cakephp-cakephp)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

36789.4k2](/packages/telnyx-telnyx-php)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.2k](/packages/typo3-cms-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
