-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Add a new Link component #22273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a new Link component #22273
Changes from 1 commit
b7a1591
d83b244
7be2a6c
66c4ed6
aaa54c2
c73ae6c
949a207
fc4d63b
aba1dbb
4a82c3d
b136372
fd2dd50
f0ce807
209901b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
|
||
namespace Symfony\Bridge\Twig\Extension; | ||
|
||
use Fig\Link\Link; | ||
use Symfony\Component\WebLink\WebLinkManagerInterface; | ||
|
||
/** | ||
|
@@ -20,11 +21,11 @@ | |
*/ | ||
class WebLinkExtension extends \Twig_Extension | ||
{ | ||
private $linkManager; | ||
private $manager; | ||
|
||
public function __construct(WebLinkManagerInterface $linkManager) | ||
public function __construct(WebLinkManagerInterface $manager) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of injecting this dep here, it would be better to create a Twig Runtime. |
||
{ | ||
$this->linkManager = $linkManager; | ||
$this->manager = $manager; | ||
} | ||
|
||
/** | ||
|
@@ -53,7 +54,12 @@ public function getFunctions() | |
*/ | ||
public function link($uri, $rel, array $attributes = array()) | ||
{ | ||
$this->linkManager->add($uri, $rel, $attributes); | ||
$link = new Link($rel, $uri); | ||
foreach ($attributes as $key => $value) { | ||
$link = $link->withAttribute($key, $value); | ||
} | ||
|
||
$this->manager->add($link); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as discussed with @dunglas on Slack, this means the manager is mutable, which conflicts with the fact that it is registered as a service |
||
|
||
return $uri; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ | |
<service id="web_link.manager" class="Symfony\Component\WebLink\WebLinkManager" public="false" /> | ||
<service id="Symfony\Component\WebLink\WebLinkManagerInterface" alias="web_link.manager" public="false" /> | ||
|
||
<service id="web_link.add_link_header_listener" class="Symfony\Component\Link\WebLink\HeaderListener" public="false"> | ||
<service id="web_link.add_link_header_listener" class="Symfony\Component\Link\WebLink\AddLinkHeaderListener" public="false"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like there is a typo in class name here, should be: |
||
<argument type="service" id="web_link.manager" /> | ||
|
||
<tag name="kernel.event_subscriber" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
|
||
namespace Symfony\Component\WebLink\EventListener; | ||
|
||
use Symfony\Component\WebLink\HttpHeaderSerializer; | ||
use Symfony\Component\WebLink\WebLinkManagerInterface; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alpha order |
||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; | ||
|
@@ -25,11 +26,13 @@ | |
*/ | ||
class AddLinkHeaderListener implements EventSubscriberInterface | ||
{ | ||
private $linkManager; | ||
private $manager; | ||
private $serializer; | ||
|
||
public function __construct(WebLinkManagerInterface $linkManager) | ||
public function __construct(WebLinkManagerInterface $manager) | ||
{ | ||
$this->linkManager = $linkManager; | ||
$this->manager = $manager; | ||
$this->serializer = new HttpHeaderSerializer(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. having this class be instantiated directly here means it's an internal implementation detail to the listener There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's indeed an internal detail of the listener. But the I would keep this as is (btw the serializer is |
||
} | ||
|
||
public function onKernelResponse(FilterResponseEvent $event) | ||
|
@@ -38,11 +41,11 @@ public function onKernelResponse(FilterResponseEvent $event) | |
return; | ||
} | ||
|
||
if ($value = $this->linkManager->buildHeaderValue()) { | ||
if ($value = $this->serializer->serialize($this->manager->getLinkProvider())) { | ||
$event->getResponse()->headers->set('Link', $value, false); | ||
|
||
// Free memory | ||
$this->linkManager->clear(); | ||
$this->manager->clear(); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?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\LinkProviderInterface; | ||
|
||
/** | ||
* Serializes a list of Link instances to a HTTP Link header. | ||
* | ||
* @see https://tools.ietf.org/html/rfc5988 | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
final class HttpHeaderSerializer | ||
{ | ||
/** | ||
* Builds the value of the "Link" HTTP header. | ||
* | ||
* @param LinkProviderInterface $linkProvider | ||
* | ||
* @return string|null | ||
*/ | ||
public function serialize(LinkProviderInterface $linkProvider) | ||
{ | ||
$elements = array(); | ||
foreach ($linkProvider->getLinks() as $link) { | ||
if ($link->isTemplated()) { | ||
continue; | ||
} | ||
|
||
$attributesParts = array('', sprintf('rel="%s"', implode(' ', $link->getRels()))); | ||
foreach ($link->getAttributes() as $key => $value) { | ||
if (is_array($value)) { | ||
foreach ($value as $v) { | ||
$attributesParts[] = sprintf('%s="%s"', $key, $v); | ||
} | ||
|
||
continue; | ||
} | ||
|
||
if (!is_bool($value)) { | ||
$attributesParts[] = sprintf('%s="%s"', $key, $value); | ||
|
||
continue; | ||
} | ||
|
||
if (true === $value) { | ||
$attributesParts[] = $key; | ||
} | ||
} | ||
|
||
$elements[] = sprintf('<%s>%s', $link->getHref(), implode('; ', $attributesParts)); | ||
} | ||
|
||
return $elements ? implode(',', $elements) : null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?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 Fig\Link\GenericLinkProvider; | ||
use Fig\Link\Link; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\WebLink\HttpHeaderSerializer; | ||
|
||
class HttpHeaderSerializerTest extends TestCase | ||
{ | ||
/** | ||
* @var HttpHeaderSerializer | ||
*/ | ||
private $serializer; | ||
|
||
protected function setUp() { | ||
$this->serializer = new HttpHeaderSerializer(); | ||
} | ||
|
||
public function testSerialize() { | ||
$linkProvider = new GenericLinkProvider(array( | ||
new Link('prerender', '/1'), | ||
(new Link('dns-prefetch', '/2'))->withAttribute('pr', 0.7), | ||
(new Link('preload', '/3'))->withAttribute('as', 'script')->withAttribute('nopush', false), | ||
(new Link('preload', '/4'))->withAttribute('as', 'image')->withAttribute('nopush', true), | ||
(new Link('alternate', '/5'))->withRel('next')->withAttribute('hreflang', array('fr', 'de'))->withAttribute('title', 'Hello'), | ||
)); | ||
|
||
$this->assertEquals('</1>; rel="prerender",</2>; rel="dns-prefetch"; pr="0.7",</3>; rel="preload"; as="script",</4>; rel="preload"; as="image"; nopush,</5>; rel="alternate next"; hreflang="fr"; hreflang="de"; title="Hello"', $this->serializer->serialize($linkProvider)); | ||
} | ||
|
||
public function testSerializeEmpty() { | ||
$this->assertNull($this->serializer->serialize(new GenericLinkProvider())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
|
||
namespace Symfony\Component\WebLink\Tests; | ||
|
||
use Fig\Link\Link; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\WebLink\WebLinkManager; | ||
use Symfony\Component\WebLink\WebLinkManagerInterface; | ||
|
@@ -20,17 +21,32 @@ | |
*/ | ||
class WebLinkManagerTest extends TestCase | ||
{ | ||
public function testManageResources() | ||
/** | ||
* @var WebLinkManager | ||
*/ | ||
private $manager; | ||
|
||
protected function setUp() | ||
{ | ||
$this->manager = new WebLinkManager(); | ||
} | ||
|
||
public function testAdd() | ||
{ | ||
$manager = new WebLinkManager(); | ||
$this->assertInstanceOf(WebLinkManagerInterface::class, $manager); | ||
$this->assertInstanceOf(WebLinkManagerInterface::class, $this->manager); | ||
|
||
$this->manager->add($link1 = new Link()); | ||
$this->manager->add($link2 = new Link()); | ||
|
||
$this->assertSame(array($link1, $link2), array_values($this->manager->getLinkProvider()->getLinks())); | ||
} | ||
|
||
$manager->add('/hello.html', 'prerender', array('pr' => 0.7)); | ||
public function testClear() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
$this->manager->add($link1 = new Link()); | ||
$this->manager->add($link2 = new Link()); | ||
|
||
$manager->add('/foo/bar.js', 'preload', array('as' => 'script', 'nopush' => false)); | ||
$manager->add('/foo/baz.css', 'preload'); | ||
$manager->add('/foo/bat.png', 'preload', array('as' => 'image', 'nopush' => true)); | ||
$this->manager->clear(); | ||
|
||
$this->assertEquals('</hello.html>; rel=prerender; pr=0.7,</foo/bar.js>; rel=preload; as=script,</foo/baz.css>; rel=preload,</foo/bat.png>; rel=preload; as=image; nopush', $manager->buildHeaderValue()); | ||
$this->assertEmpty($this->manager->getLinkProvider()->getLinks()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this dep is in require-dev in all components, but in require here, looks suspicious
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this package in the Twig extension and the FrameworkBundle (even at runtime). But the those packages have only a soft dependency to WebLink. In the context of the full stack, I think it makes sense to add it to have something working out of the box.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not
require
d by any of the packages inreplace
, so it should indeed remain inrequire-dev
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is required if you use WebLink && (Twig Bridge || Framework Bundle). We cannot express such conditions with Composer, but the full stack framework install WebLink + Twig Bridge + Framework Bundle so it needs to have this package as dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still looks inconsistent to me. symfony/symfony should not behave differently than using standalone components...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fig/link-util
added back as a dependency of the WebLink component. It fixes the inconsistency, makes the component easier to use for end users and... it's the only one implementation of PSR-13 for now.