diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..bc5ba57 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## What does this PR do? + + + +## Type of change + +- [ ] Feature (`feature/*` → `develop`) +- [ ] Bug fix (`fix/*` → `develop`) +- [ ] Hotfix (`hotfix/*` → `main` + `develop`) +- [ ] Release (`release/*` → `main` + `develop`) + +## Test plan + +- [ ] `./vendor/bin/pest` — all tests pass +- [ ] `./vendor/bin/pint --test` — no style violations +- [ ] Manually tested the affected flows + +## Notes + + diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..e6e64d4 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,34 @@ +name: Auto Label + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Apply label based on branch name + uses: actions/github-script@v7 + with: + script: | + const branch = context.payload.pull_request.head.ref; + const labels = []; + + if (branch.startsWith('feature/')) labels.push('feature'); + else if (branch.startsWith('fix/')) labels.push('bug'); + else if (branch.startsWith('hotfix/')) labels.push('hotfix'); + else if (branch.startsWith('release/')) labels.push('release'); + + if (labels.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels, + }); + } diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..d33c501 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,28 @@ +name: Static Analysis + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + phpstan: + runs-on: ubuntu-latest + name: PHPStan + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Run PHPStan + run: ./vendor/bin/phpstan analyse --no-progress diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml new file mode 100644 index 0000000..9252f27 --- /dev/null +++ b/.github/workflows/pint.yml @@ -0,0 +1,28 @@ +name: Code Style + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + pint: + runs-on: ubuntu-latest + name: Laravel Pint + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Check code style + run: ./vendor/bin/pint --test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e13085d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,35 @@ +name: Tests + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: ['8.2', '8.3'] + + name: PHP ${{ matrix.php }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, json, libxml, simplexml + coverage: none + + - name: Install dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Run tests + run: ./vendor/bin/pest diff --git a/.gitignore b/.gitignore index 3eb6429..96947ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .php_cs.cache .idea/ /composer.lock +CLAUDE.md +OVERVIEW.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2699e1e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing + +This project follows the **Gitflow** branching model. + +## Branch structure + +| Branch | Purpose | +|--------|---------| +| `main` | Production-ready code. Only receives merges from `release/*` and `hotfix/*`. | +| `develop` | Integration branch. All features land here first. | +| `feature/*` | New features, branched from and merged back into `develop`. | +| `release/*` | Release preparation, branched from `develop`, merged into both `main` and `develop`. | +| `hotfix/*` | Urgent production fixes, branched from `main`, merged into both `main` and `develop`. | + +## Day-to-day workflow + +### Starting a feature + +```bash +git checkout develop +git pull origin develop +git checkout -b feature/my-feature +``` + +Work, commit, push, then open a PR targeting `develop`. + +### Starting a release + +```bash +git checkout develop +git pull origin develop +git checkout -b release/1.2.0 +# bump version, update changelog, final fixes +git push origin release/1.2.0 +``` + +Open a PR targeting `main`. After merge, also merge into `develop` and tag the release: + +```bash +git checkout main && git pull origin main +git tag -a v1.2.0 -m "Release 1.2.0" +git push origin v1.2.0 +``` + +### Fixing a production bug (hotfix) + +```bash +git checkout main +git pull origin main +git checkout -b hotfix/fix-critical-bug +# fix, commit +git push origin hotfix/fix-critical-bug +``` + +Open two PRs: one targeting `main`, one targeting `develop`. + +## Branch naming + +| Type | Pattern | Example | +|------|---------|---------| +| Feature | `feature/` | `feature/domain-transfer` | +| Bug fix | `fix/` | `fix/response-code-cast` | +| Release | `release/` | `release/1.2.0` | +| Hotfix | `hotfix/` | `hotfix/sidn-namespace-url` | + +## Rules + +- `main` and `develop` are protected — direct pushes are blocked. +- Every change requires a PR with at least **1 approval**. +- Stale approvals are dismissed when new commits are pushed. +- Run `./vendor/bin/pest` and `./vendor/bin/pint` before opening a PR. diff --git a/README.md b/README.md index 7c0d0cf..655e10a 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,125 @@ SIDN_PASSWORD=superpass123! SIDN_HOSTNAME=drs.domain-registry.nl ``` -Start using Laravel EPP ! \ No newline at end of file +Start using Laravel EPP ! + +## Requirements + +- PHP 8.1+ +- Laravel 10 or 11 + +## Usage + +### Starting a session + +```php +use YWatchman\LaravelEPP\Epp; + +$epp = new Epp('sidn'); +$epp->start(); +$epp->login(); + +// ... perform commands ... + +$epp->logout(); +``` + +### Domain commands + +**Check availability** +```php +use YWatchman\LaravelEPP\Models\Domain; +use YWatchman\LaravelEPP\Support\Xml\Commands\Domain\CheckCommand; +use YWatchman\LaravelEPP\Responses\Domain\CheckResponse; + +$domains = [new Domain(['name' => 'example.nl'])]; +$command = new CheckCommand($domains); +$response = new CheckResponse($epp->sendRequest((string) $command)); + +if ($response->isSucceeded()) { + // $response->domainExists('example.nl') +} +``` + +**Create a domain** +```php +use YWatchman\LaravelEPP\Support\Xml\Commands\Domain\CreateCommand; +use YWatchman\LaravelEPP\Responses\Domain\CreateResponse; + +$command = new CreateCommand($domain, $contacts, $nameservers); +$response = new CreateResponse($epp->sendRequest((string) $command)); +``` + +**Transfer a domain** +```php +use YWatchman\LaravelEPP\Support\Xml\Commands\Domain\TransferCommand; +use YWatchman\LaravelEPP\Responses\Domain\TransferResponse; + +$command = new TransferCommand($domain, $authToken); +$response = new TransferResponse($epp->sendRequest((string) $command)); +``` + +### Contact commands + +**Create a contact** +```php +use YWatchman\LaravelEPP\Models\Contact; +use YWatchman\LaravelEPP\Support\Xml\Commands\Contact\CreateCommand; +use YWatchman\LaravelEPP\Responses\Contact\CreateResponse; + +$contact = new Contact([ + 'handle' => 'MYHANDLE001', + 'name' => 'Jane Doe', + 'street' => 'Main Street', + 'number' => '1', + 'city' => 'Amsterdam', + 'postal' => '1000AA', + 'country' => 'NL', + 'phone' => '+31.201234567', + 'email' => 'jane@example.nl', + 'legalForm' => 'PERSON', // PERSON, OTHER, or SIDN Dutch codes directly +]); + +$command = new CreateCommand($contact); +$response = new CreateResponse($epp->sendRequest((string) $command)); +``` + +### Host commands + +**Create a nameserver** +```php +use YWatchman\LaravelEPP\Models\Nameserver; +use YWatchman\LaravelEPP\Support\Xml\Commands\Host\CreateCommand; +use YWatchman\LaravelEPP\Responses\Host\CreateResponse; + +$nameserver = new Nameserver(['name' => 'ns1.example.nl', 'address' => '1.2.3.4']); +$command = new CreateCommand($nameserver); +$response = new CreateResponse($epp->sendRequest((string) $command)); +``` + +### Checking a response + +All response classes extend `Response` and share a common interface: + +```php +$response->isSucceeded(); // bool +$response->getCode(); // int (e.g. 1000, 2303) +$response->getMessage(); // ?string +$response->getServerTransaction(); // string +$response->getClientTransaction(); // ?string +``` + +## SIDN-specific notes + +- Legal form values `PERSON` and `OTHER` are automatically mapped to the Dutch SIDN codes `PERSOON` and `ANDERS`. All other values are passed through as-is. +- The SIDN EPP extension namespace is `https://rxsd.domain-registry.nl/sidn-ext-epp-1.0`. + +## Debugging + +Set `EPP_DEBUG=true` in your `.env` to dump raw XML frames to stdout during socket I/O. + +## Running tests + +```bash +./vendor/bin/pest +``` \ No newline at end of file diff --git a/composer.json b/composer.json index b9eb743..8b68127 100644 --- a/composer.json +++ b/composer.json @@ -39,21 +39,25 @@ "ext-simplexml": "*", "php": "^8.2", "illuminate/support": "^11.40", - "illuminate/database": "^11.40", - "guzzlehttp/guzzle": "^7.9", - "jeremykendall/php-domain-parser": "^6.3", - "symfony/dom-crawler": "^7.2", - "symfony/css-selector": "^7.2" + "jeremykendall/php-domain-parser": "^6.4", + "symfony/dom-crawler": "^7.4.8", + "symfony/css-selector": "^7.4.9" }, "type": "library", "require-dev": { - "laravel/pint": "^1.20", - "pestphp/pest": "^3.7", - "orchestra/testbench": "^9.9" + "laravel/pint": "^1.29.1", + "pestphp/pest": "^3.8.6", + "orchestra/testbench": "^9.17", + "phpstan/phpstan": "^2.1" }, "config": { "allow-plugins": { "pestphp/pest-plugin": true } + }, + "scripts": { + "post-install-cmd": "@fix:pdo-constant", + "post-update-cmd": "@fix:pdo-constant", + "fix:pdo-constant": "@php scripts/fix-pdo-constant.php" } } diff --git a/config/epp.php b/config/epp.php index dc92ee9..0d7ccd6 100644 --- a/config/epp.php +++ b/config/epp.php @@ -10,8 +10,8 @@ 'username' => env('SIDN_USERNAME'), 'password' => env('SIDN_PASSWORD'), 'hostname' => env('SIDN_HOSTNAME'), - 'port' => env('SIDN_PORT', 700), - 'timeout' => env('SIDN_TIMEOUT', 30), + 'port' => env('SIDN_PORT', 700), + 'timeout' => env('SIDN_TIMEOUT', 30), ], ], 'debug' => env('EPP_DEBUG', false), diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..f3a04b5 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,277 @@ +parameters: + ignoreErrors: + - + message: '#^If condition is always true\.$#' + identifier: if.alwaysTrue + count: 2 + path: src/Epp.php + + - + message: '#^Unreachable statement \- code above always terminates\.$#' + identifier: deadCode.unreachable + count: 1 + path: src/Epp.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Nameserver\:\:\$address\.$#' + identifier: property.notFound + count: 2 + path: src/Models/Nameserver.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Nameserver\:\:\$name\.$#' + identifier: property.notFound + count: 1 + path: src/Models/Nameserver.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$name\.$#' + identifier: property.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$sld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$tld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Call to method getRules\(\) on an unknown class Pdp\\Manager\.$#' + identifier: class.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Instantiated class Pdp\\Cache not found\.$#' + identifier: class.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Instantiated class Pdp\\CurlHttpClient not found\.$#' + identifier: class.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Instantiated class Pdp\\Manager not found\.$#' + identifier: class.notFound + count: 1 + path: src/Support/DomainParser.php + + - + message: '#^Trait YWatchman\\LaravelEPP\\Support\\Traits\\Commands\\HasExtensions is used zero times and is not analysed\.$#' + identifier: trait.unused + count: 1 + path: src/Support/Traits/Commands/HasExtensions.php + + - + message: '#^Access to undefined constant static\(YWatchman\\LaravelEPP\\Support\\Xml\\Commands\\Command\)\:\:NODE_BASE\.$#' + identifier: classConstant.notFound + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Call to an undefined method DOMNode\:\:setAttribute\(\)\.$#' + identifier: method.notFound + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Parameter \#2 \$callback of function array_walk expects callable\(mixed, int\|string, DOMElement\)\: mixed, array\{''YWatchman\\\\LaravelEPP\\\\Support\\\\Xml\\\\Commands\\\\Command'', ''recurseCallback''\} given\.$#' + identifier: argument.type + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Strict comparison using \!\=\= between null and null will always evaluate to false\.$#' + identifier: notIdentical.alwaysFalse + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Undefined variable\: \$attrs$#' + identifier: variable.undefined + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Variable \$attrs in isset\(\) is never defined\.$#' + identifier: isset.variable + count: 1 + path: src/Support/Xml/Commands/Command.php + + - + message: '#^Property YWatchman\\LaravelEPP\\Support\\Xml\\Commands\\Contact\\CreateCommand\:\:\$contact \(YWatchman\\LaravelEPP\\Models\\Contact\) does not accept YWatchman\\LaravelEPP\\Contracts\\IsContact\.$#' + identifier: assign.propertyType + count: 1 + path: src/Support/Xml/Commands/Contact/CreateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Contact\:\:\$handle\.$#' + identifier: property.notFound + count: 3 + path: src/Support/Xml/Commands/Domain/CreateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$sld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/CreateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$tld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/CreateCommand.php + + - + message: '#^Parameter \#2 \$value of method YWatchman\\LaravelEPP\\Support\\Xml\\XmlHelper\:\:createElement\(\) expects string\|null, int given\.$#' + identifier: argument.type + count: 4 + path: src/Support/Xml/Commands/Domain/CreateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$sld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/TransferCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$tld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/TransferCommand.php + + - + message: '#^Parameter \#2 \$value of method YWatchman\\LaravelEPP\\Support\\Xml\\XmlHelper\:\:createElement\(\) expects string\|null, int given\.$#' + identifier: argument.type + count: 4 + path: src/Support/Xml/Commands/Domain/TransferCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$sld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Models\\Domain\:\:\$tld\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Support\\Xml\\Commands\\Domain\\UpdateCommand\:\:\$admin\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Support\\Xml\\Commands\\Domain\\UpdateCommand\:\:\$registrant\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Support\\Xml\\Commands\\Domain\\UpdateCommand\:\:\$tech\.$#' + identifier: property.notFound + count: 1 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^Parameter \#2 \$value of method YWatchman\\LaravelEPP\\Support\\Xml\\XmlHelper\:\:createElement\(\) expects string\|null, int given\.$#' + identifier: argument.type + count: 3 + path: src/Support/Xml/Commands/Domain/UpdateCommand.php + + - + message: '#^PHPDoc tag @param has invalid value \(Nameserver\)\: Unexpected token "\\n \*", expected variable at offset 69 on line 4$#' + identifier: phpDoc.parseError + count: 1 + path: src/Support/Xml/Commands/Host/CreateCommand.php + + - + message: '#^Call to an undefined static method YWatchman\\LaravelEPP\\Exceptions\\EppException\:\:notImplemented\(\)\.$#' + identifier: staticMethod.notFound + count: 1 + path: src/Support/Xml/Objects/Host/AuthObject.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$city\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$country\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$email\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$fax\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$handle\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$name\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$number\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$phone\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$postal\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$state\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$street\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php + + - + message: '#^Access to an undefined property YWatchman\\LaravelEPP\\Contracts\\Transformable\:\:\$suffix\.$#' + identifier: property.notFound + count: 1 + path: src/Transformers/ContactTransformer.php diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..be3f3fc --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - phpstan-baseline.neon + +parameters: + paths: + - src + level: 5 + excludePaths: [] diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..df36124 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests/Unit + + + + + src + + + diff --git a/scripts/fix-pdo-constant.php b/scripts/fix-pdo-constant.php new file mode 100644 index 0000000..777dd64 --- /dev/null +++ b/scripts/fix-pdo-constant.php @@ -0,0 +1,22 @@ += 80500 ? Pdo\\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA)', + file_get_contents($file), + )); +} diff --git a/src/Contracts/IsContact.php b/src/Contracts/IsContact.php index 32d4ecc..258d1c4 100644 --- a/src/Contracts/IsContact.php +++ b/src/Contracts/IsContact.php @@ -6,8 +6,6 @@ interface IsContact { /** * Fields containing data such as legalForm. - * - * @return array */ public function fields(): array; } diff --git a/src/Contracts/Transformable.php b/src/Contracts/Transformable.php index e71d123..f1b858e 100644 --- a/src/Contracts/Transformable.php +++ b/src/Contracts/Transformable.php @@ -2,6 +2,4 @@ namespace YWatchman\LaravelEPP\Contracts; -interface Transformable -{ -} +interface Transformable {} diff --git a/src/Epp.php b/src/Epp.php index 49ef7af..ca327df 100644 --- a/src/Epp.php +++ b/src/Epp.php @@ -2,7 +2,7 @@ namespace YWatchman\LaravelEPP; -use Exception; +use Illuminate\Support\Facades\Log; use YWatchman\LaravelEPP\Exceptions\EppException; use YWatchman\LaravelEPP\Support\Xml\Commands\Session\HelloCommand; use YWatchman\LaravelEPP\Support\Xml\Commands\Session\LoginCommand; @@ -13,33 +13,23 @@ class Epp /** @var resource */ protected $socket; - /** @var bool */ - protected $loggedIn = false; + protected bool $loggedIn = false; - /** - * @var string|null - */ - protected $helloMsg; + protected ?string $helloMsg; - /** @var string */ - private $registrar; + private string $registrar; - /** @var string */ - private $username; + private string $username; - /** @var string */ - private $password; + private string $password; - /** @var string */ - private $hostname; + private string $hostname; - /** @var int */ - private $port; + private int $port; /** * Epp constructor. * - * @param string $registrar * * @throws EppException */ @@ -51,6 +41,8 @@ public function __construct(string $registrar = 'sidn') /** * Epp destruction... + * + * @throws EppException */ public function __destruct() { @@ -63,13 +55,13 @@ public function __destruct() /** * Initiate EPP session login. * - * @throws Exception + * @throws EppException */ - public function login() + public function login(): ?string { $this->start(); - $command = new HelloCommand(); + $command = new HelloCommand; $cmdString = (string) $command; $this->helloMsg = $this->sendRequest($cmdString); @@ -84,12 +76,14 @@ public function login() /** * @return string|void + * + * @throws EppException */ public function logout() { if ($this->loggedIn) { $this->loggedIn = false; - $cmd = (string) (new LogoutCommand()); + $cmd = (string) (new LogoutCommand); return $this->sendRequest($cmd); } @@ -99,10 +93,8 @@ public function logout() * Connect to EPP server. * * @throws EppException - * - * @return string|null */ - public function start() + public function start(): ?string { $ctx = stream_context_create(); @@ -115,7 +107,7 @@ public function start() $ctx ); - if (!$this->socket) { + if (! $this->socket) { throw EppException::serverClosedConnection($errno, $errstr); } @@ -125,65 +117,56 @@ public function start() /** * Read stream socket response. * - * @return string|null + * @throws EppException */ public function read(): ?string { - if ($this->socket !== false) { + if ($this->socket) { if (@feof($this->socket)) { - return new Exception('Server closed connection.'); + throw EppException::serverClosedConnection(0, 'Server closed connection.'); } $header = @fread($this->socket, 4); if (empty($header) && feof($this->socket)) { - return new Exception('Server closed connection.'); + throw EppException::serverClosedConnection(0, 'Server closed connection.'); } $length = unpack('N', $header)[1]; if ($length <= 4) { - return new Exception( - sprintf( - 'Got bad frame header, length of %d. Length should be higher than 5.', - $length - ) - ); + throw EppException::badFrame($length); } $data = fread($this->socket, ($length - 4)); if (config('epp.debug')) { - echo 'Read data.. parsing:'.PHP_EOL.PHP_EOL; $cmd = dom_import_simplexml(simplexml_load_string($data))->ownerDocument; $cmd->formatOutput = true; - echo $cmd->saveXML(); + Log::debug('EPP read: '.$cmd->saveXML()); } return $data; } - return false; + return null; } /** * Send EPP Request. * - * @param $xml * - * @return string|null + * @throws EppException */ - public function sendRequest($xml) + public function sendRequest($xml): ?string { $xml = trim(preg_replace('/\s\s+/', '', $xml)); - if ($this->socket !== false) { + if ($this->socket) { if (config('epp.debug', false)) { - echo PHP_EOL; - echo 'Writing command to socket...'.PHP_EOL; $cmd = dom_import_simplexml(simplexml_load_string($xml))->ownerDocument; $cmd->formatOutput = true; - echo $cmd->saveXML(); + Log::debug('EPP write: '.$cmd->saveXML()); } fwrite($this->socket, $this->getBigEndianLength($xml).$xml); } @@ -193,19 +176,12 @@ public function sendRequest($xml) /** * First four bits of a packet are the request length. - * - * @param $xml - * - * @return false|string */ - public function getBigEndianLength($xml) + public function getBigEndianLength($xml): false|string { return pack('N', strlen($xml) + 4); } - /** - * @return bool - */ public function isLoggedIn(): bool { return $this->loggedIn; @@ -216,7 +192,7 @@ public function isLoggedIn(): bool * * @throws EppException */ - private function setupRegistrar() + private function setupRegistrar(): void { $config = config(sprintf('epp.registrars.%s', $this->registrar)); if ($config === null) { @@ -224,11 +200,11 @@ private function setupRegistrar() } if ( - !isset($config['username'], $config['password'], $config['hostname'], $config['port']) - || !is_string($config['username']) - || !is_string($config['password']) - || !is_string($config['hostname']) - || !is_int($config['port']) + ! isset($config['username'], $config['password'], $config['hostname'], $config['port']) + || ! is_string($config['username']) + || ! is_string($config['password']) + || ! is_string($config['hostname']) + || ! is_int($config['port']) ) { throw EppException::missingCredentials($this->registrar); } diff --git a/src/Exceptions/EppException.php b/src/Exceptions/EppException.php index 483f035..8f8f640 100644 --- a/src/Exceptions/EppException.php +++ b/src/Exceptions/EppException.php @@ -7,11 +7,17 @@ class EppException extends Exception { public const SERVER_CLOSED_CONNECTION = 100; + public const AUTHENTICATION_FAILED = 101; + public const MISSING_REGISTRAR_CONFIG = 102; + public const MISSING_CREDENTIALS = 103; + public const INVALID_OPERATION = 104; + public const BAD_FRAME = 105; + public const CONTACT_TYPE_DOES_NOT_EXIST = 200; public const CONTACT_DOES_NOT_EXIST = 201; @@ -83,4 +89,12 @@ public static function authTypeDoesNotExist(string $type) self::AUTH_TYPE_DOES_NOT_EXIST ); } + + public static function badFrame(int $length): self + { + return new self( + sprintf('Got bad frame header, length of %d. Length should be higher than 5.', $length), + self::BAD_FRAME + ); + } } diff --git a/src/Exceptions/NotImplementedException.php b/src/Exceptions/NotImplementedException.php index b123b74..1644585 100644 --- a/src/Exceptions/NotImplementedException.php +++ b/src/Exceptions/NotImplementedException.php @@ -9,7 +9,7 @@ class NotImplementedException extends EppException public function __construct( string $message = 'Function is not implemented yet.', int $code = self::NOT_IMPLEMENTED, - Throwable $previous = null + ?Throwable $previous = null ) { parent::__construct($message, $code, $previous); } diff --git a/src/Facade.php b/src/Facade.php index 5fe86ea..7603539 100644 --- a/src/Facade.php +++ b/src/Facade.php @@ -4,7 +4,7 @@ class Facade extends \Illuminate\Support\Facades\Facade { - protected static function getFacadeAccessor() + protected static function getFacadeAccessor(): string { return 'Epp'; } diff --git a/src/Models/Contact.php b/src/Models/Contact.php index ca05dc1..9aeb00d 100644 --- a/src/Models/Contact.php +++ b/src/Models/Contact.php @@ -5,7 +5,7 @@ use YWatchman\LaravelEPP\Contracts\IsContact; use YWatchman\LaravelEPP\Contracts\Transformable; -class Contact extends Model implements Transformable, IsContact +class Contact extends Model implements IsContact, Transformable { protected $columns = [ 'street', @@ -25,14 +25,11 @@ class Contact extends Model implements Transformable, IsContact 'legalFormNo', ]; - /** - * @return array - */ public function fields(): array { $data = $this->attributes; - if (!empty($this->legalFormNo)) { + if (! empty($this->legalFormNo)) { $data['legalFormNo'] = $this->legalFormNo; } diff --git a/src/Models/Model.php b/src/Models/Model.php index 34160f5..259108a 100644 --- a/src/Models/Model.php +++ b/src/Models/Model.php @@ -12,8 +12,6 @@ abstract class Model /** * Model constructor. - * - * @param array $attributes */ public function __construct(array $attributes = []) { @@ -27,8 +25,6 @@ public function __construct(array $attributes = []) } /** - * @param string $name - * * @return mixed */ public function __get(string $name) @@ -40,10 +36,6 @@ public function __get(string $name) return $this->{$name}; } - /** - * @param string $name - * @param $value - */ public function __set(string $name, $value) { $this->attributes[$name] = $value; diff --git a/src/Models/Nameserver.php b/src/Models/Nameserver.php index d325324..e3c86d4 100644 --- a/src/Models/Nameserver.php +++ b/src/Models/Nameserver.php @@ -7,15 +7,14 @@ class Nameserver extends Model implements Transformable { public const VERSION_V4 = 'v4'; + public const VERSION_V6 = 'v6'; + protected $columns = [ 'name', 'address', // Should be given in format '127.0.0.1-v4' or '::1-v4' ]; - /** - * @return array - */ public function fields(): array { $data = $this->attributes; diff --git a/src/Responses/Contact/CheckResponse.php b/src/Responses/Contact/CheckResponse.php index 0d0d3b1..c03b96c 100644 --- a/src/Responses/Contact/CheckResponse.php +++ b/src/Responses/Contact/CheckResponse.php @@ -12,8 +12,6 @@ class CheckResponse extends Response /** * CheckResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -30,10 +28,6 @@ public function __construct(string $rawXml) /** * Check if contact exists. - * - * @param string $contact - * - * @return bool */ public function contactExists(string $contact): bool { @@ -42,19 +36,12 @@ public function contactExists(string $contact): bool /** * Inverse of CheckResponse::contactExists(). - * - * @param string $contact - * - * @return bool */ public function contactDoesNotExist(string $contact): bool { - return !$this->contactExists($contact); + return ! $this->contactExists($contact); } - /** - * @param $contact - */ private function addExistentContact(string $contact) { $this->existingContacts[] = $contact; diff --git a/src/Responses/Contact/CreateResponse.php b/src/Responses/Contact/CreateResponse.php index 96ffbc5..52a815f 100644 --- a/src/Responses/Contact/CreateResponse.php +++ b/src/Responses/Contact/CreateResponse.php @@ -14,8 +14,6 @@ class CreateResponse extends Response /** * CreateResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -30,20 +28,16 @@ public function __construct(string $rawXml) /** * Get creation date. - * - * @return string */ - public function getDate(): string + public function getDate(): ?string { return $this->date; } /** * Get domain name. - * - * @return string */ - public function getId(): string + public function getId(): ?string { return $this->id; } diff --git a/src/Responses/Domain/CheckResponse.php b/src/Responses/Domain/CheckResponse.php index 06422fb..f2847bd 100644 --- a/src/Responses/Domain/CheckResponse.php +++ b/src/Responses/Domain/CheckResponse.php @@ -15,8 +15,6 @@ class CheckResponse extends Response /** * CheckResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -33,17 +31,11 @@ public function __construct(string $rawXml) }); } - /** - * @return array - */ public function getAvailableDomains(): array { return $this->availableDomains; } - /** - * @return array - */ public function getOccupiedDomains(): array { return $this->occupiedDomains; @@ -51,8 +43,6 @@ public function getOccupiedDomains(): array /** * Add domain to available list. - * - * @param string $domainName */ private function addAvailableDomain(string $domainName): void { @@ -61,8 +51,6 @@ private function addAvailableDomain(string $domainName): void /** * Add domain to occupied list. - * - * @param string $domainName */ private function addOccupiedDomain(string $domainName): void { diff --git a/src/Responses/Domain/CreateResponse.php b/src/Responses/Domain/CreateResponse.php index 87483bf..c07cb2c 100644 --- a/src/Responses/Domain/CreateResponse.php +++ b/src/Responses/Domain/CreateResponse.php @@ -14,8 +14,6 @@ class CreateResponse extends Response /** * CreateResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -30,8 +28,6 @@ public function __construct(string $rawXml) /** * Get creation date. - * - * @return string */ public function getDate(): string { @@ -40,8 +36,6 @@ public function getDate(): string /** * Get domain name. - * - * @return string */ public function getName(): string { diff --git a/src/Responses/Domain/TransferResponse.php b/src/Responses/Domain/TransferResponse.php index 491022b..acb2117 100644 --- a/src/Responses/Domain/TransferResponse.php +++ b/src/Responses/Domain/TransferResponse.php @@ -23,8 +23,6 @@ class TransferResponse extends Response /** * CreateResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -42,50 +40,37 @@ public function __construct(string $rawXml) $this->name = $data->filter('trnData > name')->text(); $this->status = $data->filter('trnData > trStatus')->text(); - $this->token = $extData->filter('trnData > pw'); + $this->token = $extData->filter('trnData > pw')->text(); } } /** * Get creation date. - * - * @return string */ - public function getExpirationDate(): string + public function getExpirationDate(): ?string { return $this->expirationDate; } - /** - * @return string - */ - public function getStatus(): string + public function getStatus(): ?string { return $this->status; } - /** - * @return string - */ - public function getTransferDate(): string + public function getTransferDate(): ?string { return $this->transferDate; } - /** - * @return string - */ - public function getToken(): string + public function getToken(): ?string { return $this->token; } /** * Get domain name. - * - * @return string */ - public function getName(): string + public function getName(): ?string { return $this->name; } diff --git a/src/Responses/Domain/UpdateResponse.php b/src/Responses/Domain/UpdateResponse.php index a5cdd7d..67d9c2e 100644 --- a/src/Responses/Domain/UpdateResponse.php +++ b/src/Responses/Domain/UpdateResponse.php @@ -8,8 +8,6 @@ class UpdateResponse extends Response { /** * CheckResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { diff --git a/src/Responses/Host/CheckResponse.php b/src/Responses/Host/CheckResponse.php index 95e8f6c..142d661 100644 --- a/src/Responses/Host/CheckResponse.php +++ b/src/Responses/Host/CheckResponse.php @@ -15,8 +15,6 @@ class CheckResponse extends Response /** * CheckResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -33,17 +31,11 @@ public function __construct(string $rawXml) }); } - /** - * @return array - */ public function getAvailableNameservers(): array { return $this->availableNameservers; } - /** - * @return array - */ public function getOccupiedNameservers(): array { return $this->occupiedNameservers; @@ -51,8 +43,6 @@ public function getOccupiedNameservers(): array /** * Add nameserver to available list. - * - * @param string $nameserver */ private function addAvailableNameserver(string $nameserver): void { @@ -61,8 +51,6 @@ private function addAvailableNameserver(string $nameserver): void /** * Add domain to occupied list. - * - * @param string $nameserver */ private function addOccupiedNameserver(string $nameserver): void { diff --git a/src/Responses/Host/CreateResponse.php b/src/Responses/Host/CreateResponse.php index 722b868..465bc12 100644 --- a/src/Responses/Host/CreateResponse.php +++ b/src/Responses/Host/CreateResponse.php @@ -16,8 +16,6 @@ class CreateResponse extends Response * CreateResponse constructor. * * @todo Create error extensions for SIDN. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -32,20 +30,16 @@ public function __construct(string $rawXml) /** * Get creation date. - * - * @return string */ - public function getDate(): string + public function getDate(): ?string { return $this->date; } /** - * Get domain name. - * - * @return string + * Get host name. */ - public function getName(): string + public function getName(): ?string { return $this->name; } diff --git a/src/Responses/Registries/Sidn/Domain/CreateResponse.php b/src/Responses/Registries/Sidn/Domain/CreateResponse.php index 80a533f..a17afdb 100644 --- a/src/Responses/Registries/Sidn/Domain/CreateResponse.php +++ b/src/Responses/Registries/Sidn/Domain/CreateResponse.php @@ -12,14 +12,12 @@ class CreateResponse extends CommonCreateResponse /** * CreateResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { parent::__construct($rawXml); - if (!$this->isSucceeded()) { + if (! $this->isSucceeded()) { $this ->response ->filter('response > extension > ext > response > msg') diff --git a/src/Responses/Registries/Sidn/Domain/UpdateResponse.php b/src/Responses/Registries/Sidn/Domain/UpdateResponse.php index e0f68df..a61d04e 100644 --- a/src/Responses/Registries/Sidn/Domain/UpdateResponse.php +++ b/src/Responses/Registries/Sidn/Domain/UpdateResponse.php @@ -12,14 +12,12 @@ class UpdateResponse extends CommonUpdateResponse /** * CreateResponse constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { parent::__construct($rawXml); - if (!$this->isSucceeded()) { + if (! $this->isSucceeded()) { $this ->response ->filter('response > extension > ext > response > msg') diff --git a/src/Responses/Response.php b/src/Responses/Response.php index 78a13aa..231e7f4 100644 --- a/src/Responses/Response.php +++ b/src/Responses/Response.php @@ -36,8 +36,6 @@ class Response /** * Response constructor. - * - * @param string $rawXml */ public function __construct(string $rawXml) { @@ -50,8 +48,8 @@ public function __construct(string $rawXml) $msg = $result->filter('result > msg'); // Todo: implement RFC 5730 sec. 3 - $this->code = $result->attr('code'); - $this->succeeded = ($msg->count() === 1 && $this->code === '1000'); + $this->code = (int) $result->attr('code'); + $this->succeeded = ($msg->count() === 1 && $this->code === 1000); $this->message = $msg->text(); $this->serverTransaction = $this->response->filter('response > trID > svTRID')->text(); @@ -62,57 +60,36 @@ public function __construct(string $rawXml) } } - /** - * @return string - */ public function getServerTransaction(): string { return $this->serverTransaction; } - /** - * @return null|string - */ public function getClientTransaction(): ?string { return $this->clientTransaction; } - /** - * @return Crawler - */ public function getCrawler(): Crawler { return $this->crawler; } - /** - * @return int - */ public function getCode(): int { return $this->code; } - /** - * @return string - */ public function getRawXml(): string { return $this->rawXml; } - /** - * @return bool - */ public function isSucceeded(): bool { return $this->succeeded; } - /** - * @return string|null - */ public function getMessage(): ?string { return $this->message; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 9ea900d..f5d197b 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -7,23 +7,23 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider /** * Bootstrap application events. */ - public function boot() + public function boot(): void { $this->publishConfig(); } - public function register() + public function register(): void { $this->app->alias(Epp::class, 'Epp'); } - private function publishConfig() + private function publishConfig(): void { $path = $this->getConfigPath(); $this->publishes([$path => config_path('epp.php')], 'config'); } - private function getConfigPath() + private function getConfigPath(): string { return __DIR__.'/../config/epp.php'; } diff --git a/src/Support/ArrayHelper.php b/src/Support/ArrayHelper.php index 2b70024..8ae3b8a 100644 --- a/src/Support/ArrayHelper.php +++ b/src/Support/ArrayHelper.php @@ -6,15 +6,11 @@ class ArrayHelper { /** * Remove empty fields from array. - * - * @param array $array - * - * @return array */ - public static function filterEmpty(array &$array) + public static function filterEmpty(array &$array): array { return $array = array_filter($array, function ($value) { - return !($value === null || empty($value)); + return ! (empty($value)); }); } } diff --git a/src/Support/DomainParser.php b/src/Support/DomainParser.php index 7656e5b..2f563d2 100644 --- a/src/Support/DomainParser.php +++ b/src/Support/DomainParser.php @@ -13,14 +13,10 @@ class DomainParser { /** * Parse the domain name to a model. Returns null when the domain name can't be parsed. - * - * @param string $domainName - * - * @return Domain|null */ public static function parse(string $domainName): ?Domain { - $manager = new Manager(new Cache(), new CurlHttpClient()); + $manager = new Manager(new Cache, new CurlHttpClient); try { $domainData = $manager @@ -34,7 +30,7 @@ public static function parse(string $domainName): ?Domain return null; } - $domain = new Domain(); + $domain = new Domain; $domain->tld = $domainData->getPublicSuffix(); $domain->sld = Str::before($domainData->getRegistrableDomain(), sprintf('.%s', $domain->tld)); $domain->name = sprintf('%s.%s', $domain->sld, $domain->tld); diff --git a/src/Support/Extensions/Extension.php b/src/Support/Extensions/Extension.php index 2f1622b..798c91f 100644 --- a/src/Support/Extensions/Extension.php +++ b/src/Support/Extensions/Extension.php @@ -2,6 +2,4 @@ namespace YWatchman\LaravelEPP\Support\Extensions; -abstract class Extension -{ -} +abstract class Extension {} diff --git a/src/Support/Extensions/Sidn/SidnEppExtension.php b/src/Support/Extensions/Sidn/SidnEppExtension.php index 3d82c45..1e3db6c 100644 --- a/src/Support/Extensions/Sidn/SidnEppExtension.php +++ b/src/Support/Extensions/Sidn/SidnEppExtension.php @@ -18,8 +18,6 @@ class SidnEppExtension extends Extension /** * SidnEppExtension constructor. - * - * @param Crawler $crawler */ public function __construct(Crawler $crawler) { @@ -28,25 +26,16 @@ public function __construct(Crawler $crawler) $this->message = $crawler->text(); } - /** - * @return string - */ public function getCode(): string { return $this->code; } - /** - * @return string - */ public function getField(): string { return $this->field; } - /** - * @return string - */ public function getMessage(): string { return $this->message; diff --git a/src/Support/Traits/Commands/HasDnssec.php b/src/Support/Traits/Commands/HasDnssec.php index 75de511..0f3e1fc 100644 --- a/src/Support/Traits/Commands/HasDnssec.php +++ b/src/Support/Traits/Commands/HasDnssec.php @@ -6,26 +6,17 @@ trait HasDnssec { /** * DNSSEC status. - * - * @var bool */ - protected $dnssec = false; + protected bool $dnssec = false; - /** @var string */ - protected $pubKey; + protected string $pubKey; - /** @var int */ - protected $protocol = 3; + protected int $protocol = 3; - /** @var int */ - protected $flag = 257; + protected int $flag = 257; - /** @var int */ - protected $algorithm = 13; + protected int $algorithm = 13; - /** - * @return string|null - */ public function getPublicKey(): ?string { return $this->pubKey; @@ -34,57 +25,47 @@ public function getPublicKey(): ?string /** * Enable DNSSEC for request. */ - public function enableDNSSEC() + public function enableDNSSEC(): void { $this->dnssec = true; } /** * Set public dnskey. - * - * @param string|null $pubKey */ - public function setPublicKey(?string $pubKey) + public function setPublicKey(?string $pubKey): void { $this->pubKey = $pubKey; } /** * Set DNSSEC algorithm. - * - * @param int $algorithm */ - public function setAlgorithm(int $algorithm) + public function setAlgorithm(int $algorithm): void { $this->algorithm = $algorithm; } /** * Set DNSSEC RR flag. - * - * @param int $flag */ - public function setFlag(int $flag) + public function setFlag(int $flag): void { $this->flag = $flag; } /** * Set signing protocol. - * - * @param int $protocol */ - public function setProtocol(int $protocol) + public function setProtocol(int $protocol): void { $this->protocol = $protocol; } /** * DNSSEC node for extensions. - * - * @return mixed */ - private function dnssecNode() + private function dnssecNode(): mixed { $keyNode = $this->createElement('secDNS:keyData'); $keyOptNode = []; @@ -102,7 +83,7 @@ private function dnssecNode() private function createDnssecExtension(bool $update = false) { - $pubKey = isset($this->extensions['dnssec']['pubKey']) ? $this->extensions['dnssec']['pubKey'] : null; + $pubKey = $this->extensions['dnssec']['pubKey'] ?? null; $this->setPublicKey($pubKey); if ($update) { diff --git a/src/Support/Traits/Commands/HasExtensions.php b/src/Support/Traits/Commands/HasExtensions.php index 17407aa..518a92f 100644 --- a/src/Support/Traits/Commands/HasExtensions.php +++ b/src/Support/Traits/Commands/HasExtensions.php @@ -4,17 +4,13 @@ trait HasExtensions { - /** @var array */ - protected $extensions = []; + protected array $extensions = []; - public function getExtensions() + public function getExtensions(): array { - // + return $this->extensions; } - /** - * @param array $extensions - */ public function setExtensions(array $extensions): void { $this->extensions = $extensions; diff --git a/src/Support/Traits/Commands/HasScheduledDeletion.php b/src/Support/Traits/Commands/HasScheduledDeletion.php index 91e3b3b..9d51bbd 100644 --- a/src/Support/Traits/Commands/HasScheduledDeletion.php +++ b/src/Support/Traits/Commands/HasScheduledDeletion.php @@ -6,23 +6,21 @@ trait HasScheduledDeletion { - /** @var string */ - protected $scheduledDate; + protected string $scheduledDate; - /** @var string */ - protected $scheduledOperation; + protected string $scheduledOperation; /** * Cancellation enabled status. - * - * @var bool */ - protected $planned_cancellation = false; + protected bool $planned_cancellation = false; /** * Enable scheduled deletion for request. + * + * @throws EppException */ - public function enabledScheduledDeletion() + public function enabledScheduledDeletion(): void { $this->planned_cancellation = true; $this->setScheduledOperation($this->extensions['scheduledDelete']['operation']); @@ -31,36 +29,27 @@ public function enabledScheduledDeletion() } } - /** - * @return string - */ public function getScheduledDate(): string { return $this->scheduledDate; } - /** - * @param string $scheduledDate - */ public function setScheduledDate(string $scheduledDate): void { $this->scheduledDate = $scheduledDate; } - /** - * @return string - */ public function getScheduledOperation(): string { return $this->scheduledOperation; } /** - * @param string $scheduledOperation + * @throws EppException */ public function setScheduledOperation(string $scheduledOperation): void { - if (!in_array( + if (! in_array( $scheduledOperation, [ 'setDate', @@ -79,10 +68,8 @@ public function setScheduledOperation(string $scheduledOperation): void * - setDate # Set a cancellation date * - setDateToEndOfSubscriptionPeriod # Cancel domain at the end of the subscription. * - cancel # Cancel the planned cancellation. - * - * @return mixed */ - private function scheduledCancellationNode() + private function scheduledCancellationNode(): mixed { $node = $this->createElement('scheduledDelete:update'); $node->setAttribute('xmlns:scheduledDelete', 'http://rxsd.domain-registry.nl/sidn-ext-epp-scheduled-delete-1.0'); diff --git a/src/Support/Traits/Commands/ProvidesCheckCommand.php b/src/Support/Traits/Commands/ProvidesCheckCommand.php index 3f445b2..a5914c6 100644 --- a/src/Support/Traits/Commands/ProvidesCheckCommand.php +++ b/src/Support/Traits/Commands/ProvidesCheckCommand.php @@ -4,27 +4,23 @@ use DOMAttr; use DOMElement; +use DOMException; trait ProvidesCheckCommand { /** * Generate check node. * - * @param string $type - * - * @return DOMElement + * @throws DOMException */ private function generateCheck(string $type): DOMElement { /** @var DOMElement $node */ $node = $this->createElement(self::NODE); $node->setAttributeNodeNS(new DOMAttr(sprintf('xmlns:%s', self::NODE_BASE), self::NAMESPACE)); - $node->setAttributeNodeNS( - new DOMAttr('xmlns:'.self::NODE_BASE, self::NAMESPACE) - ); foreach ($this->iterable as $iterable) { - list($key, $value) = $this->checkKey($type); + [$key, $value] = $this->checkKey($type); $node->appendChild( $this->createElement( self::NODE_BASE.':'.$key, @@ -39,20 +35,15 @@ private function generateCheck(string $type): DOMElement /** * Get key for generateCheck(string). * - * @param string $type * * @return string[] */ private function checkKey(string $type): array { // format: ['external_key', 'local_key'] - switch ($type) { - case 'domain': - return ['name', 'domainname']; - case 'host': - return ['name', 'name']; - default: - return ['id', 'handle']; - } + return match ($type) { + 'domain', 'host' => ['name', 'name'], + default => ['id', 'handle'], + }; } } diff --git a/src/Support/Traits/Commands/ProvidesContactCommand.php b/src/Support/Traits/Commands/ProvidesContactCommand.php index 014a135..43f6154 100644 --- a/src/Support/Traits/Commands/ProvidesContactCommand.php +++ b/src/Support/Traits/Commands/ProvidesContactCommand.php @@ -10,10 +10,6 @@ trait ProvidesContactCommand { /** * Transform contact array into DOMElement. - * - * @param array $contact - * - * @return DOMElement */ public function handleContact(array $contact): DOMElement { @@ -44,9 +40,6 @@ public function handleContact(array $contact): DOMElement /** * Recurse tags for child. - * - * @param array $childTags - * @param DOMElement $node */ private function recurse(array &$childTags, DOMElement $node): void { diff --git a/src/Support/Traits/Transformers/HasAuthentication.php b/src/Support/Traits/Transformers/HasAuthentication.php index 963716b..f26f2eb 100644 --- a/src/Support/Traits/Transformers/HasAuthentication.php +++ b/src/Support/Traits/Transformers/HasAuthentication.php @@ -8,10 +8,8 @@ trait HasAuthentication { /** * Append authentication node to the end. - * - * @param string $password */ - public function includeAuth($password = Command::NOT_USED) + public function includeAuth(string $password = Command::NOT_USED): void { $this->transformed['authInfo'] = [ 'pw' => $password, diff --git a/src/Support/Xml/Commands/Command.php b/src/Support/Xml/Commands/Command.php index a15cdb4..32bf2d7 100644 --- a/src/Support/Xml/Commands/Command.php +++ b/src/Support/Xml/Commands/Command.php @@ -14,17 +14,19 @@ class Command extends XmlHelper /** @var DOMNode */ protected $walknode; + /** * @var DOMElement */ private $commandNode; + protected ?string $clientTransactionId; + public function __construct() { parent::__construct(); $this->commandNode = $this->createElement('command'); - - // Todo: clTRID + $this->clientTransactionId = uniqid('epp-', true); } /** @@ -38,9 +40,11 @@ public function __toString() ->document ->createElement('epp'); $node->setAttributeNodeNS(new DOMAttr('xmlns', 'urn:ietf:params:xml:ns:epp-1.0')); - $node->setAttributeNodeNS(new DOMAttr('xmlns:sidn-ext-epp', 'http://rxsd.domain-registry.nl/sidn-ext-epp-1.0')); foreach ($this->nodes as $newNode) { + if ($newNode->nodeName === 'command' && $this->clientTransactionId !== null) { + $newNode->appendChild($this->createElement('clTRID', $this->clientTransactionId)); + } $node->appendChild($newNode); } @@ -49,11 +53,6 @@ public function __toString() return $this->document->saveXML(null, LIBXML_NOEMPTYTAG); } - /** - * @param $cmd - * - * @return string - */ public function getCommandTag($cmd): string { return sprintf('%s:%s', static::NODE_BASE, $cmd); @@ -70,9 +69,7 @@ protected function getCommandNode() } /** - * @param $value - * @param $key - * @param null $node + * @param null $node */ protected function recurseCallback($value, $key, $node = null): void { @@ -100,9 +97,6 @@ protected function recurseCallback($value, $key, $node = null): void } } - /** - * @param $child - */ protected function setAttributes($child): void { foreach ($child as $key => $item) { @@ -110,10 +104,6 @@ protected function setAttributes($child): void } } - /** - * @param array $tag - * @param DOMNode $node - */ protected function recurseTags(array $tag, DOMNode $node): void { array_walk($tag, function ($value, $key) { diff --git a/src/Support/Xml/Commands/Contact/CheckCommand.php b/src/Support/Xml/Commands/Contact/CheckCommand.php index d32d664..cfa3c47 100644 --- a/src/Support/Xml/Commands/Contact/CheckCommand.php +++ b/src/Support/Xml/Commands/Contact/CheckCommand.php @@ -3,6 +3,7 @@ namespace YWatchman\LaravelEPP\Support\Xml\Commands\Contact; use DOMElement; +use DOMException; use Illuminate\Database\Eloquent\Model; use YWatchman\LaravelEPP\Models\Contact; use YWatchman\LaravelEPP\Support\Traits\Commands\ProvidesCheckCommand; @@ -13,23 +14,24 @@ class CheckCommand extends Command use ProvidesCheckCommand; public const NODE_BASE = 'contact'; + public const NODE = 'contact:check'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:contact-1.0'; - /** - * @var DOMElement - */ - protected $node; + protected DOMElement $node; /** * @var Contact[] */ - protected $iterable; + protected array $iterable; /** * CreateCommand constructor. * - * @param Contact[]|Model[] $contacts + * @param Contact[]|Model[] $contacts + * + * @throws DOMException */ public function __construct(array $contacts) { diff --git a/src/Support/Xml/Commands/Contact/CreateCommand.php b/src/Support/Xml/Commands/Contact/CreateCommand.php index d6b4f23..615a4d2 100644 --- a/src/Support/Xml/Commands/Contact/CreateCommand.php +++ b/src/Support/Xml/Commands/Contact/CreateCommand.php @@ -4,7 +4,6 @@ use DOMElement; use YWatchman\LaravelEPP\Contracts\IsContact; -use YWatchman\LaravelEPP\Exceptions\EppException; use YWatchman\LaravelEPP\Models\Contact; use YWatchman\LaravelEPP\Support\Traits\Commands\ProvidesContactCommand; use YWatchman\LaravelEPP\Support\Xml\Commands\Command; @@ -15,25 +14,17 @@ class CreateCommand extends Command use ProvidesContactCommand; public const NODE_BASE = 'contact'; + public const NODE = 'contact:create'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:contact-1.0'; - /** - * @var DOMElement - */ - protected $node; + protected DOMElement $node; - /** - * @var Contact - */ - protected $contact; + protected Contact $contact; /** * CreateCommand constructor. - * - * @param IsContact $contact - * - * @throws EppException */ public function __construct(IsContact $contact) { @@ -50,10 +41,7 @@ public function __construct(IsContact $contact) $n->appendChild($this->getExtensionNode()); } - /** - * @return DOMElement - */ - protected function getCreateNode() + protected function getCreateNode(): DOMElement { $contact = new ContactTransformer($this->contact); $contact = $contact->toArray(); @@ -63,22 +51,23 @@ protected function getCreateNode() /** * Generate contact extension. - * - * @throws EppException - * - * @return DOMElement */ - protected function getExtensionNode() + protected function getExtensionNode(): DOMElement { $contact = $this->contact->fields(); $node = $this->createElement('extension'); $sidnExtension = $this->createElement('sidn-ext-epp:ext'); + $sidnExtension->setAttributeNS( + 'http://www.w3.org/2000/xmlns/', + 'xmlns:sidn-ext-epp', + 'https://rxsd.domain-registry.nl/sidn-ext-epp-1.0' + ); $create = $this->createElement('sidn-ext-epp:create'); $contactNode = $this->createElement('sidn-ext-epp:contact'); - $legalForm = $this->createElement('sidn-ext-epp:legalForm', $contact['legalForm']); + $legalForm = $this->createElement('sidn-ext-epp:legalForm', $this->mapLegalForm($contact['legalForm'])); $contactNode->appendChild($legalForm); if (isset($contact['legalFormNo'])) { @@ -93,4 +82,13 @@ protected function getExtensionNode() return $node; } + + private function mapLegalForm(string $form): string + { + return match (strtoupper($form)) { + 'PERSON' => 'PERSOON', + 'OTHER' => 'ANDERS', + default => strtoupper($form), + }; + } } diff --git a/src/Support/Xml/Commands/Contact/UpdateCommand.php b/src/Support/Xml/Commands/Contact/UpdateCommand.php index 19b9eb8..6331bd4 100644 --- a/src/Support/Xml/Commands/Contact/UpdateCommand.php +++ b/src/Support/Xml/Commands/Contact/UpdateCommand.php @@ -18,33 +18,24 @@ class UpdateCommand extends Command use ProvidesContactCommand; public const NODE_BASE = 'contact'; + public const NODE = 'contact:update'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:contact-1.0'; - /** - * @var DOMElement - */ - protected $node; + protected DOMElement $node; - /** - * @var Contact - */ - protected $contact; + protected Contact $contact; - /** - * @var string - */ - protected $registrar; + protected string $registrar; /** * CreateCommand constructor. * - * @param Contact $contact - * @param string $registrar * * @throws EppException */ - public function __construct(Contact $contact, $registrar = Registrar::REGISTRAR_SIDN) + public function __construct(Contact $contact, string $registrar = Registrar::REGISTRAR_SIDN) { parent::__construct(); @@ -60,10 +51,7 @@ public function __construct(Contact $contact, $registrar = Registrar::REGISTRAR_ $n->appendChild($this->getExtensionNode()); } - /** - * @return DOMElement - */ - protected function getUpdateNode() + protected function getUpdateNode(): DOMElement { $contact = new ContactTransformer($this->contact); $contact = $contact->toArray(); @@ -74,11 +62,11 @@ protected function getUpdateNode() /** * Generate contact extension. * - * @throws EppException * - * @return DOMElement + * + * @throws EppException */ - protected function getExtensionNode() + protected function getExtensionNode(): DOMElement { $node = $this->createElement('extension'); $classPath = Extension::contactInstance($this->registrar); diff --git a/src/Support/Xml/Commands/Domain/CheckCommand.php b/src/Support/Xml/Commands/Domain/CheckCommand.php index 24c6411..641e083 100644 --- a/src/Support/Xml/Commands/Domain/CheckCommand.php +++ b/src/Support/Xml/Commands/Domain/CheckCommand.php @@ -11,7 +11,9 @@ class CheckCommand extends Command use ProvidesCheckCommand; public const NODE_BASE = 'domain'; + public const NODE = 'domain:check'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:domain-1.0'; /** @var DOMElement */ @@ -22,8 +24,6 @@ class CheckCommand extends Command /** * CheckCommand constructor. - * - * @param array $domains */ public function __construct(array $domains) { diff --git a/src/Support/Xml/Commands/Domain/CreateCommand.php b/src/Support/Xml/Commands/Domain/CreateCommand.php index 88eaccc..c1dcd44 100644 --- a/src/Support/Xml/Commands/Domain/CreateCommand.php +++ b/src/Support/Xml/Commands/Domain/CreateCommand.php @@ -14,6 +14,7 @@ class CreateCommand extends Command use HasDnssec; public const NODE = 'domain:create'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:domain-1.0'; /** @@ -53,21 +54,12 @@ class CreateCommand extends Command */ protected $extensions = []; - /** - * @var string|null - */ - protected $transactionId; - /** * CreateCommand constructor. * - * @param Domain $domain - * @param Contact $admin admin-c handle - * @param Contact $tech tech-c handle - * @param Contact $registrant registrant handle - * @param array $nameservers - * @param array $extensions - * @param string|null $transactionId + * @param Contact $admin admin-c handle + * @param Contact $tech tech-c handle + * @param Contact $registrant registrant handle */ public function __construct( Domain $domain, @@ -76,7 +68,7 @@ public function __construct( Contact $registrant, array $nameservers, array $extensions = [], - string $transactionId = null + ?string $transactionId = null ) { parent::__construct(); @@ -86,7 +78,9 @@ public function __construct( $this->registrant = $registrant; $this->nameservers = $nameservers; $this->extensions = $extensions; - $this->transactionId = $transactionId; + if ($transactionId !== null) { + $this->clientTransactionId = $transactionId; + } if (array_key_exists('dnssec', $this->extensions)) { $this->enableDNSSEC(); @@ -108,18 +102,11 @@ public function __toString() $n->appendChild($this->getExtensionNode()); } - // Todo: move $n to $this->node or something and move this code below to the Command class. - if ($this->transactionId !== null && is_string($this->transactionId)) { - $n->appendChild($this->createElement('clTRID', $this->transactionId)); - } - return parent::__toString(); } /** * Fill command. - * - * @return DOMElement */ protected function getCreateNode(): DOMElement { @@ -157,8 +144,6 @@ protected function getCreateNode(): DOMElement /** * Get contact nodes. - * - * @return array */ protected function getContactNodes(): array { @@ -186,9 +171,6 @@ protected function getContactNodes(): array return $nodes; } - /** - * @return DOMElement - */ protected function getExtensionNode(): DOMElement { $extNode = $this->createElement('extension'); diff --git a/src/Support/Xml/Commands/Domain/TransferCommand.php b/src/Support/Xml/Commands/Domain/TransferCommand.php index d7dd605..ebb89c1 100644 --- a/src/Support/Xml/Commands/Domain/TransferCommand.php +++ b/src/Support/Xml/Commands/Domain/TransferCommand.php @@ -13,7 +13,9 @@ class TransferCommand extends Command use HasDnssec; public const NODE_BASE = 'domain'; + public const NODE = 'domain:transfer'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:domain-1.0'; /** @@ -45,17 +47,12 @@ class TransferCommand extends Command /** * CreateCommand constructor. - * - * @param Domain $domain - * @param string $token - * @param array $extensions - * @param string|null $transactionId */ public function __construct( Domain $domain, string $token, array $extensions = [], - string $transactionId = null + ?string $transactionId = null ) { parent::__construct(); @@ -80,8 +77,6 @@ public function __construct( /** * Fill command. - * - * @return DOMElement */ protected function getCreateNode(): DOMElement { diff --git a/src/Support/Xml/Commands/Domain/UpdateCommand.php b/src/Support/Xml/Commands/Domain/UpdateCommand.php index add449c..1aa2000 100644 --- a/src/Support/Xml/Commands/Domain/UpdateCommand.php +++ b/src/Support/Xml/Commands/Domain/UpdateCommand.php @@ -17,7 +17,9 @@ class UpdateCommand extends Command use HasScheduledDeletion; public const NODE_BASE = 'domain'; + public const NODE = 'domain:update'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:domain-1.0'; /** @@ -54,12 +56,6 @@ class UpdateCommand extends Command /** * UpdateCommand constructor. - * - * @param Domain $domain - * @param array $add - * @param array $delete - * @param array $update - * @param array $extensions */ public function __construct( Domain $domain, @@ -121,8 +117,6 @@ protected function getUpdateNode() * Get add nodes. * * @throws Exception - * - * @return DOMElement */ protected function getAddNodes(): DOMElement { @@ -130,7 +124,7 @@ protected function getAddNodes(): DOMElement foreach ($this->add as $key => $addNode) { if (is_array($addNode)) { - if (!is_string($key)) { + if (! is_string($key)) { throw new Exception('Key should be a string.'); } $element = $this->createElement($key); @@ -154,9 +148,10 @@ protected function getAddNodes(): DOMElement /** * Get remove nodes. * - * @throws Exception * * @return DOMElement + * + * @throws Exception */ protected function getRemNodes() { @@ -164,7 +159,7 @@ protected function getRemNodes() foreach ($this->delete as $key => $delNode) { if (is_array($delNode)) { - if (!is_string($key)) { + if (! is_string($key)) { throw new Exception('Key should be a string.'); } $element = $this->createElement($key); @@ -188,9 +183,10 @@ protected function getRemNodes() /** * Get update nodes. * - * @throws Exception * * @return DOMElement + * + * @throws Exception */ protected function getChgNodes() { @@ -198,7 +194,7 @@ protected function getChgNodes() foreach ($this->update as $key => $chgNode) { if (is_array($chgNode)) { - if (!is_string($key)) { + if (! is_string($key)) { throw new Exception('Key should be a string.'); } $element = $this->createElement($key); diff --git a/src/Support/Xml/Commands/Host/CheckCommand.php b/src/Support/Xml/Commands/Host/CheckCommand.php index c51161d..9d7e435 100644 --- a/src/Support/Xml/Commands/Host/CheckCommand.php +++ b/src/Support/Xml/Commands/Host/CheckCommand.php @@ -13,7 +13,9 @@ class CheckCommand extends Command use ProvidesCheckCommand; public const NODE_BASE = 'host'; + public const NODE = 'host:check'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:host-1.0'; /** @@ -29,7 +31,7 @@ class CheckCommand extends Command /** * CreateCommand constructor. * - * @param Nameserver[]|Model[] $nameservers + * @param Nameserver[]|Model[] $nameservers */ public function __construct(array $nameservers) { diff --git a/src/Support/Xml/Commands/Host/CreateCommand.php b/src/Support/Xml/Commands/Host/CreateCommand.php index 4e1bf7c..63624c1 100644 --- a/src/Support/Xml/Commands/Host/CreateCommand.php +++ b/src/Support/Xml/Commands/Host/CreateCommand.php @@ -11,7 +11,9 @@ class CreateCommand extends Command { public const NODE_BASE = 'host'; + public const NODE = 'host:create'; + public const NAMESPACE = 'urn:ietf:params:xml:ns:host-1.0'; /** @@ -42,9 +44,6 @@ public function __construct(Nameserver $nameserver) ->appendChild($this->getCreateNode()); } - /** - * @return DOMElement - */ protected function getCreateNode(): DOMElement { $node = $this->createElement('create'); @@ -65,7 +64,7 @@ private function handleNamserver(Nameserver $nameserver): DOMElement $addresses = $nameserver->getAddresses(); foreach ($addresses as $address) { $address = explode('-', $address, 2); - if (!empty($address[0])) { + if (! empty($address[0])) { $ipNode = $this->createElement('host:addr', $address[0]); if (count($address) > 1) { diff --git a/src/Support/Xml/Commands/Session/HelloCommand.php b/src/Support/Xml/Commands/Session/HelloCommand.php index d71c43b..1c32dbf 100644 --- a/src/Support/Xml/Commands/Session/HelloCommand.php +++ b/src/Support/Xml/Commands/Session/HelloCommand.php @@ -7,8 +7,7 @@ class HelloCommand extends Command { - /** @var DOMElement */ - protected $node; + protected DOMElement $node; /** * HelloCommand constructor. diff --git a/src/Support/Xml/Commands/Session/LoginCommand.php b/src/Support/Xml/Commands/Session/LoginCommand.php index 12188e8..f521801 100644 --- a/src/Support/Xml/Commands/Session/LoginCommand.php +++ b/src/Support/Xml/Commands/Session/LoginCommand.php @@ -7,14 +7,13 @@ class LoginCommand extends Command { - /** @var DOMElement */ - protected $node; + protected DOMElement $node; /** * LoginCommand constructor. * - * @param string $username EPP Username - * @param string $password EPP Password + * @param string $username EPP Username + * @param string $password EPP Password */ public function __construct(string $username, string $password) { @@ -38,9 +37,9 @@ public function __construct(string $username, string $password) $svcNode->appendChild($this->createElement('objURI', 'urn:ietf:params:xml:ns:domain-1.0')); $svcExt = $svcNode->appendChild($this->createElement('svcExtension')); - $svcExt->appendChild($this->createElement('extURI', 'http://rxsd.domain-registry.nl/sidn-ext-epp-1.0')); - $svcExt->appendChild($this->createElement('extURI', 'http://rxsd.domain-registry.nl/sidn-ext-epp-registry-contacts-delete-1.0')); - $svcExt->appendChild($this->createElement('extURI', 'http://rxsd.domain-registry.nl/sidn-ext-epp-scheduled-delete-1.0')); + $svcExt->appendChild($this->createElement('extURI', 'https://rxsd.domain-registry.nl/sidn-ext-epp-1.0')); + $svcExt->appendChild($this->createElement('extURI', 'https://rxsd.domain-registry.nl/sidn-ext-epp-registry-contacts-delete-1.0')); + $svcExt->appendChild($this->createElement('extURI', 'https://rxsd.domain-registry.nl/sidn-ext-epp-scheduled-delete-1.0')); $svcExt->appendChild($this->createElement('extURI', 'urn:ietf:params:xml:ns:secDNS-1.1')); $svcExt->appendChild($this->createElement('extURI', 'urn:ietf:params:xml:ns:keyrelay-1.0')); } diff --git a/src/Support/Xml/Commands/Session/LogoutCommand.php b/src/Support/Xml/Commands/Session/LogoutCommand.php index 0db83b7..01d0e62 100644 --- a/src/Support/Xml/Commands/Session/LogoutCommand.php +++ b/src/Support/Xml/Commands/Session/LogoutCommand.php @@ -7,8 +7,7 @@ class LogoutCommand extends Command { - /** @var DOMElement */ - protected $node; + protected DOMElement $node; /** * LogoutCommand constructor. diff --git a/src/Support/Xml/Extensions/Extension.php b/src/Support/Xml/Extensions/Extension.php index 70dff41..ac39d1d 100644 --- a/src/Support/Xml/Extensions/Extension.php +++ b/src/Support/Xml/Extensions/Extension.php @@ -33,18 +33,16 @@ public function __construct() if ($this->prefix === null) { throw new EppException('Extension prefix cannot be null.'); } - $this->helper = new XmlHelper(); + $this->helper = new XmlHelper; $this->extension = $this->helper->createElement($this->prefix.':ext'); } /** * Return new ContactExtension instance. * - * @param string $registrar + * @param string $registrar * * @throws EppException - * - * @return string */ public static function contactInstance($registrar = Registrar::REGISTRAR_SIDN): string { @@ -61,8 +59,6 @@ public static function contactInstance($registrar = Registrar::REGISTRAR_SIDN): /** * Retrieve extension. - * - * @return DOMElement */ public function getExtension(): DOMElement { @@ -71,10 +67,6 @@ public function getExtension(): DOMElement /** * Get prefixed name. - * - * @param string $name - * - * @return string */ public function prefixedName(string $name): string { diff --git a/src/Support/Xml/Extensions/Sidn/ContactExtension.php b/src/Support/Xml/Extensions/Sidn/ContactExtension.php index 296365e..546280a 100644 --- a/src/Support/Xml/Extensions/Sidn/ContactExtension.php +++ b/src/Support/Xml/Extensions/Sidn/ContactExtension.php @@ -11,7 +11,6 @@ class ContactExtension extends SidnExtension /** * ContactExtension constructor. * - * @param IsContact $contact * * @throws EppException */ @@ -30,10 +29,6 @@ public function __construct(IsContact $contact) /** * Return the fields array as a DOM node. - * - * @param array $fields - * - * @return DOMElement */ private function getContactData(array $fields): DOMElement { diff --git a/src/Support/Xml/Objects/Contact/ContactObject.php b/src/Support/Xml/Objects/Contact/ContactObject.php index 0530324..8933728 100644 --- a/src/Support/Xml/Objects/Contact/ContactObject.php +++ b/src/Support/Xml/Objects/Contact/ContactObject.php @@ -18,8 +18,11 @@ abstract class ContactObject ]; public const CONTACT_ADMIN = 'admin'; + public const CONTACT_TECH = 'tech'; + public const CONTACT_BILLING = 'billing'; + public const CONTACT_REGISTRANT = 'registrant'; /** @var string */ @@ -64,13 +67,12 @@ abstract class ContactObject /** * ContactObject constructor. * - * @param string $type * * @throws EppException */ public function __construct(string $type) { - if (!in_array($type, self::CONTACT_TYPES)) { + if (! in_array($type, self::CONTACT_TYPES)) { throw EppException::contactTypeDoesNotExist($type); } $this->setType($type); @@ -78,217 +80,137 @@ public function __construct(string $type) /** * Return ContactObject from Contact modal. - * - * @param Contact $contact */ public static function createFromModel(Contact $contact) { -// return new self($contact->) + // return new self($contact->) } - /** - * @return string - */ public function getType(): string { return $this->type; } - /** - * @param string $type - */ public function setType(string $type): void { $this->type = $type; } - /** - * @return string - */ public function getId(): string { return $this->id; } - /** - * @param string $id - */ public function setId(string $id): void { $this->id = $id; } - /** - * @return string - */ public function getName(): string { return $this->name; } - /** - * @param string $name - */ public function setName(string $name): void { $this->name = $name; } - /** - * @return string - */ public function getOrg(): string { return $this->org; } - /** - * @param string $org - */ public function setOrg(string $org): void { $this->org = $org; } - /** - * @return string - */ public function getStreet(): string { return $this->street; } - /** - * @param string $street - */ public function setStreet(string $street): void { $this->street = $street; } - /** - * @return string - */ public function getCity(): string { return $this->city; } - /** - * @param string $city - */ public function setCity(string $city): void { $this->city = $city; } - /** - * @return string - */ public function getSp(): string { return $this->sp; } - /** - * @param string $sp - */ public function setSp(string $sp): void { $this->sp = $sp; } - /** - * @return string - */ public function getPc(): string { return $this->pc; } - /** - * @param string $pc - */ public function setPc(string $pc): void { $this->pc = $pc; } - /** - * @return string - */ public function getCc(): string { return $this->cc; } - /** - * @param string $cc - */ public function setCc(string $cc): void { $this->cc = $cc; } - /** - * @return string - */ public function getVoice(): string { return $this->voice; } - /** - * @param string $voice - */ public function setVoice(string $voice): void { $this->voice = $voice; } - /** - * @return string - */ public function getFax(): string { return $this->fax; } - /** - * @param string $fax - */ public function setFax(string $fax): void { $this->fax = $fax; } - /** - * @return string - */ public function getEmail(): string { return $this->email; } - /** - * @param string $email - */ public function setEmail(string $email): void { $this->email = $email; } - /** - * @return bool - */ public function isDisclose(): bool { return $this->disclose; } - /** - * @param bool $disclose - */ public function setDisclose(bool $disclose): void { $this->disclose = $disclose; diff --git a/src/Support/Xml/Objects/Host/AuthObject.php b/src/Support/Xml/Objects/Host/AuthObject.php index 6a68119..c6f771c 100644 --- a/src/Support/Xml/Objects/Host/AuthObject.php +++ b/src/Support/Xml/Objects/Host/AuthObject.php @@ -10,21 +10,27 @@ class AuthObject { public const AUTH_DOMAIN = 'domain:authInfo'; + public const AUTH_CONTACT = 'contact:authInfo'; + public const AUTH_TYPES = [ self::AUTH_DOMAIN, self::AUTH_CONTACT, ]; public const PW_DOMAIN = 'domain:pw'; + public const PW_CONTACT = 'contact:pw'; + public const PW_TYPES = [ self::PW_DOMAIN, self::PW_CONTACT, ]; public const DOMAIN_NAMESPACE = 'urn:ietf:params:xml:ns:domain-1.0'; + public const CONTACT_NAMESPACE = 'urn:ietf:params:xml:ns:contact-1.0'; + public const NAMESPACES = [ self::DOMAIN_NAMESPACE, self::CONTACT_NAMESPACE, @@ -32,12 +38,12 @@ class AuthObject public static function getAuthInfo(string $type, string $value = Command::NOT_USED) // Todo: move constant to global class { - if (!in_array($type, self::AUTH_TYPES)) { + if (! in_array($type, self::AUTH_TYPES)) { throw EppException::authTypeDoesNotExist($type); } - if (!in_array($type, self::NAMESPACES)) { -// throw EppException::namespac($type); namespace err + if (! in_array($type, self::NAMESPACES)) { + // throw EppException::namespac($type); namespace err } switch ($type) { @@ -53,7 +59,7 @@ public static function getAuthInfo(string $type, string $value = Command::NOT_US throw EppException::notImplemented(); } - $document = new DOMDocument(); + $document = new DOMDocument; $authObject = $document ->createElement($type); $authObject->appendChild($pwNode); diff --git a/src/Support/Xml/Objects/Host/HostObject.php b/src/Support/Xml/Objects/Host/HostObject.php index bbdff41..e65e027 100644 --- a/src/Support/Xml/Objects/Host/HostObject.php +++ b/src/Support/Xml/Objects/Host/HostObject.php @@ -9,7 +9,6 @@ class HostObject /** * Get array of DOM elements. * - * @param array $nameservers * * @return array */ @@ -23,7 +22,6 @@ public static function getNameservers(array $nameservers) /** * Get DOM element for single nameserver. * - * @param $nameserver * * @return DOMElement */ diff --git a/src/Support/Xml/XmlHelper.php b/src/Support/Xml/XmlHelper.php index dc89aa6..95cc92a 100644 --- a/src/Support/Xml/XmlHelper.php +++ b/src/Support/Xml/XmlHelper.php @@ -32,9 +32,7 @@ public function __construct() /** * Add DOM node to current XML Document. * - * @param string $name - * @param DOMNode|null $node if should be layered - * + * @param DOMNode|null $node if should be layered * @return DOMNode */ public function addNode(string $name, $node = null) @@ -51,10 +49,7 @@ public function addNode(string $name, $node = null) /** * Alias for DOMDocument::createElement(). * - * @param $name - * @param string|null $value - * - * @return DOMElement + * @param string|null $value */ public function createElement($name, $value = null): DOMElement { diff --git a/src/Transformers/ContactTransformer.php b/src/Transformers/ContactTransformer.php index 130e04a..3e2a207 100644 --- a/src/Transformers/ContactTransformer.php +++ b/src/Transformers/ContactTransformer.php @@ -11,8 +11,6 @@ class ContactTransformer extends Transformer /** * ContactTransformer constructor. - * - * @param Transformable $transformable */ public function __construct(Transformable $transformable) { @@ -24,23 +22,19 @@ public function __construct(Transformable $transformable) /** * Transform contact model to array. - * - * @return array|void */ - public function toArray() + public function toArray(): ?array { return $this->transformed; } /** * Return transformed array. - * - * @return array|void */ - protected function transform() + protected function transform(): array { return [ - 'id' => $this->transformable->handle, + 'id' => $this->transformable->handle, 'postalInfo' => [ 'attributes' => [ 'type' => 'loc', @@ -52,14 +46,14 @@ protected function transform() $this->transformable->number, $this->transformable->suffix, ], - 'city' => $this->transformable->city, - 'sp' => $this->transformable->state, - 'pc' => $this->transformable->postal, - 'cc' => $this->transformable->country, + 'city' => $this->transformable->city, + 'sp' => $this->transformable->state, + 'pc' => $this->transformable->postal, + 'cc' => $this->transformable->country, ], ], 'voice' => $this->transformable->phone, - 'fax' => $this->transformable->fax, + 'fax' => $this->transformable->fax, 'email' => $this->transformable->email, ]; } diff --git a/src/Transformers/Transformer.php b/src/Transformers/Transformer.php index a33f5ce..f766ae2 100644 --- a/src/Transformers/Transformer.php +++ b/src/Transformers/Transformer.php @@ -16,8 +16,6 @@ abstract class Transformer /** * Transformer constructor. - * - * @param Transformable $transformable */ public function __construct(Transformable $transformable) { @@ -29,7 +27,7 @@ public function __construct(Transformable $transformable) */ public function toArray() { - throw new NotImplementedException(); + throw new NotImplementedException; } /** @@ -37,6 +35,6 @@ public function toArray() */ protected function transform() { - throw new NotImplementedException(); + throw new NotImplementedException; } } diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..e53fb11 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,5 @@ +in('Unit'); diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..bd08b68 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,19 @@ +set('epp.debug', false); + } +} diff --git a/tests/Unit/Commands/Contact/CreateCommandTest.php b/tests/Unit/Commands/Contact/CreateCommandTest.php new file mode 100644 index 0000000..72c7fb4 --- /dev/null +++ b/tests/Unit/Commands/Contact/CreateCommandTest.php @@ -0,0 +1,59 @@ + 'TEST001', + 'name' => 'Test User', + 'street' => 'Main Street', + 'number' => '1', + 'city' => 'Amsterdam', + 'postal' => '1000AA', + 'country' => 'NL', + 'phone' => '+31.201234567', + 'email' => 'test@example.nl', + 'legalForm' => 'PERSOON', + ]); + + return new CreateCommand($contact); +} + +test('contact create command includes clTRID', function () { + $xml = (string) makeContactCommand(); + + expect($xml)->toContain('loadXML($xml); + + // Root must not carry the sidn namespace declaration + $epp = $doc->documentElement; + expect($epp->hasAttribute('xmlns:sidn-ext-epp'))->toBeFalse() + ->and($xml)->toContain('sidn-ext-epp:ext') + ->and($xml)->toContain('xmlns:sidn-ext-epp="https://rxsd.domain-registry.nl/sidn-ext-epp-1.0"'); + + // The namespace declaration must appear somewhere inside (on the ext element) +}); + +test('contact create command includes legalForm in sidn extension', function () { + $xml = (string) makeContactCommand(); + + expect($xml)->toContain('sidn-ext-epp:legalForm') + ->and($xml)->toContain('PERSOON'); +}); + +test('contact create command produces valid xml', function () { + $xml = (string) makeContactCommand(); + + $doc = new DOMDocument; + $result = $doc->loadXML($xml); + + expect($result)->toBeTrue(); +}); diff --git a/tests/Unit/Commands/Domain/CreateCommandTest.php b/tests/Unit/Commands/Domain/CreateCommandTest.php new file mode 100644 index 0000000..97c162f --- /dev/null +++ b/tests/Unit/Commands/Domain/CreateCommandTest.php @@ -0,0 +1,54 @@ + 'example', 'tld' => 'nl']); + $contact = new Contact(['handle' => 'TEST001']); + $ns = new Nameserver(['name' => 'ns1.example.nl', 'address' => '1.2.3.4-v4']); + + return new CreateCommand($domain, $contact, $contact, $contact, [$ns], [], $transactionId); +} + +test('domain create command contains clTRID', function () { + $xml = (string) makeDomainCommand(); + + expect($xml)->toContain('toContain('my-custom-txid'); +}); + +test('domain create command auto-generates clTRID when none provided', function () { + $xml1 = (string) makeDomainCommand(); + $xml2 = (string) makeDomainCommand(); + + // Both have clTRID, and they are different (auto-generated unique IDs) + expect($xml1)->toContain('and($xml2)->toContain('and($xml1)->not->toBe($xml2); +}); + +test('domain create command does not include sidn namespace on root epp element', function () { + $xml = (string) makeDomainCommand(); + + $doc = new DOMDocument; + $doc->loadXML($xml); + $epp = $doc->documentElement; + + expect($epp->hasAttribute('xmlns:sidn-ext-epp'))->toBeFalse(); +}); + +test('domain create command includes domain create node', function () { + $xml = (string) makeDomainCommand(); + + expect($xml)->toContain('domain:create') + ->and($xml)->toContain('example.nl'); +}); diff --git a/tests/Unit/Commands/Session/HelloCommandTest.php b/tests/Unit/Commands/Session/HelloCommandTest.php new file mode 100644 index 0000000..938200c --- /dev/null +++ b/tests/Unit/Commands/Session/HelloCommandTest.php @@ -0,0 +1,23 @@ +toContain('and($xml)->toContain('xmlns="urn:ietf:params:xml:ns:epp-1.0"'); +}); + +test('hello command does not include sidn namespace on root epp element', function () { + $xml = (string) new HelloCommand; + + expect($xml)->not->toContain('xmlns:sidn-ext-epp'); +}); + +test('hello command does not include clTRID', function () { + $xml = (string) new HelloCommand; + + // HelloCommand adds not , so clTRID should not be appended + expect($xml)->not->toContain(' + + + + Command completed successfully + + + epp-abc123 + server-xyz789 + + + +XML; + +$failureXml = <<<'XML' + + + + + Object does not exist + + + server-fail001 + + + +XML; + +test('success response is parsed correctly', function () use ($successXml) { + $response = new Response($successXml); + + expect($response->isSucceeded())->toBeTrue() + ->and($response->getCode())->toBe(1000) + ->and($response->getMessage())->toBe('Command completed successfully') + ->and($response->getServerTransaction())->toBe('server-xyz789') + ->and($response->getClientTransaction())->toBe('epp-abc123'); +}); + +test('failure response is parsed correctly', function () use ($failureXml) { + $response = new Response($failureXml); + + expect($response->isSucceeded())->toBeFalse() + ->and($response->getCode())->toBe(2303) + ->and($response->getMessage())->toBe('Object does not exist') + ->and($response->getServerTransaction())->toBe('server-fail001'); +}); + +test('clTRID is null when absent from server response', function () use ($failureXml) { + $response = new Response($failureXml); + + expect($response->getClientTransaction())->toBeNull(); +});