Skip to content

Commit b8cb78e

Browse files
committed
Improve package after review
- simplify domain validation - improve Rules::resolve and Rules::getPublicSuffix argument signature - added DomainInterface::__toString method - Documentation updated
1 parent 0589e56 commit b8cb78e

12 files changed

+153
-73
lines changed

CHANGELOG.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ All Notable changes to `PHP Domain Parser` **5.x** series will be documented in
88

99
- `Pdp\PublicSuffixListSection` interface implemented by `Pdp\Rules` and `Pdp\PublicSuffix`
1010
- `Pdp\DomainInterface` interface implemented by `Pdp\Domain` and `Pdp\PublicSuffix`
11-
- `Pdp\Domain` implements the `Countable` interface.
12-
- `Pdp\Domain::getContent` returns the Domain name value replaces `Pdp\Domain::getDomain`
11+
- `Pdp\Domain::getContent` replaces `Pdp\Domain::getDomain`
1312
- `Pdp\Domain::withPublicSuffix` updates the `Pdp\Domain` public suffix part.
1413
- `Pdp\Domain::withSubDomain` updates the `Pdp\Domain` sub domain part.
1514
- `Pdp\Domain::withLabel` adds a new label to the `Pdp\Domain`.
1615
- `Pdp\Domain::withoutLabel` removes a label from the `Pdp\Domain`.
1716
- `Pdp\Domain::resolve` attach a public suffix to the `Pdp\Domain`.
18-
- `Pdp\Domain::isResolvable` tell whether the current `Pdp\Domain` can have a public suffix attached to it or not.
17+
- `Pdp\Domain::isResolvable` tells whether the current `Pdp\Domain` can have a public suffix attached to it or not.
1918
- `Pdp\PublicSuffix::createFromDomain` returns a new `Pdp\PublicSuffix` object from a `Pdp\Domain`object
2019

2120
### Fixed
2221

2322
- `Pdp\Domain` domain part computation (public suffix, registrable domain and sub domain)
2423
- `Pdp\Domain` and `Pdp\PublicSuffix` host validation compliance to RFC improved
25-
- Improved `Pdp\Converter` and `Pdp\Manager` class to better report error on IDN conversion.
24+
- Improve `Pdp\Converter` and `Pdp\Manager` class to better report error on IDN conversion.
25+
- Improve `Pdp\Installer` vendor directory resolution see [PR #222](https://github.com/jeremykendall/php-domain-parser/pull/222)
2626

2727
### Deprecated
2828

2929
- `Pdp\Domain::getDomain` use instead `Pdp\Domain::getContent`
30-
- `Pdp\PublicSuffixListSection::ALL_DOMAINS` use the empty string instead
30+
- `Pdp\Rules::ALL_DOMAINS` use the empty string instead
3131

3232
### Removed
3333

README.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ Using the above code you can parse and get public suffix informations about any
8888
The `Pdp\Domain` returned by the `Pdp\Rules::resolve` method is an immutable value object representing a valid domain name. This object let's you access all the domain name properties as well as the public suffix informations attached to it using the following methods.
8989

9090
~~~php
91-
public function Domain::getLabel(int $key): ?string
92-
public function Domain::keys(?string $label): int[]
91+
public function Domain::__toString(): string
9392
public function Domain::getContent(): ?string
9493
public function Domain::getPublicSuffix(): ?string
9594
public function Domain::getRegistrableDomain(): ?string
9695
public function Domain::getSubDomain(); ?string
96+
public function Domain::getLabel(int $key): ?string
97+
public function Domain::keys(?string $label): int[]
9798
public function Domain::isResolvable(): bool;
9899
public function Domain::isKnown(): bool;
99100
public function Domain::isICANN(): bool;
@@ -154,17 +155,37 @@ $newDomain->isKnown(); //return true;
154155

155156
### Getting the domain public suffix information.
156157

157-
The `Pdp\Rules` object is responsible for public suffix resolution for a given domain. Public suffix resolution is done using the `Pdp\Rules::resolve` or `Pdp\Rules::getPublicSuffix` methods which expects at most two parameters:
158+
~~~php
159+
<?php
160+
161+
namespace Pdp;
162+
163+
final class Rules
164+
{
165+
public static function createFromPath(string $path, $context = null): Rules
166+
public static function createFromString(string $content): Rules
167+
public function __construct(array $rules)
168+
public function resolve($domain, $section = ''): Domain
169+
public function getPublicSuffix($domain, $section = ''): PublicSuffix
170+
}
171+
~~~
172+
173+
The `Pdp\Rules` object is responsible for public suffix resolution for a given domain. Public suffix resolution is done using:
174+
175+
- the `Pdp\Rules::resolve` method which returns a `Pdp\Domain` object;
176+
- the `Pdp\Rules::getPublicSuffix` methods which returns a `Pdp\PublicSuffix` object;
177+
178+
Both methods expect the same arguments:
158179

159-
- `$domain` a domain name as a string
180+
- `$domain` a domain name
160181
- `$section` a string which specifies which section of the PSL you want to validate the given domain against. The possible values are:
161182
- `Rules::ICANN_DOMAINS`, to validate against the PSL ICANN DOMAINS section only.
162183
- `Rules::PRIVATE_DOMAINS`, to validate against the PSL PRIVATE DOMAINS section only.
163184
- the empty string to validate against all the PSL sections.
164185

165186
By default, the `$section` argument is equal to the empty string. If an unsupported section is submitted a `Pdp\Exception` exception will be thrown.
166187

167-
While the `Pdp\Rules::resolve` returns a `Pdp\Domain` object, the `Pdp\Rules::getPublicSuffix` returns a `Pdp\PublicSuffix` object.
188+
The `Pdp\PublicSuffix` object exposes the same methods as the `Pdp\Domain` object minus all the modifying methods, `toAscii` and `toUnicode` excluded.
168189

169190
**THIS EXAMPLE ILLUSTRATES HOW THE OBJECT WORK BUT SHOULD BE AVOIDED IN PRODUCTON**
170191

@@ -178,6 +199,7 @@ $pdp_url = 'https://raw.githubusercontent.com/publicsuffix/list/master/public_su
178199
$rules = Rules::createFromPath($pdp_url);
179200

180201
$domain = $rules->resolve('www.Ulb.AC.be'); // resolution is done against all the sections available
202+
echo $domain; // returns www.ulb.ac.be
181203
echo json_encode($domain, JSON_PRETTY_PRINT);
182204
// returns
183205
// {
@@ -190,9 +212,9 @@ echo json_encode($domain, JSON_PRETTY_PRINT);
190212
// "isPrivate": false
191213
// }
192214

193-
//The same domain will yield a different result using the PSL PRIVATE DOMAIN SECTION only
215+
//The same domain will yield a different result using the PSL ICANN DOMAIN SECTION only
194216

195-
$domain = $rules->resolve('www.Ulb.AC.be', Rules::PRIVATE_DOMAINS);
217+
$domain = $rules->resolve('www.Ulb.AC.be', Rules::ICANN_DOMAINS);
196218
echo json_encode($domain, JSON_PRETTY_PRINT);
197219
// returns
198220
// {

data/pdp-PSL_FULL_5a3cc7f81795bb2e48e848af42d287b4.cache

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/Domain.php

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ public static function __set_state(array $properties): self
7272
*/
7373
public function __construct($domain = null, PublicSuffix $publicSuffix = null)
7474
{
75-
list($this->domain, $this->labels) = $this->setDomain($domain);
75+
$this->labels = $this->setLabels($domain);
76+
if (!empty($this->labels)) {
77+
$this->domain = implode('.', array_reverse($this->labels));
78+
}
7679
$this->publicSuffix = $this->setPublicSuffix($publicSuffix ?? new PublicSuffix());
7780
$this->registrableDomain = $this->setRegistrableDomain();
7881
$this->subDomain = $this->setSubDomain();
@@ -92,7 +95,7 @@ public function __construct($domain = null, PublicSuffix $publicSuffix = null)
9295
private function setPublicSuffix(PublicSuffix $publicSuffix): PublicSuffix
9396
{
9497
if (null === $publicSuffix->getContent()) {
95-
return new PublicSuffix();
98+
return $publicSuffix;
9699
}
97100

98101
if (!$this->isResolvable()) {
@@ -115,11 +118,11 @@ private function setPublicSuffix(PublicSuffix $publicSuffix): PublicSuffix
115118
/**
116119
* Normalize the domain name encoding content.
117120
*
118-
* @param mixed $subject
121+
* @param PublicSuffix $subject
119122
*
120-
* @return mixed
123+
* @return PublicSuffix
121124
*/
122-
private function normalize($subject)
125+
private function normalize(PublicSuffix $subject): PublicSuffix
123126
{
124127
if (null === $this->domain || null === $subject->getContent()) {
125128
return $subject;
@@ -135,6 +138,8 @@ private function normalize($subject)
135138

136139
/**
137140
* Computes the registrable domain part.
141+
*
142+
* @return string|null
138143
*/
139144
private function setRegistrableDomain()
140145
{
@@ -222,6 +227,14 @@ public function getContent()
222227
return $this->domain;
223228
}
224229

230+
/**
231+
* {@inheritdoc}
232+
*/
233+
public function __toString()
234+
{
235+
return (string) $this->domain;
236+
}
237+
225238
/**
226239
* Returns the full domain name.
227240
*
@@ -301,11 +314,16 @@ public function getPublicSuffix()
301314
/**
302315
* Tells whether the given domain can be resolved.
303316
*
317+
* A domain is resolvable if:
318+
* - it contains at least 2 labels
319+
* - it is not a absolute domain (end with a '.' character)
320+
*
304321
* @return bool
305322
*/
306323
public function isResolvable(): bool
307324
{
308-
return 2 <= count($this->labels);
325+
return 2 <= count($this->labels)
326+
&& '.' !== substr($this->domain, -1, 1);
309327
}
310328

311329
/**
@@ -506,13 +524,14 @@ public function withPublicSuffix($publicSuffix): self
506524
public function withLabel(int $key, $label): self
507525
{
508526
if (!$label instanceof PublicSuffix) {
509-
$label = new PublicSuffix($label);
527+
$label = $this->normalize(new PublicSuffix($label));
510528
}
511529

512-
if (1 !== count($label)) {
513-
throw new Exception(sprintf('The label `%s` is invalid', $label->getContent()));
530+
if (1 != count($label)) {
531+
throw new Exception(sprintf('The label `%s` is invalid', (string) $label));
514532
}
515533

534+
$label = (string) $label;
516535
$nb_labels = count($this->labels);
517536
$offset = filter_var($key, FILTER_VALIDATE_INT, ['options' => ['min_range' => - $nb_labels - 1, 'max_range' => $nb_labels]]);
518537
if (false === $offset) {
@@ -523,15 +542,16 @@ public function withLabel(int $key, $label): self
523542
$offset = $nb_labels + $offset;
524543
}
525544

526-
$label = $this->normalize($label)->getContent();
527545
if ($label === ($this->labels[$offset] ?? null)) {
528546
return $this;
529547
}
530548

549+
$labels = $this->labels;
550+
$labels[$offset] = $label;
551+
ksort($labels);
552+
531553
$clone = clone $this;
532-
$clone->labels[$offset] = $label;
533-
ksort($clone->labels);
534-
$clone->labels = array_values($clone->labels);
554+
$clone->labels = array_values($labels);
535555
$clone->domain = implode('.', array_reverse($clone->labels));
536556
if (null !== $this->publicSuffix->getLabel($offset)) {
537557
$clone->publicSuffix = new PublicSuffix();

src/DomainInterface.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ interface DomainInterface extends Countable, IteratorAggregate
3232
*/
3333
public function getContent();
3434

35+
/**
36+
* Returns the domain content as a string.
37+
*
38+
* @return string
39+
*/
40+
public function __toString();
41+
3542
/**
3643
* Retrieves a single domain label.
3744
*
@@ -81,4 +88,13 @@ public function toAscii();
8188
* @return static
8289
*/
8390
public function toUnicode();
91+
92+
93+
/**
94+
* {@inheritdoc}
95+
*
96+
* The external iterator iterates over the DomainInterface labels
97+
* from the right-most label to the left-most label.
98+
*/
99+
public function getIterator();
84100
}

src/IDNAConverterTrait.php

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,26 +113,26 @@ private function idnToUnicode(string $domain): string
113113
* Returns an array containing the formatted domain name in lowercase
114114
* with its associated labels in reverse order
115115
*
116-
* For example: setDomain('wWw.uLb.Ac.be') should return ['www.ulb.ac.be', ['be', 'ac', 'ulb', 'www']];
116+
* For example: setLabels('wWw.uLb.Ac.be') should return ['www.ulb.ac.be', ['be', 'ac', 'ulb', 'www']];
117117
*
118118
* @param mixed $domain
119119
*
120120
* @throws Exception If the domain is invalid
121121
*
122-
* @return array
122+
* @return string[]
123123
*/
124-
private function setDomain($domain = null): array
124+
private function setLabels($domain = null): array
125125
{
126126
if ($domain instanceof DomainInterface) {
127-
return [$domain->getContent(), iterator_to_array($domain, false)];
127+
return iterator_to_array($domain, false);
128128
}
129129

130130
if (null === $domain) {
131-
return [$domain, []];
131+
return [];
132132
}
133133

134134
if ('' === $domain) {
135-
return [$domain, ['']];
135+
return [''];
136136
}
137137

138138
if (!is_scalar($domain) && !method_exists($domain, '__toString')) {
@@ -155,9 +155,7 @@ private function setDomain($domain = null): array
155155
)
156156
^(?:(?&reg_name)\.){0,126}(?&reg_name)\.?$/ix';
157157
if (preg_match($domain_name, $formatted_domain)) {
158-
$domain = strtolower($formatted_domain);
159-
160-
return [$domain, array_reverse(explode('.', $domain))];
158+
return array_reverse(explode('.', strtolower($formatted_domain)));
161159
}
162160

163161
// a domain name can not contains URI delimiters or space
@@ -175,9 +173,7 @@ private function setDomain($domain = null): array
175173
//if a domain name contains UTF-8 chars it must be convertible using IDNA UTS46
176174
$ascii_domain = idn_to_ascii($formatted_domain, 0, INTL_IDNA_VARIANT_UTS46, $arr);
177175
if (0 === $arr['errors']) {
178-
$idn_domain = $this->idnToUnicode($ascii_domain);
179-
180-
return [$idn_domain, array_reverse(explode('.', $idn_domain))];
176+
return array_reverse(explode('.', $this->idnToUnicode($ascii_domain)));
181177
}
182178

183179
throw new Exception(sprintf('The domain `%s` is invalid : %s', $domain, self::getIdnErrors($arr['errors'])));

src/Installer.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,7 @@ private static function getVendorPath(Event $event = null)
105105
*/
106106
private static function getIO(Event $event = null)
107107
{
108-
if (null !== $event) {
109-
return $event->getIO();
110-
}
111-
112-
return new class() {
108+
return null !== $event ? $event->getIO() : new class() {
113109
public function write($messages, bool $newline = true, int $verbosity = 2)
114110
{
115111
$this->doWrite($messages, $newline, false, $verbosity);

src/PublicSuffix.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,11 @@ public static function createFromDomain(Domain $domain): self
8181
*/
8282
public function __construct($publicSuffix = null, string $section = '')
8383
{
84-
list($this->publicSuffix, $this->labels) = $this->setDomain($publicSuffix);
84+
$this->labels = $this->setLabels($publicSuffix);
85+
if (!empty($this->labels)) {
86+
$this->publicSuffix = implode('.', array_reverse($this->labels));
87+
}
88+
8589
$this->section = $this->setSection($section);
8690
}
8791

@@ -155,6 +159,14 @@ public function getContent()
155159
return $this->publicSuffix;
156160
}
157161

162+
/**
163+
* {@inheritdoc}
164+
*/
165+
public function __toString()
166+
{
167+
return (string) $this->publicSuffix;
168+
}
169+
158170
/**
159171
* {@inheritdoc}
160172
*/

src/Rules.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function __construct(array $rules)
103103
*
104104
* @return PublicSuffix
105105
*/
106-
public function getPublicSuffix($domain = null, string $section = self::ALL_DOMAINS): PublicSuffix
106+
public function getPublicSuffix($domain, string $section = self::ALL_DOMAINS): PublicSuffix
107107
{
108108
$section = $this->validateSection($section);
109109
$domain = $domain instanceof Domain ? $domain : new Domain($domain);
@@ -122,7 +122,7 @@ public function getPublicSuffix($domain = null, string $section = self::ALL_DOMA
122122
*
123123
* @return Domain
124124
*/
125-
public function resolve($domain = null, string $section = self::ALL_DOMAINS): Domain
125+
public function resolve($domain, string $section = self::ALL_DOMAINS): Domain
126126
{
127127
$section = $this->validateSection($section);
128128
try {

0 commit comments

Comments
 (0)