Files
Epicnabbo-Catalogus-Updated…/Optimized Atomcms daily updated/vendor/league/uri-components/Modifier.php
T
2025-11-25 22:42:56 +01:00

1331 lines
43 KiB
PHP

<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri;
use Deprecated;
use Dom\HTMLDocument;
use DOMDocument;
use DOMException;
use JsonSerializable;
use League\Uri\Components\DataPath;
use League\Uri\Components\Domain;
use League\Uri\Components\Fragment;
use League\Uri\Components\FragmentDirectives;
use League\Uri\Components\HierarchicalPath;
use League\Uri\Components\Host;
use League\Uri\Components\Path;
use League\Uri\Components\Query;
use League\Uri\Components\URLSearchParams;
use League\Uri\Contracts\Conditionable;
use League\Uri\Contracts\FragmentDirective;
use League\Uri\Contracts\FragmentInterface;
use League\Uri\Contracts\PathInterface;
use League\Uri\Contracts\UriAccess;
use League\Uri\Contracts\UriInterface;
use League\Uri\Exceptions\MissingFeature;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\Idna\Converter as IdnaConverter;
use League\Uri\IPv4\Converter as IPv4Converter;
use League\Uri\IPv6\Converter as IPv6Converter;
use League\Uri\KeyValuePair\Converter as KeyValuePairConverter;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface as Psr7UriInterface;
use Stringable;
use Uri\Rfc3986\Uri as Rfc3986Uri;
use Uri\WhatWg\Url as WhatWgUrl;
use ValueError;
use function class_exists;
use function filter_var;
use function implode;
use function in_array;
use function is_array;
use function is_bool;
use function is_string;
use function ltrim;
use function rtrim;
use function str_ends_with;
use function str_starts_with;
use function strpos;
use function strtolower;
use function substr;
use function trim;
use const FILTER_FLAG_IPV4;
use const FILTER_VALIDATE_IP;
class Modifier implements Stringable, JsonSerializable, UriAccess, Conditionable
{
final public function __construct(protected readonly Rfc3986Uri|WhatWgUrl|Psr7UriInterface|UriInterface $uri)
{
}
public static function wrap(Rfc3986Uri|WhatWgUrl|Stringable|string $uri): static
{
return new static(match (true) {
$uri instanceof self => $uri->uri,
$uri instanceof Psr7UriInterface,
$uri instanceof UriInterface,
$uri instanceof Rfc3986Uri,
$uri instanceof WhatWgUrl => $uri,
default => Uri::new($uri),
});
}
public function unwrap(): Rfc3986Uri|WhatWgUrl|Psr7UriInterface|UriInterface
{
return $this->uri;
}
public function jsonSerialize(): string
{
return $this->toString();
}
public function __toString(): string
{
return $this->toString();
}
public function toString(): string
{
return match (true) {
$this->uri instanceof Rfc3986Uri,
$this->uri instanceof UriInterface => $this->uri->toString(),
$this->uri instanceof WhatWgUrl => $this->uri->toAsciiString(),
$this->uri instanceof Psr7UriInterface => $this->uri->__toString(),
};
}
public function toDisplayString(): string
{
return ($this->uri instanceof Uri ? $this->uri : Uri::new($this->toString()))->toDisplayString();
}
/**
* Returns the Markdown string representation of the anchor tag with the current instance as its href attribute.
*/
public function toMarkdownAnchor(?string $textContent = null): string
{
return '['.strtr($textContent ?? '{uri}', ['{uri}' => $this->toDisplayString()]).']('.$this->toString().')';
}
/**
* Returns the HTML string representation of the anchor tag with the current instance as its href attribute.
*
* @param iterable<string, string|null|array<string>> $attributes an ordered map of key value. you must quote the value if needed
*
* @throws DOMException
*/
public function toHtmlAnchor(Stringable|string|null $textContent = null, iterable $attributes = []): string
{
FeatureDetection::supportsDom();
$uriString = $this->toString();
$rfc3987String = UriString::toIriString($uriString);
$doc = class_exists(HTMLDocument::class) ? HTMLDocument::createEmpty() : new DOMDocument(encoding:'utf-8'); /* @phpstan-ignore-line */
$element = $doc->createElement('a');
$element->setAttribute('href', $uriString);
$element->appendChild(match (true) {
null === $textContent => $doc->createTextNode($rfc3987String),
default => $doc->createTextNode(strtr((string) $textContent, ['{uri}' => $rfc3987String])),
});
foreach ($attributes as $name => $value) {
if ('href' === strtolower($name) || null === $value) {
continue;
}
if (is_array($value)) {
$value = implode(' ', $value);
}
is_string($value) || throw new ValueError('The attribute `'.$name.'` contains an invalid value.');
$value = trim($value);
if ('' === $value) {
continue;
}
$element->setAttribute($name, $value);
}
false !== ($html = $doc->saveHTML($element)) || throw new DOMException('The HTML generation failed.');
return $html;
}
public function resolve(Rfc3986Uri|WhatWgUrl|Psr7UriInterface|UriInterface|Stringable|string $uri): static
{
$uriString = match (true) {
$uri instanceof Rfc3986Uri,
$uri instanceof UriInterface => $uri->toString(),
$uri instanceof WhatWgUrl => $uri->toAsciiString(),
default => (string) $uri,
};
if (!$this->uri instanceof Psr7UriInterface) {
return new static($this->uri->resolve($uriString));
}
$components = UriString::parse(UriString::resolve($uriString, $this->toString()));
return new static(
$this->uri
->withFragment($components['fragment'] ?? '')
->withQuery($components['query'] ?? '')
->withPath($components['path'] ?? '')
->withHost($components['host'] ?? '')
->withPort($components['port'] ?? null)
->withUserInfo($components['user'] ?? '', $components['pass'] ?? null)
->withScheme($components['scheme'] ?? '')
);
}
public function normalize(): static
{
if ($this->uri instanceof WhatWgUrl) {
return $this;
}
if ($this->uri instanceof Rfc3986Uri) {
return new static(new Rfc3986Uri($this->uri->toString()));
}
if ($this->uri instanceof UriInterface) {
return new static($this->uri->normalize());
}
$uri = Uri::new($this->uri->__toString())->normalize();
if ($uri->toString() === $this->uri->__toString()) {
return $this;
}
return new static(
$this->uri
->withPath($uri->getPath())
->withHost($uri->getHost() ?? '')
->withUserInfo($uri->getUsername() ?? '', $uri->getPassword())
);
}
public function withScheme(Stringable|string|null $scheme): static
{
return new static($this->uri->withScheme(self::normalizeComponent($scheme, $this->uri)));
}
public function withUserInfo(Stringable|string|null $username, Stringable|string|null $password): static
{
if ($this->uri instanceof Rfc3986Uri) {
$userInfo = Encoder::encodeUser($username);
if (null !== $password) {
$userInfo .= ':'.Encoder::encodePassword($password);
}
return new static($this->uri->withUserInfo($userInfo));
}
if ($this->uri instanceof WhatWgUrl) {
if (null !== $username) {
$username = (string) $username;
}
if (null !== $password) {
$password = (string) $password;
}
return new static($this->uri->withUsername($username)->withPassword($password));
}
if (null == $username && $this->uri instanceof Psr7UriInterface) {
$username = '';
}
return new static($this->uri->withUserInfo(
$username instanceof Stringable ? (string) $username : $username,
$password instanceof Stringable ? (string) $password : $password,
));
}
public function withQuery(Stringable|string|null $query): static
{
$query = self::normalizeComponent($query, $this->uri);
$query = match (true) {
$this->uri instanceof Rfc3986Uri => match (true) {
Encoder::isQueryEncoded($query) => $query,
default => Encoder::encodeQueryOrFragment($query),
},
$this->uri instanceof WhatWgUrl => URLSearchParams::new($query)->value(),
default => $query,
};
return match (true) {
$this->uri instanceof Rfc3986Uri && $query === $this->uri->getRawQuery(),
$query === $this->uri->getQuery() => $this,
default => new static($this->uri->withQuery($query)),
};
}
public function withHost(Stringable|string|null $host): static
{
$host = self::normalizeComponent($host, $this->uri);
if ($this->uri instanceof Rfc3986Uri) {
if (null !== $host) {
$host = IdnaConverter::toAscii($host)->domain();
}
}
return new static($this->uri->withHost($host));
}
public function withFragment(Stringable|string|null $fragment): static
{
if ($fragment instanceof FragmentDirective) {
$fragment = new FragmentDirectives($fragment);
}
if (!$fragment instanceof FragmentInterface) {
$fragment = str_starts_with((string) $fragment, FragmentDirectives::DELIMITER)
? FragmentDirectives::fromFragment($fragment)
: Fragment::new($fragment);
}
return new static($this->uri->withFragment(
$this->uri instanceof Psr7UriInterface
? $fragment->toString()
: $fragment->value()
));
}
public function withPort(?int $port): static
{
return new static($this->uri->withPort($port));
}
public function withPath(Stringable|string $path): static
{
$path = (string) $path;
if ($this->uri instanceof Rfc3986Uri) {
$path = Encoder::encodePath($path);
}
return new static(self::normalizePath($this->uri, Path::new($path)));
}
final public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static
{
if (!is_bool($condition)) {
$condition = $condition($this);
}
return match (true) {
$condition => $onSuccess($this),
null !== $onFail => $onFail($this),
default => $this,
} ?? $this;
}
/*********************************
* Query modifier methods
*********************************/
/**
* Change the encoding of the query.
*/
public function encodeQuery(KeyValuePairConverter|int $to, KeyValuePairConverter|int|null $from = null): static
{
if (!$to instanceof KeyValuePairConverter) {
$to = KeyValuePairConverter::fromEncodingType($to);
}
$from = match (true) {
null === $from => KeyValuePairConverter::fromRFC3986(),
!$from instanceof KeyValuePairConverter => KeyValuePairConverter::fromEncodingType($from),
default => $from,
};
if ($to == $from) {
return $this;
}
$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 $this->withQuery($query);
}
/**
* Sort the URI query by keys.
*/
public function sortQuery(): static
{
return $this->withQuery(Query::fromUri($this->uri)->sort()->value());
}
/**
* Add the new query data to the existing URI query.
*/
public function appendQuery(Stringable|string|null $query): static
{
return $this->withQuery(Query::fromUri($this->uri)->append($query)->value());
}
/**
* Merge query pairs with the existing URI query.
*
* @param iterable<int, array{0:string, 1:string|null}> $pairs
*/
public function appendQueryPairs(iterable $pairs, string $prefix = ''): self
{
return $this->appendQuery(Query::fromPairs($pairs, prefix: $prefix)->value());
}
public function prefixQueryPairs(string $prefix): self
{
return $this->withQuery(Query::fromPairs(Query::fromUri($this->uri), prefix: $prefix));
}
public function prefixQueryParameters(string $prefix): self
{
return $this->withQuery(Query::fromVariable(Query::fromUri($this->uri)->parameters(), prefix: $prefix));
}
/**
* Append PHP query parameters to the existing URI query.
*/
public function appendQueryParameters(object|array $parameters, string $prefix = ''): self
{
return $this->appendQuery(Query::fromVariable($parameters, prefix: $prefix)->value());
}
/**
* Prepend PHP query parameters to the existing URI query.
*/
public function prependQueryParameters(object|array $parameters, string $prefix = ''): self
{
return $this->withQuery(Query::fromVariable($parameters, prefix: $prefix)->append(Query::fromUri($this->uri)->value())->value());
}
public function replaceQueryParameter(string $name, mixed $value): self
{
return $this->withQuery(Query::fromUri($this->uri)->replaceParameter($name, $value)->value());
}
/**
* Merge a new query with the existing URI query.
*/
public function mergeQuery(Stringable|string|null $query): static
{
return $this->withQuery(Query::fromUri($this->uri)->merge($query)->value());
}
/**
* Merge query paris with the existing URI query.
*
* @param iterable<int, array{0:string, 1:string|null}> $pairs
*/
public function mergeQueryPairs(iterable $pairs, string $prefix = ''): self
{
$currentPairs = [...Query::fromUri($this->uri)->pairs()];
$pairs = [...$pairs];
return match (true) {
[] === $pairs,
$currentPairs === $pairs => $this,
default => $this->mergeQuery(Query::fromPairs($pairs, prefix: $prefix)->value()),
};
}
/**
* Merge PHP query parameters with the existing URI query.
*/
public function mergeQueryParameters(object|array $parameters, string $prefix = ''): self
{
return $this->withQuery(Query::fromUri($this->uri)->mergeParameters($parameters, prefix: $prefix)->value());
}
/**
* Remove query data according to their key name.
*/
public function removeQueryPairsByKey(string ...$keys): static
{
$query = Query::fromUri($this->uri);
$newQuery = $query->withoutPairByKey(...$keys)->value();
return match ($query->value()) {
$newQuery => $this,
default => $this->withQuery($newQuery),
};
}
/**
* Remove query pair according to their value.
*/
public function removeQueryPairsByValue(Stringable|string|int|float|bool|null ...$values): static
{
$query = Query::fromUri($this->uri);
$newQuery = $query->withoutPairByValue(...$values)->value();
return match ($query->value()) {
$newQuery => $this,
default => $this->withQuery($newQuery),
};
}
/**
* Remove query-pair according to their key/value name.
*/
public function removeQueryPairsByKeyValue(string $key, Stringable|string|int|bool|null $value): static
{
$query = Query::fromUri($this->uri);
$newQuery = $query->withoutPairByKeyValue($key, $value)->value();
return match ($newQuery) {
$query->value() => $this,
default => $this->withQuery($newQuery),
};
}
/**
* Remove query data according to their PHP parameter key name.
*/
public function removeQueryParameters(string ...$keys): static
{
$query = Query::fromUri($this->uri);
$newQuery = $query->withoutParameters(...$keys)->value();
return match ($newQuery) {
$query->value() => $this,
default => $this->withQuery($newQuery),
};
}
/**
* Remove empty pairs from the URL query component.
*
* A pair is considered empty if its name is the empty string
* and its value is either the empty string or the null value
*/
public function removeEmptyQueryPairs(): static
{
return $this->withQuery(Query::fromUri($this->uri)->withoutEmptyPairs()->value());
}
/**
* Returns an instance where numeric indices associated to PHP's array like key are removed.
*
* This method MUST retain the state of the current instance, and return
* an instance that contains the query component normalized so that numeric indexes
* are removed from the pair key value.
*
* ie.: toto[3]=bar[3]&foo=bar becomes toto[]=bar[3]&foo=bar
*/
public function removeQueryParameterIndices(): static
{
$query = Query::fromUri($this->uri);
$newQuery = $query->withoutNumericIndices()->value();
return match ($newQuery) {
$query->value() => $this,
default => $this->withQuery($newQuery),
};
}
public function replaceQueryPair(int $offset, string $key, Stringable|string|int|float|bool|null $value): static
{
return $this->withQuery(Query::fromUri($this->uri)->replace($offset, $key, $value)->value());
}
/*********************************
* Host modifier methods
*********************************/
/**
* Add the root label to the URI.
*/
public function addRootLabel(): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
return match (true) {
null === $host,
str_ends_with($host, '.') => $this,
default => $this->withHost($host.'.'),
};
}
/**
* Append a label or a host to the current URI host.
*
* @throws SyntaxError If the host cannot be appended
*/
public function appendLabel(Stringable|string|null $label): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$isAsciiDomain = null === $host || IdnaConverter::toAscii($host)->domain() === $host;
$host = Host::new($host);
$label = Host::new($label);
if (null === $label->value()) {
return $this;
}
if ($host->isIpv4()) {
return $this->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 $this->withHost($newHost);
}
/**
* Convert the URI host part to its ASCII value.
*/
public function hostToAscii(): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$host = IdnaConverter::toAsciiOrFail((string) $currentHost);
return match (true) {
null === $currentHost,
'' === $currentHost,
$host === $currentHost => $this,
default => $this->withHost($host),
};
}
/**
* Convert the URI host part to its Unicode value.
*/
public function hostToUnicode(): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$host = IdnaConverter::toUnicode((string) $currentHost)->domain();
return match (true) {
null === $currentHost,
'' === $currentHost,
$host === $currentHost => $this,
default => $this->withHost($host),
};
}
/**
* Normalizes the URI host content to an IPv4 dot-decimal notation if possible
* otherwise returns the uri instance unchanged.
*
* @see https://url.spec.whatwg.org/#concept-ipv4-parser
*/
public function hostToDecimal(): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$hostIp = self::ipv4Converter()->toDecimal($currentHost);
return match (true) {
null === $currentHost,
'' === $currentHost,
null === $hostIp,
$currentHost === $hostIp => $this,
default => $this->withHost($hostIp),
};
}
/**
* Normalizes the URI host content to a IPv4 octal notation if possible
* otherwise returns the uri instance unchanged.
*
* @see https://url.spec.whatwg.org/#concept-ipv4-parser
*/
public function hostToOctal(): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$hostIp = self::ipv4Converter()->toOctal($currentHost);
return match (true) {
null === $currentHost,
'' === $currentHost,
null === $hostIp,
$currentHost === $hostIp => $this,
default => $this->withHost($hostIp),
};
}
/**
* Normalizes the URI host content to a IPv4 octal notation if possible
* otherwise returns the uri instance unchanged.
*
* @see https://url.spec.whatwg.org/#concept-ipv4-parser
*/
public function hostToHexadecimal(): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$hostIp = self::ipv4Converter()->toHexadecimal($currentHost);
return match (true) {
null === $currentHost,
'' === $currentHost,
null === $hostIp,
$currentHost === $hostIp => $this,
default => $this->withHost($hostIp),
};
}
public function hostToIpv6Compressed(): static
{
return $this->withHost(IPv6Converter::compress($this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost()));
}
public function hostToIpv6Expanded(): static
{
return $this->withHost(IPv6Converter::expand($this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost()));
}
/**
* Prepend a label or a host to the current URI host.
*
* @throws SyntaxError If the host cannot be prepended
*/
public function prependLabel(Stringable|string|null $label): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
$isAsciiDomain = null === $host || IdnaConverter::toAscii($host)->domain() === $host;
$host = Host::new($host);
$label = Host::new($label);
if (null === $label->value()) {
return $this;
}
if ($host->isIpv4()) {
return $this->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 $this->withHost($newHost);
}
/**
* Remove host labels according to their offset.
*/
public function removeLabels(int ...$keys): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $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 $this->withHost($newHost);
}
/**
* Remove the root label to the URI.
*/
public function removeRootLabel(): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
return match (true) {
null === $host,
'' === $host,
!str_ends_with($host, '.') => $this,
default => $this->withHost(substr($host, 0, -1)),
};
}
/**
* Slice the host from the URI.
*/
public function sliceLabels(int $offset, ?int $length = null): static
{
$currentHost = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $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();
if ($currentHost === $host) {
return $this;
}
return $this->withHost($host);
}
/**
* Remove the host zone identifier.
*/
public function removeZoneId(): static
{
$host = Host::fromUri($this->uri);
return match (true) {
$host->hasZoneIdentifier() => $this->withHost($host->withoutZoneIdentifier()->value()),
default => $this,
};
}
/**
* Replace a label of the current URI host.
*/
public function replaceLabel(int $offset, Stringable|string|null $label): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $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 $this->withHost($newHost);
}
public function normalizeIp(): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
if (in_array($host, [null, ''], true)) {
return $this;
}
try {
$converted = IPv4Converter::fromEnvironment()->toDecimal($host);
} catch (MissingFeature) {
$converted = null;
}
if (false === filter_var($converted, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$converted = IPv6Converter::compress($host);
}
if ($converted !== $host) {
return $this->withHost($converted);
}
return $this;
}
public function normalizeHost(): static
{
$host = $this->uri instanceof WhatWgUrl ? $this->uri->getAsciiHost() : $this->uri->getHost();
if (in_array($host, [null, ''], true)) {
return $this;
}
$new = $this->normalizeIp();
$newHost = $new->uri instanceof WhatWgUrl ? $new->uri->getAsciiHost() : $new->uri->getHost();
if ($newHost !== $host) {
return $new;
}
return $this->withHost(Host::new($host)->toAscii());
}
/*********************************
* Path modifier methods
*********************************/
/**
* Add a new base path to the URI path.
*/
public function addBasePath(Stringable|string $path): static
{
/** @var HierarchicalPath $path */
$path = HierarchicalPath::new($path)->withLeadingSlash();
/** @var HierarchicalPath $currentPath */
$currentPath = HierarchicalPath::fromUri($this->uri)->withLeadingSlash();
return match (true) {
!str_starts_with($currentPath->toString(), $path->toString()) => $this->withPath($path->append($currentPath)->toString()),
default => $this->withPath($currentPath),
};
}
/**
* Add a leading slash to the URI path.
*/
public function addLeadingSlash(): static
{
$path = $this->uri->getPath();
return match (true) {
str_starts_with($path, '/') => $this,
default => $this->withPath('/'.$path),
};
}
/**
* Add a trailing slash to the URI path.
*/
public function addTrailingSlash(): static
{
$path = $this->uri->getPath();
return match (true) {
str_ends_with($path, '/') => $this,
default => $this->withPath($path.'/'),
};
}
/**
* Append a new segment or a new path to the URI path.
*/
public function appendSegment(Stringable|string $segment): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->append($segment));
}
/**
* Convert the Data URI path to its ascii form.
*/
public function dataPathToAscii(): static
{
return $this->withPath(DataPath::fromUri($this->uri)->toAscii()->toString());
}
/**
* Convert the Data URI path to its binary (base64encoded) form.
*/
public function dataPathToBinary(): static
{
return $this->withPath(DataPath::fromUri($this->uri)->toBinary()->toString());
}
/**
* Prepend a new segment or a new path to the URI path.
*/
public function prependSegment(Stringable|string $segment): static
{
return$this->withPath(HierarchicalPath::fromUri($this->uri)->prepend($segment));
}
/**
* Remove a base path from the URI path.
*/
public function removeBasePath(Stringable|string $path): static
{
$basePath = HierarchicalPath::new($path)->withLeadingSlash()->toString();
$currentPath = HierarchicalPath::fromUri($this->uri)->withLeadingSlash()->toString();
$newPath = substr($currentPath, strlen($basePath));
return match (true) {
'/' === $basePath,
!str_starts_with($currentPath, $basePath),
!str_starts_with($newPath, '/') => $this,
default => $this->withPath($newPath),
};
}
/**
* Remove dot segments from the URI path.
*/
public function removeDotSegments(): static
{
return $this->withPath(Path::fromUri($this->uri)->withoutDotSegments()->toString());
}
/**
* Remove empty segments from the URI path.
*/
public function removeEmptySegments(): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withoutEmptySegments()->toString());
}
/**
* Remove the leading slash from the URI path.
*/
public function removeLeadingSlash(): static
{
return $this->withPath(Path::fromUri($this->uri)->withoutLeadingSlash());
}
/**
* Remove the trailing slash from the URI path.
*/
public function removeTrailingSlash(): static
{
$path = $this->uri->getPath();
return match (true) {
!str_ends_with($path, '/') => $this,
default => $this->withPath(substr($path, 0, -1)),
};
}
/**
* Remove path segments from the URI path according to their offsets.
*/
public function removeSegments(int ...$keys): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withoutSegment(...$keys)->toString());
}
/**
* Replace the URI path basename.
*/
public function replaceBasename(Stringable|string $basename): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withBasename($basename));
}
/**
* Replace the data URI path parameters.
*/
public function replaceDataUriParameters(Stringable|string $parameters): static
{
return $this->withPath(DataPath::fromUri($this->uri)->withParameters($parameters)->toString());
}
/**
* Replace the URI path dirname.
*/
public function replaceDirname(Stringable|string $dirname): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withDirname($dirname));
}
/**
* Replace the URI path basename extension.
*/
public function replaceExtension(Stringable|string $extension): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withExtension($extension)->toString());
}
/**
* Replace a segment from the URI path according its offset.
*/
public function replaceSegment(int $offset, Stringable|string $segment): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->withSegment($offset, $segment)->toString());
}
/**
* Slice the host from the URI.
*/
public function sliceSegments(int $offset, ?int $length = null): static
{
return $this->withPath(HierarchicalPath::fromUri($this->uri)->slice($offset, $length));
}
/**
* Normalize a URI path.
*
* Make sure the path always has a leading slash if an authority is present
* and the path is not the empty string.
*/
final protected static function normalizePath(WhatWgUrl|Rfc3986Uri|Psr7UriInterface|UriInterface $uri, PathInterface $path): WhatWgUrl|Rfc3986Uri|Psr7UriInterface|UriInterface
{
$pathString = $path->toString();
$authority = match (true) {
$uri instanceof Rfc3986Uri => UriString::buildAuthority([
'host' => $uri->getHost(),
'port' => $uri->getPort(),
'user' => $uri->getUsername(),
'pass' => $uri->getPassword(),
]),
$uri instanceof WhatWgUrl => UriString::buildAuthority([
'host' => $uri->getAsciiHost(),
'port' => $uri->getPort(),
'user' => $uri->getUsername(),
'pass' => $uri->getPassword(),
]),
default => $uri->getAuthority(),
};
return match (true) {
'' === $pathString,
'/' === $pathString[0],
null === $authority,
'' === $authority => $uri->withPath($pathString),
default => $uri->withPath('/'.$pathString),
};
}
/**
* Normalize the URI component value depending on the subject interface.
*
* null value MUST be converted to the empty string if a Psr7 UriInterface is being manipulated.
*/
final protected static function normalizeComponent(Stringable|string|null $component, Rfc3986Uri|WhatWgUrl|Psr7UriInterface|UriInterface $uri): ?string
{
return match (true) {
$uri instanceof Psr7UriInterface,
$component instanceof Stringable => (string) $component,
default => $component,
};
}
final protected static function ipv4Converter(): IPv4Converter
{
static $converter;
$converter = $converter ?? IPv4Converter::fromEnvironment();
return $converter;
}
public function displayUriString(): string
{
if ($this->uri instanceof Uri) {
return $this->uri->toDisplayString();
}
return Uri::new($this->uri)->toDisplayString();
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see Modifier::displayUriString()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::displayUriString() instead', since:'league/uri-components:7.6.0')]
public function getIdnUriString(): string
{
if ($this->uri instanceof WhatWgUrl) {
return $this->uri->toUnicodeString();
}
$currentHost = $this->uri->getHost();
if (null === $currentHost || '' === $currentHost) {
return $this->toString();
}
$host = IdnaConverter::toUnicode($currentHost)->domain();
if ($host === $currentHost) {
return $this->toString();
}
$components = match (true) {
$this->uri instanceof Rfc3986Uri => UriString::parse($this->uri->toRawString()),
$this->uri instanceof UriInterface => $this->uri->toComponents(),
default => UriString::parse($this->uri),
};
$components['host'] = $host;
return UriString::build($components);
}
/*********************************
* Fragment modifier methods
*********************************/
public function appendFragmentDirectives(FragmentDirectives|FragmentDirective|Stringable|string ...$directives): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->append(...$directives));
}
final protected function applyFragmentChanges(FragmentDirectives $fragmentDirectives): static
{
$fValue = Fragment::fromUri($this->unwrap())->value();
if (null === $fValue) {
return $this->withFragment($fragmentDirectives);
}
$pos = strpos($fValue, FragmentDirectives::DELIMITER);
if (false === $pos) {
return $this->withFragment($fValue.$fragmentDirectives->value());
}
return $this->withFragment(substr($fValue, 0, $pos).$fragmentDirectives->value());
}
public function prependFragmentDirectives(FragmentDirectives|FragmentDirective|Stringable|string ...$directives): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->prepend(...$directives));
}
public function removeFragmentDirectives(int ...$offset): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->remove(...$offset));
}
public function replaceFragmentDirective(int $offset, FragmentDirective|Stringable|string $directive): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->replace($offset, $directive));
}
public function sliceFragmentDirectives(int $offset, ?int $length): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->slice($offset, $length));
}
public function filterFragmentDirectives(callable $callback): static
{
return $this->applyFragmentChanges(FragmentDirectives::fromUri($this->unwrap())->filter($callback));
}
public function stripFragmentDirectives(): static
{
$fragment = Fragment::fromUri($this->unwrap())->value();
if (null === $fragment || (false === ($pos = strpos($fragment, FragmentDirectives::DELIMITER)))) {
return $this;
}
return $this->withFragment(substr($fragment, 0, $pos));
}
public function retainFragmentDirectives(): static
{
return $this->withFragment(FragmentDirectives::fromUri($this->unwrap()));
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see Modifier::wrap()
*
* @param UriFactoryInterface|null $uriFactory deprecated, will be removed in the next major release
*/
#[Deprecated(message:'use League\Uri\Modifier::wrap() instead', since:'league/uri-components:7.6.0')]
public static function from(Rfc3986Uri|WhatWgUrl|Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static
{
return new static(match (true) {
$uri instanceof self => $uri->uri,
$uri instanceof Psr7UriInterface,
$uri instanceof UriInterface,
$uri instanceof Rfc3986Uri,
$uri instanceof WhatWgUrl => $uri,
$uriFactory instanceof UriFactoryInterface => $uriFactory->createUri((string) $uri), // using UriFactoryInterface is deprecated
default => Uri::new($uri),
});
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.2.0
* @codeCoverageIgnore
* @see Modifier::removeQueryParameters()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::removeQueryParameters() instead', since:'league/uri-components:7.2.0')]
public function removeParams(string ...$keys): static
{
return $this->removeQueryParameters(...$keys);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.2.0
* @codeCoverageIgnore
* @see Modifier::removeEmptyQueryPairs()
*
* Remove empty pairs from the URL query component.
*
* A pair is considered empty if it's name is the empty string
* and its value is either the empty string or the null value
*/
#[Deprecated(message:'use League\Uri\Modifier::removeEmptyQueryPairs() instead', since:'league/uri-components:7.2.0')]
public function removeEmptyPairs(): static
{
return $this->removeEmptyQueryPairs();
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.2.0
* @codeCoverageIgnore
* @see Modifier::removeQueryPairsByKey()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::removeQueryPairsByKey() instead', since:'league/uri-components:7.2.0')]
public function removePairs(string ...$keys): static
{
return $this->removeQueryPairsByKey(...$keys);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.2.0
* @codeCoverageIgnore
* @see Modifier::removeQueryPairsByKey()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::removeQueryPairsByKey() instead', since:'league/uri-components:7.2.0')]
public function removeQueryPairs(string ...$keys): static
{
return $this->removeQueryPairsByKey(...$keys);
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see Modifier::unwrap()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::uri() instead', since:'league/uri-components:7.6.0')]
public function getUri(): Psr7UriInterface|UriInterface
{
if ($this->uri instanceof Rfc3986Uri || $this->uri instanceof WhatWgUrl) {
return Uri::new($this->uri);
}
return $this->uri;
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @deprecated Since version 7.6.0
* @codeCoverageIgnore
* @see Modifier::toString()
*
* Remove query data according to their key name.
*/
#[Deprecated(message:'use League\Uri\Modifier::toString() instead', since:'league/uri-components:7.6.0')]
public function getUriString(): string
{
return $this->toString();
}
}