diff --git a/components/CHANGELOG.md b/components/CHANGELOG.md index 33e8bdb9..6cda7833 100644 --- a/components/CHANGELOG.md +++ b/components/CHANGELOG.md @@ -18,7 +18,8 @@ All Notable changes to `League\Uri\Components` will be documented in this file ### Fixed -- None +- `Modifier::getUriString` returns the result of calling `__tostring` on the underlying URI object being manipulated +- `Modifier` host related method return host in IDN form or ASCII form depending on the URI input format ### Deprecated diff --git a/components/Modifier.php b/components/Modifier.php index 092aa6ab..f71720af 100644 --- a/components/Modifier.php +++ b/components/Modifier.php @@ -73,7 +73,7 @@ public function getUri(): Psr7UriInterface|UriInterface public function getUriString(): string { - return $this->toString(); + return $this->uri->__toString(); } public function jsonSerialize(): string @@ -88,7 +88,7 @@ public function __toString(): string public function toString(): string { - return $this->uri->__toString(); + return ($this->uri instanceof UriRenderer) ? $this->uri->toString() : Uri::new($this->uri)->toString(); } public function toDisplayString(): string @@ -156,10 +156,9 @@ final public function when(callable|bool $condition, callable $onSuccess, ?calla */ public function encodeQuery(KeyValuePairConverter|int $to, KeyValuePairConverter|int|null $from = null): static { - $to = match (true) { - !$to instanceof KeyValuePairConverter => KeyValuePairConverter::fromEncodingType($to), - default => $to, - }; + if (!$to instanceof KeyValuePairConverter) { + $to = KeyValuePairConverter::fromEncodingType($to); + } $from = match (true) { null === $from => KeyValuePairConverter::fromRFC3986(), @@ -172,14 +171,17 @@ public function encodeQuery(KeyValuePairConverter|int $to, KeyValuePairConverter } $originalQuery = $this->uri->getQuery(); + if (null === $originalQuery || '' === trim($originalQuery)) { + return $this; + } + + /** @var string $query */ $query = QueryString::buildFromPairs(QueryString::parseFromValue($originalQuery, $from), $to); + if ($query === $originalQuery) { + return $this; + } - return match (true) { - null === $query, - '' === $query, - $originalQuery === $query => $this, - default => new static($this->uri->withQuery($query)), - }; + return new static($this->uri->withQuery($query)); } /** @@ -410,15 +412,28 @@ public function addRootLabel(): static */ public function appendLabel(Stringable|string|null $label): static { - $host = Host::fromUri($this->uri); + $host = $this->uri->getHost(); + $isAsciiDomain = null === $host || IdnaConverter::toAscii($host)->domain() === $host; + + $host = Host::new($host); $label = Host::new($label); - return match (true) { - null === $label->value() => $this, - $host->isDomain() => new static($this->uri->withHost(static::normalizeComponent(Domain::new($host)->append($label)->toUnicode(), $this->uri))), - $host->isIpv4() => new static($this->uri->withHost($host->value().'.'.ltrim($label->value(), '.'))), - default => throw new SyntaxError('The URI host '.$host->toString().' cannot be appended.'), - }; + if (null === $label->value()) { + return $this; + } + + if ($host->isIpv4()) { + return new static($this->uri->withHost($host->value().'.'.ltrim($label->value(), '.'))); + } + + if (!$host->isDomain()) { + throw new SyntaxError('The URI host '.$host->toString().' cannot be appended.'); + } + + $newHost = Domain::new($host)->append($label); + $newHost = !$isAsciiDomain ? $newHost->toUnicode() : $newHost->toAscii(); + + return new static($this->uri->withHost(static::normalizeComponent($newHost, $this->uri))); } /** @@ -534,15 +549,28 @@ public function hostToIpv6Expanded(): static */ public function prependLabel(Stringable|string|null $label): static { - $host = Host::fromUri($this->uri); + $host = $this->uri->getHost(); + $isAsciiDomain = null === $host || IdnaConverter::toAscii($host)->domain() === $host; + + $host = Host::new($host); $label = Host::new($label); - return match (true) { - null === $label->value() => $this, - $host->isIpv4() => new static($this->uri->withHost(rtrim($label->value(), '.').'.'.$host->value())), - $host->isDomain() => new static($this->uri->withHost(static::normalizeComponent(Domain::new($host)->prepend($label)->toUnicode(), $this->uri))), - default => throw new SyntaxError('The URI host '.$host->toString().' cannot be prepended.'), - }; + if (null === $label->value()) { + return $this; + } + + if ($host->isIpv4()) { + return new static($this->uri->withHost(rtrim($label->value(), '.').'.'.$host->value())); + } + + if (!$host->isDomain()) { + throw new SyntaxError('The URI host '.$host->toString().' cannot be prepended.'); + } + + $newHost = Domain::new($host)->prepend($label); + $newHost = !$isAsciiDomain ? $newHost->toUnicode() : $newHost->toAscii(); + + return new static($this->uri->withHost(static::normalizeComponent($newHost, $this->uri))); } /** @@ -550,12 +578,16 @@ public function prependLabel(Stringable|string|null $label): static */ public function removeLabels(int ...$keys): static { - return new static($this->uri->withHost( - static::normalizeComponent( - Domain::fromUri($this->uri)->withoutLabel(...$keys)->toUnicode(), - $this->uri - ) - )); + $host = $this->uri->getHost(); + if (null === $host || ('' === $host && $this->uri instanceof Psr7UriInterface)) { + return $this; + } + + $isAsciiDomain = IdnaConverter::toAscii($host)->domain() === $host; + $newHost = Domain::new($host)->withoutLabel(...$keys); + $newHost = !$isAsciiDomain ? $newHost->toUnicode() : $newHost->toAscii(); + + return new static($this->uri->withHost(static::normalizeComponent($newHost, $this->uri))); } /** @@ -579,13 +611,19 @@ public function removeRootLabel(): static public function sliceLabels(int $offset, ?int $length = null): static { $currentHost = $this->uri->getHost(); + if (null === $currentHost || ('' === $currentHost && $this->uri instanceof Psr7UriInterface)) { + return $this; + } + + $isAsciiDomain = IdnaConverter::toAscii($currentHost)->domain() === $currentHost; $host = Domain::new($currentHost)->slice($offset, $length); + $host = !$isAsciiDomain ? $host->toUnicode() : $host->toAscii(); - return match (true) { - $host->value() === $currentHost, - $host->toUnicode() === $currentHost => $this, - default => new static($this->uri->withHost($host->toUnicode())), - }; + if ($currentHost === $host) { + return $this; + } + + return new static($this->uri->withHost($host)); } /** @@ -598,7 +636,7 @@ public function removeZoneId(): static return match (true) { $host->hasZoneIdentifier() => new static($this->uri->withHost( static::normalizeComponent( - Host::fromUri($this->uri)->withoutZoneIdentifier()->value(), + $host->withoutZoneIdentifier()->value(), $this->uri ) )), @@ -611,12 +649,12 @@ public function removeZoneId(): static */ public function replaceLabel(int $offset, Stringable|string|null $label): static { - return new static($this->uri->withHost( - static::normalizeComponent( - Domain::fromUri($this->uri)->withLabel($offset, $label)->toUnicode(), - $this->uri - ) - )); + $host = $this->uri->getHost(); + $isAsciiDomain = null === $host || IdnaConverter::toAscii($host)->domain() === $host; + $newHost = Domain::new($host)->withLabel($offset, $label); + $newHost = !$isAsciiDomain ? $newHost->toUnicode() : $newHost->toAscii(); + + return new static($this->uri->withHost(static::normalizeComponent($newHost, $this->uri))); } public function whatwgHost(): static diff --git a/components/ModifierTest.php b/components/ModifierTest.php index a776a838..8bd6c3b2 100644 --- a/components/ModifierTest.php +++ b/components/ModifierTest.php @@ -436,9 +436,9 @@ public function testModifyingTheHostKeepHostUnicode(): void $modifier = Modifier::from(Utils::uriFor('http://shop.bebe.be')); - self::assertSame('http://bébé.bebe.be', $modifier->replaceLabel(-1, 'bébé')->getUriString()); - self::assertSame('http://bébé.shop.bebe.be', $modifier->prependLabel('bébé')->getUriString()); - self::assertSame('http://shop.bebe.be.bébé', $modifier->appendLabel('bébé')->getUriString()); + self::assertSame('http://xn--bb-bjab.bebe.be', $modifier->replaceLabel(-1, 'bébé')->getUriString()); + self::assertSame('http://xn--bb-bjab.shop.bebe.be', $modifier->prependLabel('bébé')->getUriString()); + self::assertSame('http://shop.bebe.be.xn--bb-bjab', $modifier->appendLabel('bébé')->getUriString()); self::assertSame('http://shop.bebe.be', $modifier->hostToAscii()->getUriString()); self::assertSame('http://shop.bebe.be', $modifier->hostToUnicode()->getUriString()); } @@ -469,7 +469,7 @@ public function testItCanConvertHostToUnicode(): void self::assertSame('http://xn--bb-bjab.be', $uri); self::assertSame('http://xn--bb-bjab.be', (string) $modifier); - self::assertSame($uriString, (string) $modifier->hostToUnicode()); + self::assertSame($uriString, $modifier->hostToUnicode()->getUriString()); } public function testICanNormalizeIPv4HostToDecimal(): void diff --git a/docs/components/7.0/modifiers.md b/docs/components/7.0/modifiers.md index 184508f5..3f8185aa 100644 --- a/docs/components/7.0/modifiers.md +++ b/docs/components/7.0/modifiers.md @@ -73,46 +73,43 @@ echo $uri::class; // returns GuzzleHttp\Psr7\Uri echo $uri, PHP_EOL; // returns http://shop.bébé.be./toto?foo=toto&foo=tata ~~~ +### Returned URI object and string representations +

While the class does manipulate URI it does not implement any URI related interface.

-

If a PSR-7 or a League UriInterface implementing instance is given -then the return value will also be a PSR-7 UriInterface implementing instance.

-

The getIdnUriString method is available since version 7.5.0.

+

If an UriInterface implementing instance is given, then the returned URI object will also be of the same UriInterface type.

-The `Modifier::getUri` method returns either a `PSR-7` or a League URI `UriInterface`, conversely, -the `Modifier::getUriString` method returns the RFC3986 string representation for the URI and -the `Modifier::getIdnUriString` method returns the RFC3986 string representation for the URI -with a Internationalized Domain Name (IDNA) if applicable. Last but not least, the class -implements the `Stringable` and the `JsonSerializable` interface to improve developer experience. +The `Modifier` can return different URI results depending on the context and your usage. -Under the hood the `Modifier` class intensively uses the [URI components objects](/components/7.0/) -to apply changes to the submitted URI object. +The `Modifier::getUri` method returns a League URI `UriInterface` unless you instantiated the modifier +with a `PSR-7` Uri object in which case a `PSR-7` Uri object of the same type is returned. If you +are not interested in the returned URI but only on its underlying string representation, you can instead use +the `Modifier::getUriString` which is a shortcut to `Modifier::getUri->__toString()`. -

The when, toString and toDisplayString methods are available since version 7.6.0

+

Available since version 7.6.0 -To ease modifying URI since version 7.6.0 you can directly access the modifier methods from the underlying -URI object. The methods behave as their owner so T +the `Modifier::toString` method returns the **strict** RFC3986 string representation of the URI regardless of the underlying URI object string representation. +This is the representation used by the `Stringable` and the `JsonSerializable` interface to improve interoperability. -```php -use League\Uri\Modifier; +The `Modifier::toDisplayString` method returns a RFC3987 like string representation which is more suited for +displaying the URI and should not be used to interact with an API as the produced URI may not be RFC3986 compliant +at all. -$foo = ''; -echo Modifier::from('http://bébé.be') - ->when( - '' !== $foo, - fn (Modifier $uri) => $uri->withQuery('fname=jane&lname=Doe'), //on true - fn (Modifier $uri) => $uri->mergeQueryParameters(['fname' => 'john', 'lname' => 'Doe']), //on false - ) - ->appendSegment('toto') - ->addRootLabel() - ->prependLabel('shop') - ->appendQuery('foo=toto&foo=tata') - ->withFragment('chapter1') - ->toDisplayString(); -// returns 'http://shop.bébé.be./toto?fname=john&lname=Doe&foo=toto&foo=tata#chapter1'; +```php +use GuzzleHttp\Psr7\Utils; + +$uri = Modifier::from(Utils::uriFor('https://bébé.be?foo[]=bar'))->prepend('shop'); +$uri->getUri()::class; // returns 'GuzzleHttp\Psr7\Uri' +$uri->getUri()->__toString(); // returns 'https://shop.bébé.be?foo%5B%5D=bar' +$uri->getUriString(); // returns 'https://shop.bébé.be?foo%5B%5D=bar' +$uri->toString(); // returns 'https://shop.xn--bb-bjab.be?foo%5B%5D=bar' +$uri->toDisplayString(); // returns 'https://shop.bébé.be?foo[]=bar' ``` ### Available modifiers +Under the hood the `Modifier` class intensively uses the [URI components objects](/components/7.0/) +to apply the following changes to the submitted URI. +