From b570ee110317c965fef4d273f4ddba00c490db69 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Aug 2019 15:15:15 +0200 Subject: [PATCH] [WebLink] implement PSR-13 directly --- composer.json | 1 - .../Twig/Extension/WebLinkExtension.php | 4 +- .../Tests/Extension/WebLinkExtensionTest.php | 2 +- src/Symfony/Bridge/Twig/composer.json | 2 +- .../Controller/ControllerTrait.php | 6 +- .../Tests/Controller/ControllerTraitTest.php | 2 +- .../Bundle/FrameworkBundle/composer.json | 3 +- src/Symfony/Component/WebLink/CHANGELOG.md | 5 + .../Component/WebLink/GenericLinkProvider.php | 83 ++++++++++ src/Symfony/Component/WebLink/Link.php | 153 ++++++++++++++++++ .../AddLinkHeaderListenerTest.php | 4 +- .../WebLink/Tests/GenericLinkProviderTest.php | 87 ++++++++++ .../Tests/HttpHeaderSerializerTest.php | 2 +- .../Component/WebLink/Tests/LinkTest.php | 109 +++++++++++++ src/Symfony/Component/WebLink/composer.json | 7 +- 15 files changed, 454 insertions(+), 16 deletions(-) create mode 100644 src/Symfony/Component/WebLink/GenericLinkProvider.php create mode 100644 src/Symfony/Component/WebLink/Link.php create mode 100644 src/Symfony/Component/WebLink/Tests/GenericLinkProviderTest.php create mode 100644 src/Symfony/Component/WebLink/Tests/LinkTest.php diff --git a/composer.json b/composer.json index b3956a8875648..93d6b029d8c12 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,6 @@ "ext-xml": "*", "doctrine/event-manager": "~1.0", "doctrine/persistence": "~1.0", - "fig/link-util": "^1.0", "twig/twig": "^1.41|^2.10", "psr/cache": "~1.0", "psr/container": "^1.0", diff --git a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php index 0ca519ee72423..63bfa2eba0260 100644 --- a/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php @@ -11,9 +11,9 @@ namespace Symfony\Bridge\Twig\Extension; -use Fig\Link\GenericLinkProvider; -use Fig\Link\Link; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\Link; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/WebLinkExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/WebLinkExtensionTest.php index 332165571c9a8..1739c1ee91833 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/WebLinkExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/WebLinkExtensionTest.php @@ -11,11 +11,11 @@ namespace Symfony\Bridge\Twig\Tests\Extension; -use Fig\Link\Link; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\WebLinkExtension; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\WebLink\Link; /** * @author Kévin Dunglas diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index a21b3c288cf8d..bddfe8bee55b0 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -42,7 +42,7 @@ "symfony/console": "^3.4|^4.0|^5.0", "symfony/var-dumper": "^3.4|^4.0|^5.0", "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/web-link": "^3.4|^4.0|^5.0", + "symfony/web-link": "^4.4|^5.0", "symfony/workflow": "^4.3|^5.0" }, "conflict": { diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php index 9cb7a58f6e856..14e62f074b24b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php @@ -12,9 +12,8 @@ namespace Symfony\Bundle\FrameworkBundle\Controller; use Doctrine\Common\Persistence\ManagerRegistry; -use Fig\Link\GenericLinkProvider; -use Fig\Link\Link; use Psr\Container\ContainerInterface; +use Psr\Link\LinkInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; @@ -33,6 +32,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\GenericLinkProvider; /** * Common features needed in controllers. @@ -420,7 +420,7 @@ protected function dispatchMessage($message, array $stamps = []): Envelope * * @final */ - protected function addLink(Request $request, Link $link) + protected function addLink(Request $request, LinkInterface $link) { if (!class_exists(AddLinkHeaderListener::class)) { throw new \LogicException('You can not use the "addLink" method if the WebLink component is not available. Try running "composer require symfony/web-link".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php index aa03b2d13fab5..fd064bb15b1a9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/ControllerTraitTest.php @@ -11,7 +11,6 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Controller; -use Fig\Link\Link; use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\DependencyInjection\Container; @@ -29,6 +28,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\WebLink\Link; abstract class ControllerTraitTest extends TestCase { diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 5e3a699e3dc12..152f1b65a5a2f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -31,7 +31,6 @@ }, "require-dev": { "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", "symfony/asset": "^3.4|^4.0|^5.0", "symfony/browser-kit": "^4.3|^5.0", "symfony/console": "^4.3|^5.0", @@ -58,7 +57,7 @@ "symfony/workflow": "^4.3|^5.0", "symfony/yaml": "^3.4|^4.0|^5.0", "symfony/property-info": "^3.4|^4.0|^5.0", - "symfony/web-link": "^3.4|^4.0|^5.0", + "symfony/web-link": "^4.4|^5.0", "doctrine/annotations": "~1.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0", "twig/twig": "~1.34|~2.4" diff --git a/src/Symfony/Component/WebLink/CHANGELOG.md b/src/Symfony/Component/WebLink/CHANGELOG.md index 2204282c26ca6..28dad5abdd749 100644 --- a/src/Symfony/Component/WebLink/CHANGELOG.md +++ b/src/Symfony/Component/WebLink/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +4.4.0 +----- + + * implement PSR-13 directly + 3.3.0 ----- diff --git a/src/Symfony/Component/WebLink/GenericLinkProvider.php b/src/Symfony/Component/WebLink/GenericLinkProvider.php new file mode 100644 index 0000000000000..5c8b2e71144d7 --- /dev/null +++ b/src/Symfony/Component/WebLink/GenericLinkProvider.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\WebLink; + +use Psr\Link\EvolvableLinkProviderInterface; +use Psr\Link\LinkInterface; + +class GenericLinkProvider implements EvolvableLinkProviderInterface +{ + /** + * @var LinkInterface[] + */ + private $links = []; + + /** + * @param LinkInterface[] $links + */ + public function __construct(array $links = []) + { + $that = $this; + + foreach ($links as $link) { + $that = $that->withLink($link); + } + + $this->links = $that->links; + } + + /** + * {@inheritdoc} + */ + public function getLinks(): array + { + return array_values($this->links); + } + + /** + * {@inheritdoc} + */ + public function getLinksByRel($rel): array + { + $links = []; + + foreach ($this->links as $link) { + if (\in_array($rel, $link->getRels())) { + $links[] = $link; + } + } + + return $links; + } + + /** + * {@inheritdoc} + */ + public function withLink(LinkInterface $link) + { + $that = clone $this; + $that->links[spl_object_id($link)] = $link; + + return $that; + } + + /** + * {@inheritdoc} + */ + public function withoutLink(LinkInterface $link) + { + $that = clone $this; + unset($that->links[spl_object_id($link)]); + + return $that; + } +} diff --git a/src/Symfony/Component/WebLink/Link.php b/src/Symfony/Component/WebLink/Link.php new file mode 100644 index 0000000000000..a7d034c1fae8d --- /dev/null +++ b/src/Symfony/Component/WebLink/Link.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\WebLink; + +use Psr\Link\EvolvableLinkInterface; + +class Link implements EvolvableLinkInterface +{ + // Relations defined in https://www.w3.org/TR/html5/links.html#links and applicable on link elements + public const REL_ALTERNATE = 'alternate'; + public const REL_AUTHOR = 'author'; + public const REL_HELP = 'help'; + public const REL_ICON = 'icon'; + public const REL_LICENSE = 'license'; + public const REL_SEARCH = 'search'; + public const REL_STYLESHEET = 'stylesheet'; + public const REL_NEXT = 'next'; + public const REL_PREV = 'prev'; + + // Relation defined in https://www.w3.org/TR/preload/ + public const REL_PRELOAD = 'preload'; + + // Relations defined in https://www.w3.org/TR/resource-hints/ + public const REL_DNS_PREFETCH = 'dns-prefetch'; + public const REL_PRECONNECT = 'preconnect'; + public const REL_PREFETCH = 'prefetch'; + public const REL_PRERENDER = 'prerender'; + + // Extra relations + public const REL_MERCURE = 'mercure'; + + private $href = ''; + + /** + * @var string[] + */ + private $rel = []; + + /** + * @var string[] + */ + private $attributes = []; + + public function __construct(string $rel = null, string $href = '') + { + if (null !== $rel) { + $this->rel[$rel] = $rel; + } + $this->href = $href; + } + + /** + * {@inheritdoc} + */ + public function getHref(): string + { + return $this->href; + } + + /** + * {@inheritdoc} + */ + public function isTemplated(): bool + { + return $this->hrefIsTemplated($this->href); + } + + /** + * {@inheritdoc} + */ + public function getRels(): array + { + return array_values($this->rel); + } + + /** + * {@inheritdoc} + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function withHref($href) + { + $that = clone $this; + $that->href = $href; + $that->templated = $this->hrefIsTemplated($href); + + return $that; + } + + /** + * {@inheritdoc} + */ + public function withRel($rel) + { + $that = clone $this; + $that->rel[$rel] = $rel; + + return $that; + } + + /** + * {@inheritdoc} + */ + public function withoutRel($rel) + { + $that = clone $this; + unset($that->rel[$rel]); + + return $that; + } + + /** + * {@inheritdoc} + */ + public function withAttribute($attribute, $value) + { + $that = clone $this; + $that->attributes[$attribute] = $value; + + return $that; + } + + /** + * {@inheritdoc} + */ + public function withoutAttribute($attribute) + { + $that = clone $this; + unset($that->attributes[$attribute]); + + return $that; + } + + private function hrefIsTemplated(string $href): bool + { + return false !== strpos($href, '{') || false !== strpos($href, '}'); + } +} diff --git a/src/Symfony/Component/WebLink/Tests/EventListener/AddLinkHeaderListenerTest.php b/src/Symfony/Component/WebLink/Tests/EventListener/AddLinkHeaderListenerTest.php index a1b4a20787752..ddbbef7c263cf 100644 --- a/src/Symfony/Component/WebLink/Tests/EventListener/AddLinkHeaderListenerTest.php +++ b/src/Symfony/Component/WebLink/Tests/EventListener/AddLinkHeaderListenerTest.php @@ -11,8 +11,6 @@ namespace Symfony\Component\WebLink\Tests\EventListener; -use Fig\Link\GenericLinkProvider; -use Fig\Link\Link; use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; @@ -20,6 +18,8 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\Link; /** * @author Kévin Dunglas diff --git a/src/Symfony/Component/WebLink/Tests/GenericLinkProviderTest.php b/src/Symfony/Component/WebLink/Tests/GenericLinkProviderTest.php new file mode 100644 index 0000000000000..b4176fe7f7b5d --- /dev/null +++ b/src/Symfony/Component/WebLink/Tests/GenericLinkProviderTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\WebLink\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\WebLink\GenericLinkProvider; +use Symfony\Component\WebLink\Link; + +/** + * Test case borrowed from https://github.com/php-fig/link/. + */ +class GenericLinkProviderTest extends TestCase +{ + public function testCanAddLinksByMethod() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + + $provider = (new GenericLinkProvider()) + ->withLink($link); + + $this->assertContains($link, $provider->getLinks()); + } + + public function testCanAddLinksByConstructor() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + + $provider = (new GenericLinkProvider()) + ->withLink($link); + + $this->assertContains($link, $provider->getLinks()); + } + + public function testCanGetLinksByRel() + { + $link1 = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + $link2 = (new Link()) + ->withHref('http://www.php-fig.org/') + ->withRel('home') + ->withAttribute('me', 'you') + ; + + $provider = (new GenericLinkProvider()) + ->withLink($link1) + ->withLink($link2); + + $links = $provider->getLinksByRel('home'); + $this->assertContains($link2, $links); + $this->assertNotContains($link1, $links); + } + + public function testCanRemoveLinks() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + + $provider = (new GenericLinkProvider()) + ->withLink($link) + ->withoutLink($link); + + $this->assertNotContains($link, $provider->getLinks()); + } +} diff --git a/src/Symfony/Component/WebLink/Tests/HttpHeaderSerializerTest.php b/src/Symfony/Component/WebLink/Tests/HttpHeaderSerializerTest.php index 5347528ae02e6..fa50645a7251c 100644 --- a/src/Symfony/Component/WebLink/Tests/HttpHeaderSerializerTest.php +++ b/src/Symfony/Component/WebLink/Tests/HttpHeaderSerializerTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\WebLink\Tests; -use Fig\Link\Link; use PHPUnit\Framework\TestCase; use Symfony\Component\WebLink\HttpHeaderSerializer; +use Symfony\Component\WebLink\Link; class HttpHeaderSerializerTest extends TestCase { diff --git a/src/Symfony/Component/WebLink/Tests/LinkTest.php b/src/Symfony/Component/WebLink/Tests/LinkTest.php new file mode 100644 index 0000000000000..979bbb8b4f67e --- /dev/null +++ b/src/Symfony/Component/WebLink/Tests/LinkTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\WebLink\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\WebLink\Link; + +/** + * Test case borrowed from https://github.com/php-fig/link/. + */ +class LinkTest extends TestCase +{ + public function testCanSetAndRetrieveValues() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + + $this->assertEquals('http://www.google.com', $link->getHref()); + $this->assertContains('next', $link->getRels()); + $this->assertArrayHasKey('me', $link->getAttributes()); + $this->assertEquals('you', $link->getAttributes()['me']); + } + + public function testCanRemoveValues() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withAttribute('me', 'you') + ; + + $link = $link->withoutAttribute('me') + ->withoutRel('next'); + + $this->assertEquals('http://www.google.com', $link->getHref()); + $this->assertFalse(\in_array('next', $link->getRels())); + $this->assertArrayNotHasKey('me', $link->getAttributes()); + } + + public function testMultipleRels() + { + $link = (new Link()) + ->withHref('http://www.google.com') + ->withRel('next') + ->withRel('reference'); + + $this->assertCount(2, $link->getRels()); + $this->assertContains('next', $link->getRels()); + $this->assertContains('reference', $link->getRels()); + } + + public function testConstructor() + { + $link = new Link('next', 'http://www.google.com'); + + $this->assertEquals('http://www.google.com', $link->getHref()); + $this->assertContains('next', $link->getRels()); + } + + /** + * @dataProvider templatedHrefProvider + */ + public function testTemplated(string $href) + { + $link = (new Link()) + ->withHref($href); + + $this->assertTrue($link->isTemplated()); + } + + /** + * @dataProvider notTemplatedHrefProvider + */ + public function testNotTemplated(string $href) + { + $link = (new Link()) + ->withHref($href); + + $this->assertFalse($link->isTemplated()); + } + + public function templatedHrefProvider() + { + return [ + ['http://www.google.com/{param}/foo'], + ['http://www.google.com/foo?q={param}'], + ]; + } + + public function notTemplatedHrefProvider() + { + return [ + ['http://www.google.com/foo'], + ['/foo/bar/baz'], + ]; + } +} diff --git a/src/Symfony/Component/WebLink/composer.json b/src/Symfony/Component/WebLink/composer.json index ba00423565710..87eccfbffa087 100644 --- a/src/Symfony/Component/WebLink/composer.json +++ b/src/Symfony/Component/WebLink/composer.json @@ -15,10 +15,13 @@ "homepage": "https://symfony.com/contributors" } ], + "provide": { + "psr/link-implementation": "1.0" + }, "require": { "php": "^7.1.3", - "fig/link-util": "^1.0", - "psr/link": "^1.0" + "psr/link": "^1.0", + "symfony/polyfill-php72": "^1.5" }, "suggest": { "symfony/http-kernel": "" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy