Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5a76834

Browse filesBrowse files
committed
feature #22273 Add a new Link component (dunglas)
This PR was squashed before being merged into the 3.3-dev branch (closes #22273). Discussion ---------- Add a new Link component | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? |no <!-- don't forget updating src/**/CHANGELOG.md files --> | BC breaks? | no | Deprecations? | no <!-- don't forget updating UPGRADE-*.md files --> | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | todo This a proposal to extract HTTP preloading features introduced in #21478 in their own component. There are some good reasons to do it: * HTTP preloading is not (only) about assets: this standalone component could be very useful to replace resources embedding in APIs by HTTP/2 pushes like described in [this article](https://evertpot.com/rest-embedding-hal-http2/) by @evert. In such case, there is no reason to carry the whole asset component for an API. * There is no dependency nor relation at all between the code of the asset compnent and the one I've added for Preloading features * It makes the code cleaner (no more optional dependency in the `Asset` Twig extension) This component would also better fit in HttpFoundation than in Asset. But there is no dependency between it and HttpFoundation and it can easily be used with PSR-7 too, so IMO it better belongs in a standalone component. Btw, ~~~I plan to add support for prefetching to this component. Except a PR soon.~~~ Prefetching and prerendering support added in this PR. ping @symfony/deciders Commits ------- 053de25 Add a new Link component
2 parents aada1a1 + 053de25 commit 5a76834
Copy full SHA for 5a76834
Expand file treeCollapse file tree

33 files changed

+580
-245
lines changed

‎composer.json

Copy file name to clipboardExpand all lines: composer.json
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
"require": {
1919
"php": ">=5.5.9",
2020
"doctrine/common": "~2.4",
21+
"fig/link-util": "^1.0",
2122
"twig/twig": "~1.32|~2.2",
2223
"psr/cache": "~1.0",
2324
"psr/container": "^1.0",
25+
"psr/link": "^1.0",
2426
"psr/log": "~1.0",
2527
"psr/simple-cache": "^1.0",
2628
"symfony/polyfill-intl-icu": "~1.0",
@@ -76,6 +78,7 @@
7678
"symfony/twig-bundle": "self.version",
7779
"symfony/validator": "self.version",
7880
"symfony/var-dumper": "self.version",
81+
"symfony/web-link": "self.version",
7982
"symfony/web-profiler-bundle": "self.version",
8083
"symfony/web-server-bundle": "self.version",
8184
"symfony/workflow": "self.version",

‎src/Symfony/Bridge/Twig/Extension/AssetExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Extension/AssetExtension.php
+1-25Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Bridge\Twig\Extension;
1313

1414
use Symfony\Component\Asset\Packages;
15-
use Symfony\Component\Asset\Preload\PreloadManagerInterface;
1615

1716
/**
1817
* Twig extension for the Symfony Asset component.
@@ -22,12 +21,10 @@
2221
class AssetExtension extends \Twig_Extension
2322
{
2423
private $packages;
25-
private $preloadManager;
2624

27-
public function __construct(Packages $packages, PreloadManagerInterface $preloadManager = null)
25+
public function __construct(Packages $packages)
2826
{
2927
$this->packages = $packages;
30-
$this->preloadManager = $preloadManager;
3128
}
3229

3330
/**
@@ -38,7 +35,6 @@ public function getFunctions()
3835
return array(
3936
new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')),
4037
new \Twig_SimpleFunction('asset_version', array($this, 'getAssetVersion')),
41-
new \Twig_SimpleFunction('preload', array($this, 'preload')),
4238
);
4339
}
4440

@@ -71,26 +67,6 @@ public function getAssetVersion($path, $packageName = null)
7167
return $this->packages->getVersion($path, $packageName);
7268
}
7369

74-
/**
75-
* Preloads an asset.
76-
*
77-
* @param string $path A public path
78-
* @param string $as A valid destination according to https://fetch.spec.whatwg.org/#concept-request-destination
79-
* @param bool $nopush If this asset should not be pushed over HTTP/2
80-
*
81-
* @return string The path of the asset
82-
*/
83-
public function preload($path, $as = '', $nopush = false)
84-
{
85-
if (null === $this->preloadManager) {
86-
throw new \RuntimeException('A preload manager must be configured to use the "preload" function.');
87-
}
88-
89-
$this->preloadManager->addResource($path, $as, $nopush);
90-
91-
return $path;
92-
}
93-
9470
/**
9571
* Returns the name of the extension.
9672
*
+137Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Extension;
13+
14+
use Fig\Link\GenericLinkProvider;
15+
use Fig\Link\Link;
16+
use Symfony\Component\HttpFoundation\RequestStack;
17+
18+
/**
19+
* Twig extension for the Symfony WebLink component.
20+
*
21+
* @author Kévin Dunglas <dunglas@gmail.com>
22+
*/
23+
class WebLinkExtension extends \Twig_Extension
24+
{
25+
private $requestStack;
26+
27+
public function __construct(RequestStack $requestStack)
28+
{
29+
$this->requestStack = $requestStack;
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function getFunctions()
36+
{
37+
return array(
38+
new \Twig_SimpleFunction('link', array($this, 'link')),
39+
new \Twig_SimpleFunction('preload', array($this, 'preload')),
40+
new \Twig_SimpleFunction('dns_prefetch', array($this, 'dnsPrefetch')),
41+
new \Twig_SimpleFunction('preconnect', array($this, 'preconnect')),
42+
new \Twig_SimpleFunction('prefetch', array($this, 'prefetch')),
43+
new \Twig_SimpleFunction('prerender', array($this, 'prerender')),
44+
);
45+
}
46+
47+
/**
48+
* Adds a "Link" HTTP header.
49+
*
50+
* @param string $uri The relation URI
51+
* @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
52+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
53+
*
54+
* @return string The relation URI
55+
*/
56+
public function link($uri, $rel, array $attributes = array())
57+
{
58+
if (!$request = $this->requestStack->getMasterRequest()) {
59+
return $uri;
60+
}
61+
62+
$link = new Link($rel, $uri);
63+
foreach ($attributes as $key => $value) {
64+
$link = $link->withAttribute($key, $value);
65+
}
66+
67+
$linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
68+
$request->attributes->set('_links', $linkProvider->withLink($link));
69+
70+
return $uri;
71+
}
72+
73+
/**
74+
* Preloads a resource.
75+
*
76+
* @param string $uri A public path
77+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('crossorigin' => 'use-credentials')")
78+
*
79+
* @return string The path of the asset
80+
*/
81+
public function preload($uri, array $attributes = array())
82+
{
83+
return $this->link($uri, 'preload', $attributes);
84+
}
85+
86+
/**
87+
* Resolves a resource origin as early as possible.
88+
*
89+
* @param string $uri A public path
90+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
91+
*
92+
* @return string The path of the asset
93+
*/
94+
public function dnsPrefetch($uri, array $attributes = array())
95+
{
96+
return $this->link($uri, 'dns-prefetch', $attributes);
97+
}
98+
99+
/**
100+
* Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
101+
*
102+
* @param string $uri A public path
103+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
104+
*
105+
* @return string The path of the asset
106+
*/
107+
public function preconnect($uri, array $attributes = array())
108+
{
109+
return $this->link($uri, 'preconnect', $attributes);
110+
}
111+
112+
/**
113+
* Indicates to the client that it should prefetch this resource.
114+
*
115+
* @param string $uri A public path
116+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
117+
*
118+
* @return string The path of the asset
119+
*/
120+
public function prefetch($uri, array $attributes = array())
121+
{
122+
return $this->link($uri, 'prefetch', $attributes);
123+
}
124+
125+
/**
126+
* Indicates to the client that it should prerender this resource .
127+
*
128+
* @param string $uri A public path
129+
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
130+
*
131+
* @return string The path of the asset
132+
*/
133+
public function prerender($uri, array $attributes = array())
134+
{
135+
return $this->link($uri, 'prerender', $attributes);
136+
}
137+
}

‎src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Tests/Extension/AssetExtensionTest.php
-45Lines changed: 0 additions & 45 deletions
This file was deleted.
+92Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Tests\Extension;
13+
14+
use Fig\Link\Link;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Bridge\Twig\Extension\WebLinkExtension;
17+
use Symfony\Component\HttpFoundation\Request;
18+
use Symfony\Component\HttpFoundation\RequestStack;
19+
20+
/**
21+
* @author Kévin Dunglas <dunglas@gmail.com>
22+
*/
23+
class WebLinkExtensionTest extends TestCase
24+
{
25+
/**
26+
* @var Request
27+
*/
28+
private $request;
29+
30+
/**
31+
* @var WebLinkExtension
32+
*/
33+
private $extension;
34+
35+
protected function setUp()
36+
{
37+
$this->request = new Request();
38+
39+
$requestStack = new RequestStack();
40+
$requestStack->push($this->request);
41+
42+
$this->extension = new WebLinkExtension($requestStack);
43+
}
44+
45+
public function testLink()
46+
{
47+
$this->assertEquals('/foo.css', $this->extension->link('/foo.css', 'preload', array('as' => 'style', 'nopush' => true)));
48+
49+
$link = (new Link('preload', '/foo.css'))->withAttribute('as', 'style')->withAttribute('nopush', true);
50+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
51+
}
52+
53+
public function testPreload()
54+
{
55+
$this->assertEquals('/foo.css', $this->extension->preload('/foo.css', array('as' => 'style', 'crossorigin' => true)));
56+
57+
$link = (new Link('preload', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
58+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
59+
}
60+
61+
public function testDnsPrefetch()
62+
{
63+
$this->assertEquals('/foo.css', $this->extension->dnsPrefetch('/foo.css', array('as' => 'style', 'crossorigin' => true)));
64+
65+
$link = (new Link('dns-prefetch', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
66+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
67+
}
68+
69+
public function testPreconnect()
70+
{
71+
$this->assertEquals('/foo.css', $this->extension->preconnect('/foo.css', array('as' => 'style', 'crossorigin' => true)));
72+
73+
$link = (new Link('preconnect', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
74+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
75+
}
76+
77+
public function testPrefetch()
78+
{
79+
$this->assertEquals('/foo.css', $this->extension->prefetch('/foo.css', array('as' => 'style', 'crossorigin' => true)));
80+
81+
$link = (new Link('prefetch', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
82+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
83+
}
84+
85+
public function testPrerender()
86+
{
87+
$this->assertEquals('/foo.css', $this->extension->prerender('/foo.css', array('as' => 'style', 'crossorigin' => true)));
88+
89+
$link = (new Link('prerender', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
90+
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
91+
}
92+
}

‎src/Symfony/Bridge/Twig/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/composer.json
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"twig/twig": "~1.28|~2.0"
2121
},
2222
"require-dev": {
23+
"fig/link-util": "^1.0",
2324
"symfony/asset": "~2.8|~3.0",
2425
"symfony/finder": "~2.8|~3.0",
2526
"symfony/form": "^3.2.5",
@@ -34,7 +35,8 @@
3435
"symfony/stopwatch": "~2.8|~3.0",
3536
"symfony/console": "~2.8|~3.0",
3637
"symfony/var-dumper": "~2.8.10|~3.1.4|~3.2",
37-
"symfony/expression-language": "~2.8|~3.0"
38+
"symfony/expression-language": "~2.8|~3.0",
39+
"symfony/web-link": "~3.3"
3840
},
3941
"suggest": {
4042
"symfony/finder": "",
@@ -48,7 +50,8 @@
4850
"symfony/security": "For using the SecurityExtension",
4951
"symfony/stopwatch": "For using the StopwatchExtension",
5052
"symfony/var-dumper": "For using the DumpExtension",
51-
"symfony/expression-language": "For using the ExpressionExtension"
53+
"symfony/expression-language": "For using the ExpressionExtension",
54+
"symfony/web-link": "For using the WebLinkExtension"
5255
},
5356
"autoload": {
5457
"psr-4": { "Symfony\\Bridge\\Twig\\": "" },

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.