Modern PHP 8.2+ EPP client library for the .UA registry (epp.hostmaster.ua).

Maintainers

Package info

github.com/hostmaster-direct/ua

pkg:composer/hostmaster/ua

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-04-29 18:36 UTC

This package is auto-updated.

Last update: 2026-04-29 18:45:56 UTC


README

Modern PHP 8.2+ EPP client library for the Ukrainian (.UA) domain registry, epp.hostmaster.ua.

  • Strict types, readonly DTOs, typed enums, full PSR-4 / Composer
  • Native RFC 5730/5734 EPP-over-TLS transport (4-byte length-prefixed frames)
  • Hostmaster.ua dialect: contact-1.1, domain-1.1, host-1.1, uaepp-1.1 (license, force-host-delete), rgp-1.1, balance-1.0
  • Per-result-code exception hierarchy — handle ObjectExistsException, BillingException, AuthorizationException, etc. directly
  • PSR-3 logging hook for wire-level debugging

Install

composer require hostmaster/ua

Requires PHP 8.2+ with ext-dom, ext-libxml, ext-openssl, ext-mbstring.

Quick start

use Hostmaster\Ua\Client;
use Hostmaster\Ua\Config;
use Hostmaster\Ua\Enum\ContactType;
use Hostmaster\Ua\Enum\PostalType;
use Hostmaster\Ua\Model\Address;
use Hostmaster\Ua\Model\AuthInfo;
use Hostmaster\Ua\Model\ContactCreate;
use Hostmaster\Ua\Model\DomainContact;
use Hostmaster\Ua\Model\DomainCreate;
use Hostmaster\Ua\Model\Nameserver;
use Hostmaster\Ua\Model\Period;
use Hostmaster\Ua\Model\PostalInfo;

$client = new Client(new Config(
    username: 'MYREG',
    password: 'super-secret',
    // host: 'epp-test.hostmaster.ua', // OT&E
    // localCert: '/etc/ssl/registrar.pem',
    // localPk:   '/etc/ssl/registrar.key',
));

$client->login();

$results = $client->domain->check(['example.ua', 'example2.ua']);
foreach ($results as $r) {
    printf("%s -> %s%s\n", $r->name, $r->available ? 'free' : 'taken',
        $r->reason ? " ({$r->reason})" : '');
}

$client->contact->create(new ContactCreate(
    id: 'CT-EXAMPLE',
    postalInfo: [
        new PostalInfo(
            type: PostalType::International,
            name: 'John Doe',
            address: new Address(
                street: ['10 Khreshchatyk St'],
                city: 'Kyiv',
                countryCode: 'UA',
                postalCode: '01001',
            ),
        ),
    ],
    email: 'john@example.com',
    authInfo: new AuthInfo('contact-secret'),
));

$client->domain->create(new DomainCreate(
    name: 'example.ua',
    registrant: 'CT-EXAMPLE',
    period: new Period(1),
    nameservers: [
        Nameserver::reference('ns1.example.com'),
        Nameserver::reference('ns2.example.com'),
    ],
    contacts: [
        new DomainContact('CT-EXAMPLE', ContactType::Admin),
        new DomainContact('CT-EXAMPLE', ContactType::Tech),
    ],
    authInfo: new AuthInfo('domain-secret'),
    license: 'A0000000', // optional uaepp:license extension
));

$client->logout();

Architecture

Hostmaster\Ua
├── Client                  Facade — connects services with a Session
├── Config                  Readonly connection / login config
├── Session                 Owns a Connection; runs login/logout/dispatch
├── Connection
│   ├── Connection          Transport interface
│   └── SocketConnection    TLS stream socket, length-prefixed frames
├── Protocol
│   ├── Namespaces          XML namespace constants
│   ├── XmlBuilder          Build EPP request frames (DOM-based)
│   ├── XmlParser           Parse server <response> envelopes
│   ├── EppResponse         Successful response with raw DOM + xpath
│   ├── ResultCode          enum: every EPP 1xxx/2xxx code
│   ├── Hydrator            DOM → DTO conversion
│   └── TridGenerator       clTRID strategy (default = random)
├── Service
│   ├── DomainService       check/info/create/update/delete/renew/transfer + RGP restore
│   ├── ContactService     check/info/create/update/delete/transfer*
│   ├── HostService         check/info/create/update/delete (+ uaepp force delete)
│   ├── PollService         poll request/ack
│   └── BalanceService      balance:info
├── Notification
│   └── EmailNotificationParser  Parses notify@epp.hostmaster.ua emails
├── Logging
│   ├── Redactor            Stateless secret stripping for any string
│   └── RedactingLogger     PSR-3 decorator that redacts on the way through
├── Model                   Readonly DTOs (DomainInfo, ContactInfo, EmailNotification, …)
├── Enum                    PostalType, ContactType, IpVersion, TransferOp, EmailTemplate, …
└── Exception               Hierarchy keyed on EPP result codes

Exception handling

Every 2xxx response is thrown as a typed exception. Catch the specific class you care about, fall back to EppException for everything else, and HostmasterException for any failure originating in this library:

use Hostmaster\Ua\Exception\AuthenticationException;
use Hostmaster\Ua\Exception\BillingException;
use Hostmaster\Ua\Exception\ObjectExistsException;
use Hostmaster\Ua\Exception\ObjectNotFoundException;
use Hostmaster\Ua\Exception\EppException;
use Hostmaster\Ua\Exception\ConnectionException;

try {
    $client->login();
    $client->domain->create($req);
} catch (AuthenticationException $e) {
    // 2200/2501 — bad credentials
} catch (ObjectExistsException $e) {
    // 2302 — domain already registered
} catch (BillingException $e) {
    // 2104 — registrar balance is negative
} catch (EppException $e) {
    // any other 2xxx — inspect $e->resultCode, $e->reasons, $e->svTRID
} catch (ConnectionException $e) {
    // network/TLS failure
}

The exception classes correspond to result-code groups defined in src/Protocol/ResultCode.php and mapped in src/Exception/EppException.php.

Logging & secret redaction

Pass any PSR-3 LoggerInterface as the second argument to new Client(...). Wire frames are logged at debug level. Sensitive fields (<pw>, <newPW>, <domain:pw>, <contact:pw>, <authInfo>, <ext>) are redacted by default before they ever reach the logger — your handler sees <pw>*****</pw>, while your application code still receives the real password as a string when you build a request or read a response.

$client = new Client($config, new \Monolog\Logger('epp'));

Standalone redaction (no logger needed)

Redactor is framework-agnostic — call it from anywhere you want to store or display EPP XML safely (DB columns, support tickets, audit trails):

use Hostmaster\Ua\Logging\Redactor;

$db->insert('epp_audit', [
    'trid' => $svTRID,
    'request' => Redactor::redact($requestXml),
    'response' => Redactor::redact($responseXml),
]);

Add custom rules for your own extension fields:

$customPatterns = array_merge(Redactor::defaultPatterns(), [
    '/(<myext:secret>)([^<]*)(<\/myext:secret>)/u',
]);
$safe = Redactor::withPatterns($xml, $customPatterns);

PSR-3 wrapper

Wrap any PSR-3 logger so the rest of your code doesn't have to think about redaction:

use Hostmaster\Ua\Logging\RedactingLogger;

$logger = new RedactingLogger(new \Monolog\Logger('epp'));
$client = new Client($config, $logger);
$logger->info('paying invoice', ['xml' => $rawXml]); // 'xml' value is sanitised on its way out

The wrapper redacts the message string and every string-valued context field, recursively. Non-string context values pass through untouched.

Polling messages

while (($msg = $client->poll->request()) !== null) {
    echo "[{$msg->id}] {$msg->message}\n";
    // $msg->response gives access to <resData> / <extension> for typed parsing
    $client->poll->acknowledge($msg->id);
}

Email notifications

The registry mirrors poll messages over email (From: notify@epp.hostmaster.ua, X-Notifier: UAEPP-NOTIFY, X-Template: …). EmailNotificationParser converts a raw RFC 5322 message — or already-split headers + body — into an EmailNotification value object with the operation, object type, name, and ROID extracted from the subject:

use Hostmaster\Ua\Notification\EmailNotificationParser;
use Hostmaster\Ua\Enum\EmailTemplate;

$notification = EmailNotificationParser::parseRaw($rawEmail);

if ($notification->template === EmailTemplate::ObjectNew
    && $notification->objectType === 'DOMAIN') {
    $repo->markCreated($notification->objectName, $notification->roid);
}

// Or when your MTA already gave you an array of headers (e.g. from a webhook):
$n = EmailNotificationParser::fromHeaders($headersArray, $body);

// Sanity check before parsing:
if (EmailNotificationParser::isRegistryNotification($headersArray)) {
    // … process …
}

EmailTemplate covers every documented X-Template value (object created/changed/deleted, transfer requested/completed/cancelled/rejected, RGP, pending delete, host unlinked, contract events, balance reports, DNSSEC checks, etc.). Unknown values fall back to EmailTemplate::Unknown so the parser stays forward-compatible.

Hostmaster.ua extensions

  • uaepp:license — passed via DomainCreate::$license / DomainUpdate::$license
  • uaepp:deleteNS confirm="yes"HostService::delete($name, force: true)
  • rgp:restore op="request"DomainService::restoreRequest($name)

Examples

The examples/ directory contains a runnable script for every EPP command, mirroring the structure of the official Hostmaster.ua documentation. See examples/README.md for a one-to-one mapping of registry doc pages to library calls.

export UAEPP_USER=my-registrar
export UAEPP_PASS=secret
php examples/session.php
php examples/domain.php
php examples/contact.php
php examples/host.php
php examples/poll.php
php examples/balance.php
php examples/notifications-email.php
php examples/logging-redaction.php
php examples/error-handling.php
php examples/end-to-end.php

Development

composer install
composer test
composer phpstan
composer cs-fix

License

MIT — see LICENSE.