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
-------
053de25edf
Add a new Link component
This commit is contained in:
commit
5a76834aea
@ -18,9 +18,11 @@
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"doctrine/common": "~2.4",
|
||||
"fig/link-util": "^1.0",
|
||||
"twig/twig": "~1.32|~2.2",
|
||||
"psr/cache": "~1.0",
|
||||
"psr/container": "^1.0",
|
||||
"psr/link": "^1.0",
|
||||
"psr/log": "~1.0",
|
||||
"psr/simple-cache": "^1.0",
|
||||
"symfony/polyfill-intl-icu": "~1.0",
|
||||
@ -76,6 +78,7 @@
|
||||
"symfony/twig-bundle": "self.version",
|
||||
"symfony/validator": "self.version",
|
||||
"symfony/var-dumper": "self.version",
|
||||
"symfony/web-link": "self.version",
|
||||
"symfony/web-profiler-bundle": "self.version",
|
||||
"symfony/web-server-bundle": "self.version",
|
||||
"symfony/workflow": "self.version",
|
||||
|
@ -12,7 +12,6 @@
|
||||
namespace Symfony\Bridge\Twig\Extension;
|
||||
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\Asset\Preload\PreloadManagerInterface;
|
||||
|
||||
/**
|
||||
* Twig extension for the Symfony Asset component.
|
||||
@ -22,12 +21,10 @@ use Symfony\Component\Asset\Preload\PreloadManagerInterface;
|
||||
class AssetExtension extends \Twig_Extension
|
||||
{
|
||||
private $packages;
|
||||
private $preloadManager;
|
||||
|
||||
public function __construct(Packages $packages, PreloadManagerInterface $preloadManager = null)
|
||||
public function __construct(Packages $packages)
|
||||
{
|
||||
$this->packages = $packages;
|
||||
$this->preloadManager = $preloadManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +35,6 @@ class AssetExtension extends \Twig_Extension
|
||||
return array(
|
||||
new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')),
|
||||
new \Twig_SimpleFunction('asset_version', array($this, 'getAssetVersion')),
|
||||
new \Twig_SimpleFunction('preload', array($this, 'preload')),
|
||||
);
|
||||
}
|
||||
|
||||
@ -71,26 +67,6 @@ class AssetExtension extends \Twig_Extension
|
||||
return $this->packages->getVersion($path, $packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads an asset.
|
||||
*
|
||||
* @param string $path A public path
|
||||
* @param string $as A valid destination according to https://fetch.spec.whatwg.org/#concept-request-destination
|
||||
* @param bool $nopush If this asset should not be pushed over HTTP/2
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function preload($path, $as = '', $nopush = false)
|
||||
{
|
||||
if (null === $this->preloadManager) {
|
||||
throw new \RuntimeException('A preload manager must be configured to use the "preload" function.');
|
||||
}
|
||||
|
||||
$this->preloadManager->addResource($path, $as, $nopush);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the extension.
|
||||
*
|
||||
|
137
src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
Normal file
137
src/Symfony/Bridge/Twig/Extension/WebLinkExtension.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?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\Bridge\Twig\Extension;
|
||||
|
||||
use Fig\Link\GenericLinkProvider;
|
||||
use Fig\Link\Link;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Twig extension for the Symfony WebLink component.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class WebLinkExtension extends \Twig_Extension
|
||||
{
|
||||
private $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return array(
|
||||
new \Twig_SimpleFunction('link', array($this, 'link')),
|
||||
new \Twig_SimpleFunction('preload', array($this, 'preload')),
|
||||
new \Twig_SimpleFunction('dns_prefetch', array($this, 'dnsPrefetch')),
|
||||
new \Twig_SimpleFunction('preconnect', array($this, 'preconnect')),
|
||||
new \Twig_SimpleFunction('prefetch', array($this, 'prefetch')),
|
||||
new \Twig_SimpleFunction('prerender', array($this, 'prerender')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "Link" HTTP header.
|
||||
*
|
||||
* @param string $uri The relation URI
|
||||
* @param string $rel The relation type (e.g. "preload", "prefetch", "prerender" or "dns-prefetch")
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
|
||||
*
|
||||
* @return string The relation URI
|
||||
*/
|
||||
public function link($uri, $rel, array $attributes = array())
|
||||
{
|
||||
if (!$request = $this->requestStack->getMasterRequest()) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$link = new Link($rel, $uri);
|
||||
foreach ($attributes as $key => $value) {
|
||||
$link = $link->withAttribute($key, $value);
|
||||
}
|
||||
|
||||
$linkProvider = $request->attributes->get('_links', new GenericLinkProvider());
|
||||
$request->attributes->set('_links', $linkProvider->withLink($link));
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads a resource.
|
||||
*
|
||||
* @param string $uri A public path
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('crossorigin' => 'use-credentials')")
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function preload($uri, array $attributes = array())
|
||||
{
|
||||
return $this->link($uri, 'preload', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a resource origin as early as possible.
|
||||
*
|
||||
* @param string $uri A public path
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function dnsPrefetch($uri, array $attributes = array())
|
||||
{
|
||||
return $this->link($uri, 'dns-prefetch', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a early connection to a resource (DNS resolution, TCP handshake, TLS negotiation).
|
||||
*
|
||||
* @param string $uri A public path
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function preconnect($uri, array $attributes = array())
|
||||
{
|
||||
return $this->link($uri, 'preconnect', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates to the client that it should prefetch this resource.
|
||||
*
|
||||
* @param string $uri A public path
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function prefetch($uri, array $attributes = array())
|
||||
{
|
||||
return $this->link($uri, 'prefetch', $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates to the client that it should prerender this resource .
|
||||
*
|
||||
* @param string $uri A public path
|
||||
* @param array $attributes The attributes of this link (e.g. "array('as' => true)", "array('pr' => 0.5)")
|
||||
*
|
||||
* @return string The path of the asset
|
||||
*/
|
||||
public function prerender($uri, array $attributes = array())
|
||||
{
|
||||
return $this->link($uri, 'prerender', $attributes);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?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\Bridge\Twig\Tests\Extension;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Twig\Extension\AssetExtension;
|
||||
use Symfony\Component\Asset\Packages;
|
||||
use Symfony\Component\Asset\Preload\PreloadManager;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class AssetExtensionTest extends TestCase
|
||||
{
|
||||
public function testGetAndPreloadAssetUrl()
|
||||
{
|
||||
if (!class_exists(PreloadManager::class)) {
|
||||
$this->markTestSkipped('Requires Asset 3.3+.');
|
||||
}
|
||||
|
||||
$preloadManager = new PreloadManager();
|
||||
$extension = new AssetExtension(new Packages(), $preloadManager);
|
||||
|
||||
$this->assertEquals('/foo.css', $extension->preload('/foo.css', 'style', true));
|
||||
$this->assertEquals('</foo.css>; rel=preload; as=style; nopush', $preloadManager->buildLinkValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \RuntimeException
|
||||
*/
|
||||
public function testNoConfiguredPreloadManager()
|
||||
{
|
||||
$extension = new AssetExtension(new Packages());
|
||||
$extension->preload('/foo.css');
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?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\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;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class WebLinkExtensionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @var WebLinkExtension
|
||||
*/
|
||||
private $extension;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->request = new Request();
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
$requestStack->push($this->request);
|
||||
|
||||
$this->extension = new WebLinkExtension($requestStack);
|
||||
}
|
||||
|
||||
public function testLink()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->link('/foo.css', 'preload', array('as' => 'style', 'nopush' => true)));
|
||||
|
||||
$link = (new Link('preload', '/foo.css'))->withAttribute('as', 'style')->withAttribute('nopush', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
|
||||
public function testPreload()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->preload('/foo.css', array('as' => 'style', 'crossorigin' => true)));
|
||||
|
||||
$link = (new Link('preload', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
|
||||
public function testDnsPrefetch()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->dnsPrefetch('/foo.css', array('as' => 'style', 'crossorigin' => true)));
|
||||
|
||||
$link = (new Link('dns-prefetch', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
|
||||
public function testPreconnect()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->preconnect('/foo.css', array('as' => 'style', 'crossorigin' => true)));
|
||||
|
||||
$link = (new Link('preconnect', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
|
||||
public function testPrefetch()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->prefetch('/foo.css', array('as' => 'style', 'crossorigin' => true)));
|
||||
|
||||
$link = (new Link('prefetch', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
|
||||
public function testPrerender()
|
||||
{
|
||||
$this->assertEquals('/foo.css', $this->extension->prerender('/foo.css', array('as' => 'style', 'crossorigin' => true)));
|
||||
|
||||
$link = (new Link('prerender', '/foo.css'))->withAttribute('as', 'style')->withAttribute('crossorigin', true);
|
||||
$this->assertEquals(array($link), array_values($this->request->attributes->get('_links')->getLinks()));
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@
|
||||
"twig/twig": "~1.28|~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fig/link-util": "^1.0",
|
||||
"symfony/asset": "~2.8|~3.0",
|
||||
"symfony/finder": "~2.8|~3.0",
|
||||
"symfony/form": "^3.2.5",
|
||||
@ -34,7 +35,8 @@
|
||||
"symfony/stopwatch": "~2.8|~3.0",
|
||||
"symfony/console": "~2.8|~3.0",
|
||||
"symfony/var-dumper": "~2.8.10|~3.1.4|~3.2",
|
||||
"symfony/expression-language": "~2.8|~3.0"
|
||||
"symfony/expression-language": "~2.8|~3.0",
|
||||
"symfony/web-link": "~3.3"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/finder": "",
|
||||
@ -48,7 +50,8 @@
|
||||
"symfony/security": "For using the SecurityExtension",
|
||||
"symfony/stopwatch": "For using the StopwatchExtension",
|
||||
"symfony/var-dumper": "For using the DumpExtension",
|
||||
"symfony/expression-language": "For using the ExpressionExtension"
|
||||
"symfony/expression-language": "For using the ExpressionExtension",
|
||||
"symfony/web-link": "For using the WebLinkExtension"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bridge\\Twig\\": "" },
|
||||
|
@ -18,10 +18,10 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Translation\Translator;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
|
||||
/**
|
||||
* FrameworkExtension configuration structure.
|
||||
@ -101,6 +101,7 @@ class Configuration implements ConfigurationInterface
|
||||
$this->addPropertyInfoSection($rootNode);
|
||||
$this->addCacheSection($rootNode);
|
||||
$this->addPhpErrorsSection($rootNode);
|
||||
$this->addWebLinkSection($rootNode);
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
@ -806,4 +807,16 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
private function addWebLinkSection(ArrayNodeDefinition $rootNode)
|
||||
{
|
||||
$rootNode
|
||||
->children()
|
||||
->arrayNode('web_link')
|
||||
->info('web links configuration')
|
||||
->{!class_exists(FullStack::class) && class_exists(HttpHeaderSerializer::class) ? 'canBeDisabled' : 'canBeEnabled'}()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,10 @@ use Doctrine\Common\Annotations\Reader;
|
||||
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
|
||||
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\DependencyInjection\Alias;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@ -28,7 +30,6 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\Serializer\Encoder\YamlEncoder;
|
||||
use Symfony\Component\Serializer\Encoder\CsvEncoder;
|
||||
@ -36,8 +37,8 @@ use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
use Symfony\Component\Workflow;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/**
|
||||
* FrameworkExtension.
|
||||
@ -208,6 +209,14 @@ class FrameworkExtension extends Extension
|
||||
$this->registerPropertyInfoConfiguration($config['property_info'], $container, $loader);
|
||||
}
|
||||
|
||||
if ($this->isConfigEnabled($container, $config['web_link'])) {
|
||||
if (!class_exists(HttpHeaderSerializer::class)) {
|
||||
throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed.');
|
||||
}
|
||||
|
||||
$loader->load('web_link.xml');
|
||||
}
|
||||
|
||||
$this->addAnnotatedClassesToCompile(array(
|
||||
'**Bundle\\Controller\\',
|
||||
'**Bundle\\Entity\\',
|
||||
|
@ -43,13 +43,5 @@
|
||||
<argument /> <!-- manifest path -->
|
||||
</service>
|
||||
|
||||
<service id="assets.preload_manager" class="Symfony\Component\Asset\Preload\PreloadManager" public="false" />
|
||||
|
||||
<service id="asset.preload_listener" class="Symfony\Component\Asset\EventListener\PreloadListener">
|
||||
<argument type="service" id="assets.preload_manager" />
|
||||
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
|
||||
</services>
|
||||
</container>
|
||||
|
@ -14,6 +14,7 @@
|
||||
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="esi" type="esi" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="fragments" type="fragments" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="web-link" type="web_link" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="profiler" type="profiler" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="router" type="router" minOccurs="0" maxOccurs="1" />
|
||||
<xsd:element name="session" type="session" minOccurs="0" maxOccurs="1" />
|
||||
@ -62,6 +63,10 @@
|
||||
<xsd:attribute name="path" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="web_link">
|
||||
<xsd:attribute name="enabled" type="xsd:boolean" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="profiler">
|
||||
<xsd:all>
|
||||
<xsd:element name="matcher" type="profiler_matcher" minOccurs="0" maxOccurs="1" />
|
||||
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
|
||||
<service id="web_link.add_link_header_listener" class="Symfony\Component\WebLink\AddLinkHeaderListener" public="false">
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
|
||||
</services>
|
||||
</container>
|
@ -238,6 +238,9 @@ class ConfigurationTest extends TestCase
|
||||
'log' => true,
|
||||
'throw' => true,
|
||||
),
|
||||
'web_link' => array(
|
||||
'enabled' => !class_exists(FullStack::class),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
$container->loadFromExtension('framework', array(
|
||||
'web_link' => array('enabled' => true),
|
||||
));
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
|
||||
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
||||
|
||||
<framework:config>
|
||||
<framework:web-link enabled="true" />
|
||||
</framework:config>
|
||||
</container>
|
@ -0,0 +1,3 @@
|
||||
framework:
|
||||
web_link:
|
||||
enabled: true
|
@ -408,10 +408,10 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
$this->assertEquals('assets.custom_version_strategy', (string) $defaultPackage->getArgument(1));
|
||||
}
|
||||
|
||||
public function testAssetHasPreloadListener()
|
||||
public function testWebLink()
|
||||
{
|
||||
$container = $this->createContainerFromFile('assets');
|
||||
$this->assertTrue($container->hasDefinition('asset.preload_listener'));
|
||||
$container = $this->createContainerFromFile('web_link');
|
||||
$this->assertTrue($container->hasDefinition('web_link.add_link_header_listener'));
|
||||
}
|
||||
|
||||
public function testTranslator()
|
||||
|
@ -34,6 +34,7 @@
|
||||
"doctrine/cache": "~1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fig/link-util": "^1.0",
|
||||
"symfony/asset": "~2.8|~3.0",
|
||||
"symfony/browser-kit": "~2.8|~3.0",
|
||||
"symfony/console": "~3.3",
|
||||
@ -53,6 +54,7 @@
|
||||
"symfony/workflow": "~3.3",
|
||||
"symfony/yaml": "~3.2",
|
||||
"symfony/property-info": "~3.3",
|
||||
"symfony/web-link": "~3.3",
|
||||
"doctrine/annotations": "~1.0",
|
||||
"phpdocumentor/reflection-docblock": "^3.0",
|
||||
"twig/twig": "~1.26|~2.0",
|
||||
@ -79,7 +81,8 @@
|
||||
"symfony/validator": "For using validation",
|
||||
"symfony/yaml": "For using the debug:config and lint:yaml commands",
|
||||
"symfony/property-info": "For using the property_info service",
|
||||
"symfony/process": "For using the server:run, server:start, server:stop, and server:status commands"
|
||||
"symfony/process": "For using the server:run, server:start, server:stop, and server:status commands",
|
||||
"symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bundle\\FrameworkBundle\\": "" },
|
||||
|
@ -11,11 +11,13 @@
|
||||
|
||||
namespace Symfony\Bundle\TwigBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Bridge\Twig\Extension\WebLinkExtension;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
|
||||
/**
|
||||
* TwigExtension.
|
||||
@ -48,6 +50,12 @@ class TwigExtension extends Extension
|
||||
$container->removeDefinition('twig.translation.extractor');
|
||||
}
|
||||
|
||||
if (class_exists(HttpHeaderSerializer::class)) {
|
||||
$definition = $container->register('twig.extension.weblink', WebLinkExtension::class);
|
||||
$definition->setPublic(false);
|
||||
$definition->addArgument(new Reference('request_stack'));
|
||||
}
|
||||
|
||||
foreach ($configs as $key => $config) {
|
||||
if (isset($config['globals'])) {
|
||||
foreach ($config['globals'] as $name => $value) {
|
||||
|
@ -72,7 +72,6 @@
|
||||
|
||||
<service id="twig.extension.assets" class="Symfony\Bridge\Twig\Extension\AssetExtension" public="false">
|
||||
<argument type="service" id="assets.packages" />
|
||||
<argument type="service" id="assets.preload_manager" on-invalid="ignore" />
|
||||
</service>
|
||||
|
||||
<service id="twig.extension.code" class="Symfony\Bridge\Twig\Extension\CodeExtension" public="false">
|
||||
|
@ -34,6 +34,7 @@
|
||||
"symfony/templating": "~2.8|~3.0",
|
||||
"symfony/yaml": "~2.8|~3.0",
|
||||
"symfony/framework-bundle": "^3.2.2",
|
||||
"symfony/web-link": "~3.3",
|
||||
"doctrine/annotations": "~1.0"
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -1,58 +0,0 @@
|
||||
<?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\Asset\Preload;
|
||||
|
||||
/**
|
||||
* Manages preload HTTP headers.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class PreloadManager implements PreloadManagerInterface
|
||||
{
|
||||
private $resources = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addResource($uri, $as = '', $nopush = false)
|
||||
{
|
||||
$this->resources[$uri] = array('as' => $as, 'nopush' => $nopush);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->resources = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildLinkValue()
|
||||
{
|
||||
if (!$this->resources) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parts = array();
|
||||
foreach ($this->resources as $uri => $options) {
|
||||
$as = '' === $options['as'] ? '' : sprintf('; as=%s', $options['as']);
|
||||
$nopush = $options['nopush'] ? '; nopush' : '';
|
||||
|
||||
$parts[] = sprintf('<%s>; rel=preload%s%s', $uri, $as, $nopush);
|
||||
}
|
||||
|
||||
return implode(',', $parts);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?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\Asset\Preload;
|
||||
|
||||
/**
|
||||
* Manages resources to preload according to the W3C "Preload" specification.
|
||||
*
|
||||
* @see https://www.w3.org/TR/preload/
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
interface PreloadManagerInterface
|
||||
{
|
||||
/**
|
||||
* Adds an element to the list of resources to preload.
|
||||
*
|
||||
* @param string $uri The resource URI
|
||||
* @param string $as A valid destination according to https://fetch.spec.whatwg.org/#concept-request-destination
|
||||
* @param bool $nopush If this asset should not be pushed over HTTP/2
|
||||
*/
|
||||
public function addResource($uri, $as = '', $nopush = false);
|
||||
|
||||
/**
|
||||
* Clears the list of resources.
|
||||
*/
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
* Builds the value of the preload Link HTTP header.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function buildLinkValue();
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?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\Asset\Preload;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class PreloadManagerTest extends TestCase
|
||||
{
|
||||
public function testManageResources()
|
||||
{
|
||||
$manager = new PreloadManager();
|
||||
$this->assertInstanceOf(PreloadManagerInterface::class, $manager);
|
||||
|
||||
$manager->addResource('/foo/bar.js', 'script', false);
|
||||
$manager->addResource('/foo/baz.css');
|
||||
$manager->addResource('/foo/bat.png', 'image', true);
|
||||
|
||||
$this->assertEquals('</foo/bar.js>; rel=preload; as=script,</foo/baz.css>; rel=preload,</foo/bat.png>; rel=preload; as=image; nopush', $manager->buildLinkValue());
|
||||
}
|
||||
}
|
3
src/Symfony/Component/WebLink/.gitignore
vendored
Normal file
3
src/Symfony/Component/WebLink/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
7
src/Symfony/Component/WebLink/CHANGELOG.md
Normal file
7
src/Symfony/Component/WebLink/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* added the component
|
@ -9,26 +9,28 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\EventListener;
|
||||
namespace Symfony\Component\WebLink\EventListener;
|
||||
|
||||
use Symfony\Component\Asset\Preload\PreloadManager;
|
||||
use Symfony\Component\Asset\Preload\PreloadManagerInterface;
|
||||
use Psr\Link\LinkProviderInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\WebLink\HttpHeaderSerializer;
|
||||
|
||||
/**
|
||||
* Adds the preload Link HTTP header to the response.
|
||||
* Adds the Link HTTP header to the response.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class PreloadListener implements EventSubscriberInterface
|
||||
class AddLinkHeaderListener implements EventSubscriberInterface
|
||||
{
|
||||
private $preloadManager;
|
||||
private $serializer;
|
||||
|
||||
public function __construct(PreloadManagerInterface $preloadManager)
|
||||
public function __construct()
|
||||
{
|
||||
$this->preloadManager = $preloadManager;
|
||||
$this->serializer = new HttpHeaderSerializer();
|
||||
}
|
||||
|
||||
public function onKernelResponse(FilterResponseEvent $event)
|
||||
@ -37,12 +39,12 @@ class PreloadListener implements EventSubscriberInterface
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value = $this->preloadManager->buildLinkValue()) {
|
||||
$event->getResponse()->headers->set('Link', $value, false);
|
||||
|
||||
// Free memory
|
||||
$this->preloadManager->clear();
|
||||
$linkProvider = $event->getRequest()->attributes->get('_links');
|
||||
if (!$linkProvider instanceof LinkProviderInterface || !$links = $linkProvider->getLinks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getResponse()->headers->set('Link', $this->serializer->serialize($links), false);
|
||||
}
|
||||
|
||||
/**
|
66
src/Symfony/Component/WebLink/HttpHeaderSerializer.php
Normal file
66
src/Symfony/Component/WebLink/HttpHeaderSerializer.php
Normal file
@ -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\LinkInterface;
|
||||
|
||||
/**
|
||||
* 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 LinkInterface[]|\Traversable $links
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function serialize($links)
|
||||
{
|
||||
$elements = array();
|
||||
foreach ($links 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;
|
||||
}
|
||||
}
|
19
src/Symfony/Component/WebLink/LICENSE
Normal file
19
src/Symfony/Component/WebLink/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2017 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
18
src/Symfony/Component/WebLink/README.md
Normal file
18
src/Symfony/Component/WebLink/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
WebLink Component
|
||||
=================
|
||||
|
||||
The WebLink component manages links between resources. It is particularly useful to advise clients
|
||||
to preload and prefetch documents through HTTP and HTTP/2 pushes.
|
||||
|
||||
This component implements the [HTML5's Links](https://www.w3.org/TR/html5/links.html), [Preload](https://www.w3.org/TR/preload/)
|
||||
and [Resource Hints](https://www.w3.org/TR/resource-hints/) W3C's specifications.
|
||||
It can also be used with extensions defined in the [HTML5 link type extensions wiki](http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions).
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/weblink/introduction.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
@ -9,11 +9,13 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Asset\Tests\EventListener;
|
||||
namespace Symfony\Component\WebLink\Tests\EventListener;
|
||||
|
||||
use Fig\Link\GenericLinkProvider;
|
||||
use Fig\Link\Link;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Asset\EventListener\PreloadListener;
|
||||
use Symfony\Component\Asset\Preload\PreloadManager;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
@ -22,18 +24,18 @@ use Symfony\Component\HttpKernel\KernelEvents;
|
||||
/**
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class PreloadListenerTest extends TestCase
|
||||
class AddLinkHeaderListenerTest extends TestCase
|
||||
{
|
||||
public function testOnKernelResponse()
|
||||
{
|
||||
$manager = new PreloadManager();
|
||||
$manager->addResource('/foo');
|
||||
|
||||
$subscriber = new PreloadListener($manager);
|
||||
$request = new Request(array(), array(), array('_links' => new GenericLinkProvider(array(new Link('preload', '/foo')))));
|
||||
$response = new Response('', 200, array('Link' => '<https://demo.api-platform.com/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"'));
|
||||
|
||||
$subscriber = new AddLinkHeaderListener();
|
||||
|
||||
$event = $this->getMockBuilder(FilterResponseEvent::class)->disableOriginalConstructor()->getMock();
|
||||
$event->method('isMasterRequest')->willReturn(true);
|
||||
$event->method('getRequest')->willReturn($request);
|
||||
$event->method('getResponse')->willReturn($response);
|
||||
|
||||
$subscriber->onKernelResponse($event);
|
||||
@ -42,15 +44,14 @@ class PreloadListenerTest extends TestCase
|
||||
|
||||
$expected = array(
|
||||
'<https://demo.api-platform.com/docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"',
|
||||
'</foo>; rel=preload',
|
||||
'</foo>; rel="preload"',
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, $response->headers->get('Link', null, false));
|
||||
$this->assertNull($manager->buildLinkValue());
|
||||
}
|
||||
|
||||
public function testSubscribedEvents()
|
||||
{
|
||||
$this->assertEquals(array(KernelEvents::RESPONSE => 'onKernelResponse'), PreloadListener::getSubscribedEvents());
|
||||
$this->assertEquals(array(KernelEvents::RESPONSE => 'onKernelResponse'), AddLinkHeaderListener::getSubscribedEvents());
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?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()
|
||||
{
|
||||
$links = 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($links));
|
||||
}
|
||||
|
||||
public function testSerializeEmpty()
|
||||
{
|
||||
$this->assertNull($this->serializer->serialize(array()));
|
||||
}
|
||||
}
|
43
src/Symfony/Component/WebLink/composer.json
Normal file
43
src/Symfony/Component/WebLink/composer.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "symfony/web-link",
|
||||
"type": "library",
|
||||
"description": "Symfony WebLink Component",
|
||||
"keywords": ["link", "psr13", "http", "HTTP/2", "preload", "prefetch", "prerender", "dns-prefetch", "push", "performance"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kévin Dunglas",
|
||||
"email": "dunglas@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"fig/link-util": "^1.0",
|
||||
"psr/link": "^1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/http-kernel": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/event-dispatcher": "^2.8|^3.0",
|
||||
"symfony/http-foundation": "^2.8|^3.0",
|
||||
"symfony/http-kernel": "^2.8|^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Link\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.3-dev"
|
||||
}
|
||||
}
|
||||
}
|
28
src/Symfony/Component/WebLink/phpunit.xml.dist
Normal file
28
src/Symfony/Component/WebLink/phpunit.xml.dist
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony WebLink Component Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
Reference in New Issue
Block a user