feature #33122 [WebLink] implement PSR-13 directly (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[WebLink] implement PSR-13 directly

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Implementing PSR-13 is simple enough and the repo we're using is [freezed](https://github.com/php-fig/link-util/pulls).

This also allows us to add some type declarations. We're going to need them before merging #30323.

Commits
-------

b570ee1103 [WebLink] implement PSR-13 directly
This commit is contained in:
Nicolas Grekas 2019-08-12 17:24:33 +02:00
commit 3bc4e4f00d
15 changed files with 454 additions and 16 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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 <dunglas@gmail.com>

View File

@ -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": {

View File

@ -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\Routing\Generator\UrlGeneratorInterface;
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 @@ trait ControllerTrait
*
* @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".');

View File

@ -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\AnonymousToken;
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
{

View File

@ -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"

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
4.4.0
-----
* implement PSR-13 directly
3.3.0
-----

View File

@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}

View File

@ -0,0 +1,153 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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, '}');
}
}

View File

@ -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\HttpFoundation\Response;
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 <dunglas@gmail.com>

View File

@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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());
}
}

View File

@ -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
{

View File

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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'],
];
}
}

View File

@ -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": ""