diff --git a/components/Modifier.php b/components/Modifier.php index f2cacf6f..4e314940 100644 --- a/components/Modifier.php +++ b/components/Modifier.php @@ -24,7 +24,8 @@ use League\Uri\Contracts\UriAccess; use League\Uri\Contracts\UriInterface; use League\Uri\Exceptions\SyntaxError; -use League\Uri\IPv4\Converter; +use League\Uri\Idna\Converter as IdnConverter; +use League\Uri\IPv4\Converter as IPv4Converter; use Psr\Http\Message\UriFactoryInterface; use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; @@ -195,12 +196,15 @@ public function appendLabel(Stringable|string|null $label): static */ public function hostToAscii(): static { - return new static($this->uri->withHost( - static::normalizeComponent( - Host::fromUri($this->uri)->value(), - $this->uri - ) - )); + $currentHost = $this->uri->getHost(); + $host = IdnConverter::toAsciiOrFail((string) $currentHost); + + return match (true) { + null === $currentHost, + '' === $currentHost, + $host === $currentHost => $this, + default => new static($this->uri->withHost($host)), + }; } /** @@ -208,12 +212,15 @@ public function hostToAscii(): static */ public function hostToUnicode(): static { - return new static($this->uri->withHost( - static::normalizeComponent( - Host::fromUri($this->uri)->toUnicode(), - $this->uri - ) - )); + $currentHost = $this->uri->getHost(); + $host = IdnConverter::toUnicode((string) $currentHost)->domain(); + + return match (true) { + null === $currentHost, + '' === $currentHost, + $host === $currentHost => $this, + default => new static($this->uri->withHost($host)), + }; } /** @@ -312,13 +319,13 @@ public function removeLabels(int ...$keys): static */ public function removeRootLabel(): static { - $currentHost = $this->uri->getHost(); + $host = $this->uri->getHost(); return match (true) { - null === $currentHost, - '' === $currentHost, - !str_ends_with($currentHost, '.') => $this, - default => new static($this->uri->withHost(substr($currentHost, 0, -1))), + null === $host, + '' === $host, + !str_ends_with($host, '.') => $this, + default => new static($this->uri->withHost(substr($host, 0, -1))), }; } @@ -342,12 +349,17 @@ public function sliceLabels(int $offset, int $length = null): static */ public function removeZoneId(): static { - return new static($this->uri->withHost( - static::normalizeComponent( - Host::fromUri($this->uri)->withoutZoneIdentifier()->value(), - $this->uri - ) - )); + $host = Host::fromUri($this->uri); + + return match (true) { + $host->hasZoneIdentifier() => new static($this->uri->withHost( + static::normalizeComponent( + Host::fromUri($this->uri)->withoutZoneIdentifier()->value(), + $this->uri + ) + )), + default => $this, + }; } /** @@ -584,11 +596,11 @@ final protected static function normalizeComponent(?string $component, Psr7UriIn }; } - final protected static function ipv4Converter(): Converter + final protected static function ipv4Converter(): IPv4Converter { static $converter; - $converter = $converter ?? Converter::fromEnvironment(); + $converter = $converter ?? IPv4Converter::fromEnvironment(); return $converter; } diff --git a/components/ModifierTest.php b/components/ModifierTest.php index 38463233..0f5e5714 100644 --- a/components/ModifierTest.php +++ b/components/ModifierTest.php @@ -194,6 +194,13 @@ public static function validHostProvider(): array ]; } + public function testItCanSliceHostLabels(): void + { + $uri = 'http://www.localhost.co.uk/path/to/the/sky/'; + + self::assertSame('http://www.localhost/path/to/the/sky/', Modifier::from($uri)->sliceLabels(2, 2)->getUriString()); + } + public function testAppendLabelWithIpv4Host(): void { $uri = Http::new('http://127.0.0.1/foo/bar'); @@ -708,4 +715,11 @@ public static function providesInvalidMethodNames(): iterable yield 'unknown method' => ['method' => 'unknownMethod']; yield 'case sensitive method' => ['method' => 'rePLAceExtenSIOn']; } + + public function testItCanSlicePathSegments(): void + { + $uri = 'http://www.localhost.com/path/to/the/sky/'; + + self::assertSame('http://www.localhost.com/the/sky/', Modifier::from($uri)->sliceSegments(2, 2)->getUriString()); + } } diff --git a/interfaces/IPv4/Converter.php b/interfaces/IPv4/Converter.php index df2c97ad..b746c46a 100644 --- a/interfaces/IPv4/Converter.php +++ b/interfaces/IPv4/Converter.php @@ -42,12 +42,12 @@ final class Converter '/^(?\d+)$/' => 10, ]; - private readonly mixed $maxIpv4Number; + private readonly mixed $maxIPv4Number; public function __construct( private readonly Calculator $calculator ) { - $this->maxIpv4Number = $calculator->sub($calculator->pow(2, 32), 1); + $this->maxIPv4Number = $calculator->sub($calculator->pow(2, 32), 1); } /** @@ -170,7 +170,7 @@ public function toDecimal(Stringable|string|null $host): ?string * * @see https://url.spec.whatwg.org/#ipv4-number-parser * - * @return mixed Returns null if it can not correctly convert the label + * @return mixed returns null if it can not correctly convert the label */ private function labelToNumber(string $label): mixed { @@ -185,7 +185,7 @@ private function labelToNumber(string $label): mixed } $number = $this->calculator->baseConvert($number, $base); - if (0 <= $this->calculator->compare($number, 0) && 0 >= $this->calculator->compare($number, $this->maxIpv4Number)) { + if (0 <= $this->calculator->compare($number, 0) && 0 >= $this->calculator->compare($number, $this->maxIPv4Number)) { return $number; } } diff --git a/interfaces/UriString.php b/interfaces/UriString.php index 2814c41a..2b105128 100644 --- a/interfaces/UriString.php +++ b/interfaces/UriString.php @@ -38,9 +38,9 @@ * @author Ignace Nyamagana Butera * @since 6.0.0 * + * @phpstan-type AuthorityMap array{user:?string, pass:?string, host:?string, port:?int} * @phpstan-type ComponentMap array{scheme:?string, user:?string, pass:?string, host:?string, port:?int, path:string, query:?string, fragment:?string} * @phpstan-type InputComponentMap array{scheme? : ?string, user? : ?string, pass? : ?string, host? : ?string, port? : ?int, path? : ?string, query? : ?string, fragment? : ?string} - * @phpstan-type AuthorityMap array{user:?string, pass:?string, host:?string, port:?int} */ final class UriString { diff --git a/uri/BaseUri.php b/uri/BaseUri.php index a95d04d5..25c4995d 100644 --- a/uri/BaseUri.php +++ b/uri/BaseUri.php @@ -54,39 +54,6 @@ final protected function __construct( $this->origin = $this->computeOrigin($this->uri, $this->nullValue); } - final protected function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue): Psr7UriInterface|UriInterface|null - { - $scheme = $uri->getScheme(); - if ('blob' !== $scheme) { - return match (true) { - isset(static::WHATWG_SPECIAL_SCHEMES[$scheme]) => $uri - ->withFragment($nullValue) - ->withQuery($nullValue) - ->withPath('') - ->withUserInfo($nullValue), - default => null, - }; - } - - $components = UriString::parse($uri->getPath()); - if ($uri instanceof Psr7UriInterface) { - /** @var ComponentMap $components */ - $components = array_map(fn ($component) => null === $component ? '' : $component, $components); - } - - return match (true) { - null !== $components['scheme'] && isset(static::WHATWG_SPECIAL_SCHEMES[strtolower($components['scheme'])]) => $uri - ->withFragment($nullValue) - ->withQuery($nullValue) - ->withPath('') - ->withHost($components['host']) - ->withPort($components['port']) - ->withScheme($components['scheme']) - ->withUserInfo($nullValue), - default => null, - }; - } - public static function from(Stringable|string $uri, ?UriFactoryInterface $uriFactory = null): static { return new static(static::formatHost(static::filterUri($uri, $uriFactory)), $uriFactory); @@ -135,22 +102,37 @@ public function origin(): ?self */ public function isCrossOrigin(Stringable|string $uri): bool { - return null === $this->origin - || null === ($uriOrigin = $this->computeOrigin(static::filterUri($uri), null)) + if (null === $this->origin) { + return true; + } + + $uri = static::filterUri($uri); + $uriOrigin = $this->computeOrigin($uri, $uri instanceof Psr7UriInterface ? '' : null); + + return null === $uriOrigin || $uriOrigin->__toString() !== $this->origin->__toString(); } + /** + * Tells whether the URI is absolute. + */ public function isAbsolute(): bool { return $this->nullValue !== $this->uri->getScheme(); } + /** + * Tells whether the URI is a network path. + */ public function isNetworkPath(): bool { return $this->nullValue === $this->uri->getScheme() && $this->nullValue !== $this->uri->getAuthority(); } + /** + * Tells whether the URI is an absolute path. + */ public function isAbsolutePath(): bool { return $this->nullValue === $this->uri->getScheme() @@ -158,6 +140,9 @@ public function isAbsolutePath(): bool && '/' === ($this->uri->getPath()[0] ?? ''); } + /** + * Tells whether the URI is a relative path. + */ public function isRelativePath(): bool { return $this->nullValue === $this->uri->getScheme() @@ -173,56 +158,6 @@ public function isSameDocument(Stringable|string $uri): bool return $this->normalize(static::filterUri($uri)) === $this->normalize($this->uri); } - /** - * Normalizes a URI for comparison. - */ - final protected function normalize(Psr7UriInterface|UriInterface $uri): string - { - $null = $uri instanceof Psr7UriInterface ? '' : null; - - $path = $uri->getPath(); - if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) { - $path = $this->removeDotSegments($path); - } - - $query = $uri->getQuery(); - $pairs = null === $query ? [] : explode('&', $query); - sort($pairs); - - static $regexpEncodedChars = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|AF]|6[1-9|A-F]|7[\d|E]),i'; - $value = preg_replace_callback( - $regexpEncodedChars, - static fn (array $matches): string => rawurldecode($matches[0]), - [$path, implode('&', $pairs)] - ) ?? ['', $null]; - - [$path, $query] = $value + ['', $null]; - if ($null !== $uri->getAuthority() && '' === $path) { - $path = '/'; - } - - return $uri - ->withHost(Uri::fromComponents(['host' => $uri->getHost()])->getHost()) - ->withPath($path) - ->withQuery([] === $pairs ? $null : $query) - ->withFragment($null) - ->__toString(); - } - - /** - * Input URI normalization to allow Stringable and string URI. - */ - final protected static function filterUri(Stringable|string $uri, UriFactoryInterface|null $uriFactory = null): Psr7UriInterface|UriInterface - { - return match (true) { - $uri instanceof UriAccess => $uri->getUri(), - $uri instanceof Psr7UriInterface, - $uri instanceof UriInterface => $uri, - $uriFactory instanceof UriFactoryInterface => $uriFactory->createUri((string) $uri), - default => Uri::new($uri), - }; - } - /** * Resolves a URI against a base URI using RFC3986 rules. * @@ -232,7 +167,7 @@ final protected static function filterUri(Stringable|string $uri, UriFactoryInte * This method MUST be transparent when dealing with error and exceptions. * It MUST not alter or silence them apart from validating its own parameters. */ - final public function resolve(Stringable|string $uri): static + public function resolve(Stringable|string $uri): static { $uri = static::formatHost(static::filterUri($uri, $this->uriFactory)); $null = $uri instanceof Psr7UriInterface ? '' : null; @@ -275,7 +210,122 @@ final public function resolve(Stringable|string $uri): static } /** - * Remove dot segments from the URI path. + * Relativize a URI according to a base URI. + * + * This method MUST retain the state of the submitted URI instance, and return + * a URI instance of the same type that contains the applied modifications. + * + * This method MUST be transparent when dealing with error and exceptions. + * It MUST not alter of silence them apart from validating its own parameters. + */ + public function relativize(Stringable|string $uri): static + { + $uri = static::formatHost(static::filterUri($uri, $this->uriFactory)); + if ($this->canNotBeRelativize($uri)) { + return new static($uri, $this->uriFactory); + } + + $null = $uri instanceof Psr7UriInterface ? '' : null; + $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null); + $targetPath = $uri->getPath(); + $basePath = $this->uri->getPath(); + + return new static( + match (true) { + $targetPath !== $basePath => $uri->withPath(static::relativizePath($targetPath, $basePath)), + static::componentEquals('query', $uri) => $uri->withPath('')->withQuery($null), + $null === $uri->getQuery() => $uri->withPath(static::formatPathWithEmptyBaseQuery($targetPath)), + default => $uri->withPath(''), + }, + $this->uriFactory + ); + } + + final protected function computeOrigin(Psr7UriInterface|UriInterface $uri, ?string $nullValue): Psr7UriInterface|UriInterface|null + { + $scheme = $uri->getScheme(); + if ('blob' !== $scheme) { + return match (true) { + isset(static::WHATWG_SPECIAL_SCHEMES[$scheme]) => $uri + ->withFragment($nullValue) + ->withQuery($nullValue) + ->withPath('') + ->withUserInfo($nullValue), + default => null, + }; + } + + $components = UriString::parse($uri->getPath()); + if ($uri instanceof Psr7UriInterface) { + /** @var ComponentMap $components */ + $components = array_map(fn ($component) => null === $component ? '' : $component, $components); + } + + return match (true) { + null !== $components['scheme'] && isset(static::WHATWG_SPECIAL_SCHEMES[strtolower($components['scheme'])]) => $uri + ->withFragment($nullValue) + ->withQuery($nullValue) + ->withPath('') + ->withHost($components['host']) + ->withPort($components['port']) + ->withScheme($components['scheme']) + ->withUserInfo($nullValue), + default => null, + }; + } + + /** + * Normalizes a URI for comparison; this URI string representation is not suitable for usage as per RFC guidelines. + */ + final protected function normalize(Psr7UriInterface|UriInterface $uri): string + { + $null = $uri instanceof Psr7UriInterface ? '' : null; + + $path = $uri->getPath(); + if ('/' === ($path[0] ?? '') || '' !== $uri->getScheme().$uri->getAuthority()) { + $path = $this->removeDotSegments($path); + } + + $query = $uri->getQuery(); + $pairs = null === $query ? [] : explode('&', $query); + sort($pairs); + + static $regexpEncodedChars = ',%(2[D|E]|3\d|4[1-9|A-F]|5[\d|AF]|6[1-9|A-F]|7[\d|E]),i'; + $value = preg_replace_callback( + $regexpEncodedChars, + static fn (array $matches): string => rawurldecode($matches[0]), + [$path, implode('&', $pairs)] + ) ?? ['', $null]; + + [$path, $query] = $value + ['', $null]; + if ($null !== $uri->getAuthority() && '' === $path) { + $path = '/'; + } + + return $uri + ->withHost(Uri::fromComponents(['host' => $uri->getHost()])->getHost()) + ->withPath($path) + ->withQuery([] === $pairs ? $null : $query) + ->withFragment($null) + ->__toString(); + } + + /** + * Input URI normalization to allow Stringable and string URI. + */ + final protected static function filterUri(Stringable|string $uri, UriFactoryInterface|null $uriFactory = null): Psr7UriInterface|UriInterface + { + return match (true) { + $uri instanceof UriAccess => $uri->getUri(), + $uri instanceof Psr7UriInterface, + $uri instanceof UriInterface => $uri, + $uriFactory instanceof UriFactoryInterface => $uriFactory->createUri((string) $uri), + default => Uri::new($uri), + }; + } + + /** + * Remove dot segments from the URI path as per RFC specification. */ final protected function removeDotSegments(string $path): string { @@ -283,8 +333,22 @@ final protected function removeDotSegments(string $path): string return $path; } + $reducer = function (array $carry, string $segment): array { + if ('..' === $segment) { + array_pop($carry); + + return $carry; + } + + if (!isset(static::DOT_SEGMENTS[$segment])) { + $carry[] = $segment; + } + + return $carry; + }; + $oldSegments = explode('/', $path); - $newPath = implode('/', array_reduce($oldSegments, static::reducer(...), [])); + $newPath = implode('/', array_reduce($oldSegments, $reducer(...), [])); if (isset(static::DOT_SEGMENTS[end($oldSegments)])) { $newPath .= '/'; } @@ -299,26 +363,6 @@ final protected function removeDotSegments(string $path): string return $newPath; } - /** - * Remove dot segments. - * - * @return array - */ - final protected static function reducer(array $carry, string $segment): array - { - if ('..' === $segment) { - array_pop($carry); - - return $carry; - } - - if (!isset(static::DOT_SEGMENTS[$segment])) { - $carry[] = $segment; - } - - return $carry; - } - /** * Resolves an URI path and query component. * @@ -366,63 +410,27 @@ final protected function resolvePathAndQuery(Psr7UriInterface|UriInterface $uri) return [$targetPath, $uri->getQuery()]; } - /** - * Relativize a URI according to a base URI. - * - * This method MUST retain the state of the submitted URI instance, and return - * a URI instance of the same type that contains the applied modifications. - * - * This method MUST be transparent when dealing with error and exceptions. - * It MUST not alter of silence them apart from validating its own parameters. - */ - final public function relativize(Stringable|string $uri): static - { - $uri = static::formatHost(static::filterUri($uri, $this->uriFactory)); - if ($this->canNotBeRelativize($uri)) { - return new static($uri, $this->uriFactory); - } - - $null = $uri instanceof Psr7UriInterface ? '' : null; - $uri = $uri->withScheme($null)->withPort(null)->withUserInfo($null)->withHost($null); - $targetPath = $uri->getPath(); - $basePath = $this->uri->getPath(); - - return new static( - match (true) { - $targetPath !== $basePath => $uri->withPath(static::relativizePath($targetPath, $basePath)), - static::componentEquals('query', $uri) => $uri->withPath('')->withQuery($null), - $null === $uri->getQuery() => $uri->withPath(static::formatPathWithEmptyBaseQuery($targetPath)), - default => $uri->withPath(''), - }, - $this->uriFactory - ); - } - /** * Tells whether the component value from both URI object equals. - */ - final protected function componentEquals(string $property, Psr7UriInterface|UriInterface $uri): bool - { - return static::getComponent($property, $uri) === static::getComponent($property, $this->uri); - } - - /** - * Returns the component value from the submitted URI object. * * @pqram 'query'|'authority'|'scheme' $property */ - final protected static function getComponent(string $property, Psr7UriInterface|UriInterface $uri): ?string + final protected function componentEquals(string $property, Psr7UriInterface|UriInterface $uri): bool { - $component = match ($property) { - 'query' => $uri->getQuery(), - 'authority' => $uri->getAuthority(), - default => $uri->getScheme(), - }; + $getComponent = function (string $property, Psr7UriInterface|UriInterface $uri): ?string { + $component = match ($property) { + 'query' => $uri->getQuery(), + 'authority' => $uri->getAuthority(), + default => $uri->getScheme(), + }; - return match (true) { - $uri instanceof UriInterface, '' !== $component => $component, - default => null, + return match (true) { + $uri instanceof UriInterface, '' !== $component => $component, + default => null, + }; }; + + return $getComponent($property, $uri) === $getComponent($property, $this->uri); } /** @@ -449,9 +457,9 @@ final protected static function formatHost(Psr7UriInterface|UriInterface $uri): */ final protected function canNotBeRelativize(Psr7UriInterface|UriInterface $uri): bool { - return static::from($uri)->isRelativePath() - || !static::componentEquals('scheme', $uri) - || !static::componentEquals('authority', $uri); + return !static::componentEquals('scheme', $uri) + || !static::componentEquals('authority', $uri) + || static::from($uri)->isRelativePath(); } /** @@ -484,11 +492,11 @@ final protected static function relativizePath(string $path, string $basePath): */ final protected static function getSegments(string $path): array { - if ('' !== $path && '/' === $path[0]) { - $path = substr($path, 1); - } - - return explode('/', $path); + return explode('/', match (true) { + '' === $path, + '/' !== $path[0] => $path, + default => substr($path, 1), + }); } /** diff --git a/uri/Http.php b/uri/Http.php index 3aeb35f7..2b3dc7f0 100644 --- a/uri/Http.php +++ b/uri/Http.php @@ -14,8 +14,10 @@ namespace League\Uri; use JsonSerializable; +use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriInterface; use League\Uri\Exceptions\SyntaxError; +use League\Uri\UriTemplate\TemplateCanNotBeExpanded; use Psr\Http\Message\UriInterface as Psr7UriInterface; use Stringable; @@ -76,6 +78,12 @@ public static function fromBaseUri(Stringable|string $uri, Stringable|string|nul return new self(Uri::fromBaseUri($uri, $baseUri)); } + /** + * Creates a new instance from a template. + * + * @throws TemplateCanNotBeExpanded if the variables are invalid or missing + * @throws UriException if the variables are invalid or missing + */ public static function fromTemplate(Stringable|string $template, iterable $variables = []): self { return new self(Uri::fromTemplate($template, $variables)); diff --git a/uri/Uri.php b/uri/Uri.php index 1892a823..28f70d81 100644 --- a/uri/Uri.php +++ b/uri/Uri.php @@ -56,7 +56,7 @@ use const FILTER_VALIDATE_IP; /** - * @phpstan-import-type ComponentMap from UriInterface + * @phpstan-import-type ComponentMap from UriString * @phpstan-import-type InputComponentMap from UriString */ final class Uri implements UriInterface @@ -406,7 +406,25 @@ private function formatPort(?int $port = null): ?int */ public static function new(Stringable|string $uri = ''): self { - $components = $uri instanceof UriInterface ? $uri->getComponents() : UriString::parse($uri); + $components = match (true) { + $uri instanceof UriInterface => $uri->getComponents(), + $uri instanceof Psr7UriInterface => (function (Psr7UriInterface $uri): array { + $normalize = fn ($component) => '' !== $component ? $component : null; + $userInfo = $uri->getUserInfo(); + [$user, $pass] = '' !== $userInfo ? explode(':', $userInfo, 2) : ['', '']; + return [ + 'scheme' => $normalize($uri->getScheme()), + 'user' => $normalize($user), + 'pass' => $normalize($pass), + 'host' => $normalize($uri->getHost()), + 'port' => $uri->getPort(), + 'path' => $uri->getPath(), + 'query' => $normalize($uri->getQuery()), + 'fragment' => $normalize($uri->getFragment()), + ]; + })($uri), + default => UriString::parse($uri), + }; return new self( $components['scheme'], @@ -445,7 +463,7 @@ public static function fromBaseUri(Stringable|string $uri, Stringable|string|nul * @throws TemplateCanNotBeExpanded if the variables are invalid or missing * @throws UriException if the resulting expansion can not be converted to a UriInterface instance */ - public static function fromTemplate(Stringable|string $template, iterable $variables = []): self + public static function fromTemplate(UriTemplate|Stringable|string $template, iterable $variables = []): self { return match (true) { $template instanceof UriTemplate => self::fromComponents($template->expand($variables)->getComponents()), @@ -984,7 +1002,7 @@ public function jsonSerialize(): string */ public function getComponents(): array { - [$user, $pass] = null !== $this->userInfo ? explode(':', $this->userInfo) : [null, null]; + [$user, $pass] = null !== $this->userInfo ? explode(':', $this->userInfo, 2) : [null, null]; return [ 'scheme' => $this->scheme, diff --git a/uri/UriTemplate.php b/uri/UriTemplate.php index 625cf04b..f7eeaf3d 100644 --- a/uri/UriTemplate.php +++ b/uri/UriTemplate.php @@ -31,7 +31,7 @@ * @author Ignace Nyamagana Butera * @since 6.1.0 */ -final class UriTemplate implements Stringable +final class UriTemplate { private readonly Template $template; private readonly VariableBag $defaultVariables; @@ -40,7 +40,7 @@ final class UriTemplate implements Stringable * @throws SyntaxError if the template syntax is invalid * @throws TemplateCanNotBeExpanded if the template or the variables are invalid */ - public function __construct(Template|Stringable|string $template, iterable $defaultVariables = []) + public function __construct(Stringable|string $template, iterable $defaultVariables = []) { $this->template = $template instanceof Template ? $template : Template::new($template); $this->defaultVariables = $this->filterVariables($defaultVariables); @@ -52,13 +52,14 @@ private function filterVariables(iterable $variables): VariableBag $variables = new VariableBag($variables); } - $offsets = array_fill_keys($this->template->variableNames, 1); - return $variables - ->filter(fn ($value, string|int $name) => array_key_exists($name, $offsets)); + ->filter(fn ($value, string|int $name) => array_key_exists( + $name, + array_fill_keys($this->template->variableNames, 1) + )); } - public function __toString(): string + public function getTemplate(): string { return $this->template->value; } @@ -118,18 +119,4 @@ public function expandOrFail(iterable $variables = []): UriInterface $this->filterVariables($variables)->replace($this->defaultVariables) )); } - - /** - * DEPRECATION WARNING! This method will be removed in the next major point release. - * - * @deprecated Since version 7.0.0 - * @codeCoverageIgnore - * @see UriTemplate::__toString - * - * Returns the template string - */ - public function getTemplate(): string - { - return $this->__toString(); - } } diff --git a/uri/UriTemplate/TemplateTest.php b/uri/UriTemplate/TemplateTest.php index fd3bddf0..1d425fc5 100644 --- a/uri/UriTemplate/TemplateTest.php +++ b/uri/UriTemplate/TemplateTest.php @@ -95,6 +95,7 @@ public static function uriTemplateSpecificationDataProvider(): iterable public function testItCanBeInstantiatedWithAValidNotation(string $notation): void { self::assertSame($notation, Template::new($notation)->value); + self::assertSame($notation, (string) Template::new($notation)); } public static function providesValidNotation(): iterable @@ -180,4 +181,11 @@ public static function providesExpansion(): iterable ], ]; } + + public function testExpandOrFailIfAtLeastOneVariableIsMissing(): void + { + $this->expectException(TemplateCanNotBeExpanded::class); + + Template::new('{var}{baz}')->expandOrFail(['var' => 'bar']); + } } diff --git a/uri/UriTemplateTest.php b/uri/UriTemplateTest.php index fbdfef94..3538f7e1 100644 --- a/uri/UriTemplateTest.php +++ b/uri/UriTemplateTest.php @@ -35,7 +35,7 @@ public function testGetTemplate(): void $uriTemplate = new UriTemplate($template, $variables); - self::assertSame($template, (string) $uriTemplate); + self::assertSame($template, $uriTemplate->getTemplate()); } public function testGetDefaultVariables(): void @@ -356,9 +356,9 @@ public function testExpansionWithMultipleSameExpression(): void public function testExpandOrFailIfVariablesAreMissing(): void { - $this->expectException(UriTemplate\TemplateCanNotBeExpanded::class); + $this->expectException(TemplateCanNotBeExpanded::class); - (new UriTemplate('{var}'))->expandOrFail([]); + (new UriTemplate('{var}'))->expandOrFail(); } public function testExpandOrFailIfAtLeastOneVariableIsMissing(): void