From c722abaa52ea96ff4bb7cb8717adf85a24560a6d Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Wed, 25 Dec 2024 14:59:54 +0100 Subject: [PATCH] Adding UriStringProvider interface --- CHANGELOG.md | 2 ++ Uri.php | 47 +++++++++++++++++++++++--- UriTest.php | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++- composer.json | 1 + 4 files changed, 138 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522155c10..a39fab744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ All Notable changes to `League\Uri` will be documented in this file - `Uri::toUnixPath` returns the URI path as a Unix Path or `null` - `Uri::toWindowsPath` returns the URI path as a Windows Path or `null` - `Uri::toRfc8089` return the URI in a RFC8089 formator `null` +- `Uri::toAnchor` returns the HTML anchor string using the instance as the href attribute value +- `Uri::toMarkdown` returns the markdown link construct using the instance as the href attribute value ### Fixed diff --git a/Uri.php b/Uri.php index ee437ceb4..32398b8b7 100644 --- a/Uri.php +++ b/Uri.php @@ -14,11 +14,14 @@ namespace League\Uri; use Deprecated; +use DOMDocument; +use DOMException; use finfo; use League\Uri\Contracts\Conditionable; use League\Uri\Contracts\UriComponentInterface; use League\Uri\Contracts\UriException; use League\Uri\Contracts\UriInterface; +use League\Uri\Contracts\UriStringProvider; use League\Uri\Exceptions\ConversionFailed; use League\Uri\Exceptions\MissingFeature; use League\Uri\Exceptions\SyntaxError; @@ -75,7 +78,7 @@ * @phpstan-import-type ComponentMap from UriString * @phpstan-import-type InputComponentMap from UriString */ -final class Uri implements UriInterface, Conditionable +final class Uri implements UriInterface, Conditionable, UriStringProvider { /** * RFC3986 invalid characters. @@ -1039,6 +1042,42 @@ public function toDisplayString(): string return UriString::build($components); } + /** + * Returns the HTML string representation of the anchor tag with the current instance as its href attribute. + * + * @throws DOMException + */ + public function toAnchorTag(?string $content = null, ?string $class = null, ?string $target = null): string + { + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->preserveWhiteSpace = false; + $doc->formatOutput = true; + $anchor = $doc->createElement('a'); + $anchor->setAttribute('href', $this->toString()); + if (null !== $class) { + $anchor->setAttribute('class', $class); + } + if (null !== $target) { + $anchor->setAttribute('target', $target); + } + + $anchor->appendChild($doc->createTextNode($content ?? $this->toDisplayString())); + $anchor = $doc->saveHTML($anchor); + if (false === $anchor) { + throw new DOMException('The link generation failed.'); + } + + return $anchor; + } + + /** + * Returns the markdown string representation of the anchor tag with the current instance as its href attribute. + */ + public function toMarkdown(?string $content = null): string + { + return '['.($content ?? $this->toDisplayString()).']('.$this->toString().')'; + } + /** * Returns the Unix filesystem path. * @@ -1208,9 +1247,9 @@ public function getFragment(): ?string return $this->fragment; } - public function getOrigin(): ?self + public function getOrigin(): ?string { - return null === $this->origin ? null : Uri::new($this->origin); + return $this->origin; } public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): static @@ -1429,7 +1468,7 @@ public function isCrossOrigin(UriInterface|Stringable|string $uri): bool return true; } - return $this->origin !== (string) $origin; + return $this->origin !== $origin; } public function isSameOrigin(Stringable|string $uri): bool diff --git a/UriTest.php b/UriTest.php index 44564b627..587eca4d5 100644 --- a/UriTest.php +++ b/UriTest.php @@ -717,7 +717,7 @@ public static function sameValueAsProvider(): array #[DataProvider('getOriginProvider')] public function testGetOrigin(Psr7UriInterface|Uri|string $uri, ?string $expectedOrigin): void { - self::assertSame($expectedOrigin, Uri::new($uri)->getOrigin()?->toString()); + self::assertSame($expectedOrigin, Uri::new($uri)->getOrigin()); } public static function getOriginProvider(): array @@ -981,4 +981,95 @@ public static function providesUriToDisplay(): iterable 'output' => 'http://bébé.be', ]; } + + #[Test] + #[DataProvider('providesUriToMarkdown')] + public function it_will_generate_the_markdown_code_for_the_instance(string $uri, ?string $content, string $expected): void + { + self::assertSame($expected, Uri::new($uri)->toMarkdown($content)); + } + + public static function providesUriToMarkdown(): iterable + { + yield 'empty string' => [ + 'uri' => '', + 'content' => '', + 'expected' => '[]()', + ]; + + yield 'URI with a specific content' => [ + 'uri' => 'http://example.com/foo/bar', + 'content' => 'this is a link', + 'expected' => '[this is a link](http://example.com/foo/bar)', + ]; + + yield 'URI without content' => [ + 'uri' => 'http://Bébé.be', + 'content' => null, + 'expected' => '[http://bébé.be](http://xn--bb-bjab.be)', + ]; + } + + #[Test] + #[DataProvider('providesUriToHTML')] + public function it_will_generate_the_html_code_for_the_instance( + string $uri, + ?string $content, + ?string $class, + ?string $target, + string $expected + ): void { + self::assertSame($expected, Uri::new($uri)->toAnchorTag($content, $class, $target)); + } + + public static function providesUriToHTML(): iterable + { + yield 'empty string' => [ + 'uri' => '', + 'content' => '', + 'class' => null, + 'target' => null, + 'expected' => '', + ]; + + yield 'URI with a specific content' => [ + 'uri' => 'http://example.com/foo/bar', + 'content' => 'this is a link', + 'class' => null, + 'target' => null, + 'expected' => 'this is a link', + ]; + + yield 'URI without content' => [ + 'uri' => 'http://Bébé.be', + 'content' => null, + 'class' => null, + 'target' => null, + 'expected' => 'http://bébé.be', + ]; + + yield 'URI without content and with class' => [ + 'uri' => 'http://Bébé.be', + 'content' => null, + 'class' => 'foo bar', + 'target' => null, + 'expected' => 'http://bébé.be', + ]; + + yield 'URI without content and with target' => [ + 'uri' => 'http://Bébé.be', + 'content' => null, + 'class' => null, + 'target' => '_blank', + 'expected' => 'http://bébé.be', + ]; + + yield 'URI without content, with target and class' => [ + 'uri' => 'http://Bébé.be', + 'content' => null, + 'class' => 'foo bar', + 'target' => '_blank', + 'expected' => 'http://bébé.be', + ]; + } } diff --git a/composer.json b/composer.json index e1c4be3e9..d88b63436 100644 --- a/composer.json +++ b/composer.json @@ -56,6 +56,7 @@ "league/uri-schemes": "^1.0" }, "suggest": { + "ext-dom": "to convert the URI into an HTML anchor tag", "ext-bcmath": "to improve IPV4 host parsing", "ext-fileinfo": "to create Data URI from file contennts", "ext-gmp": "to improve IPV4 host parsing",