feature #13234 [Asset] added the component (fabpot)

This PR was merged into the 2.7 branch.

Discussion
----------

[Asset] added the component

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #10973, #11748, #11876, #4883, #12474
| License       | MIT
| Doc PR        | not yet

TODO:

 - [ ] submit documentation PR

The current Asset sub-namespace in Templating has several (major) problems:

 * It does not cover all use cases (see #10973 and #4883 for some example)
 * It has some design issues (relies on the Request instance and so requires the request scope, very coupled with the PHP templating sub-system, see #11748 and #11876)

To decouple this feature and make it reusable in Silex for instance, and to fix the design issues and make it more extensible, I've decided to extract and rework the features provided into a new Asset component.

Basically, this component allows the developer to easily manage asset URLs: versioning, paths, and hosts.

Both the new and the old asset management features are kept in this PR to avoid breaking BC; the old system is of course deprecated and automatically converted to the new one.

Even if the features are quite similar, and besides the flexilibity of the new system, here are some differences:

 * `PathPackage` always prepend the path (even if the given path starts with `/`).
 * Usage is stricter (for instance, `PathPackage` requires a basePath to be passed and `UrlPackage` requires that at least on URL is passed).
 * In the configuration, named packages inherits from the version and version format of the default package by default.
 * It is not possible to override the version when asking for a URL (instead, you can define your own version strategy implementation -- the use cases explained in #6092 are easily implemented this way).
 * It's not possible to generate absolute URLs (see #13264 for a better alternative using composition; so using `absolute_url(asset_path('me.png')) should work)`.

#10973 was about adding shortcuts for bundles, which is a good idea; but given that you rarely reference built-in or third-party bundle assets and because we now have a one-bundle default approach named AppBundle, the same can be achieved with just a simple piece of configuration with the new assets feature:

```yml
framework:
    assets:
        packages:
            app:
                base_path: /bundles/app/
            img:
                base_path: /bundles/app/images/
```

Then:

```jinja
{{ asset('images/me.png', 'app') }}
# /bundles/app/images/me.png

{{ asset('me.png', 'img') }}
# /bundles/app/images/me.png
```

#12474 discussed the possibility to add a version for absolute URL. It's not possible to do that in a generic way as the version strategy involves both the version and the path, which obviously cannot work when the path is an absolute URL already. Instead, one should use the `asset_version` Twig function to add the version manually.

Commits
-------

0750d02 removed usage of the deprecated forms of asset() in the core framework
f74a1f2 renamed asset_path() to asset() and added a BC layer
4d0adea [Asset] added a NullContext class
d33c41d [Asset] added the component
This commit is contained in:
Fabien Potencier 2015-02-10 14:18:49 +01:00
commit 97cff759ef
72 changed files with 2213 additions and 154 deletions

View File

@ -22,6 +22,7 @@
"psr/log": "~1.0"
},
"replace": {
"symfony/asset": "self.version",
"symfony/browser-kit": "self.version",
"symfony/class-loader": "self.version",
"symfony/config": "self.version",

View File

@ -6,6 +6,7 @@ CHANGELOG
* added LogoutUrlExtension (provides `logout_url` and `logout_path`)
* added an HttpFoundation extension (provides the `absolute_url` and the `relative_path` functions)
* added AssetExtension (provides the `asset` and `asset_version` functions)
2.5.0
-----

View File

@ -0,0 +1,144 @@
<?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 Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
/**
* Twig extension for the Symfony Asset component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AssetExtension extends \Twig_Extension
{
private $packages;
private $foundationExtension;
/**
* Passing an HttpFoundationExtension instance as a second argument must not be relied on
* as it's only there to maintain BC with older Symfony version. It will be removed in 3.0.
*/
public function __construct(Packages $packages, HttpFoundationExtension $foundationExtension = null)
{
$this->packages = $packages;
$this->foundationExtension = $foundationExtension;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')),
new \Twig_SimpleFunction('asset_version', array($this, 'getAssetVersion')),
new \Twig_SimpleFunction('assets_version', array($this, 'getAssetsVersion')),
);
}
/**
* Returns the public url/path of an asset.
*
* If the package used to generate the path is an instance of
* UrlPackage, you will always get a URL and not a path.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string The public path of the asset
*/
public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null)
{
// BC layer to be removed in 3.0
if (2 < $count = func_num_args()) {
trigger_error('Generating absolute URLs with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0. Please use absolute_url() instead.', E_USER_DEPRECATED);
if (4 === $count) {
trigger_error('Forcing a version with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
}
$args = func_get_args();
return $this->getLegacyAssetUrl($path, $packageName, $args[2], isset($args[3]) ? $args[3] : null);
}
return $this->packages->getUrl($path, $packageName);
}
/**
* Returns the version of an asset.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string The asset version
*/
public function getAssetVersion($path, $packageName = null)
{
return $this->packages->getVersion($path, $packageName);
}
public function getAssetsVersion($packageName = null)
{
trigger_error('The Twig assets_version() function was deprecated in 2.7 and will be removed in 3.0. Please use asset_version() instead.', E_USER_DEPRECATED);
return $this->packages->getVersion('/', $packageName);
}
private function getLegacyAssetUrl($path, $packageName = null, $absolute = false, $version = null)
{
if ($version) {
$package = $this->packages->getPackage($packageName);
$v = new \ReflectionProperty($package, 'versionStrategy');
$v->setAccessible(true);
$currentVersionStrategy = $v->getValue($package);
$f = new \ReflectionProperty($currentVersionStrategy, 'format');
$f->setAccessible(true);
$format = $f->getValue($currentVersionStrategy);
$v->setValue($package, new StaticVersionStrategy($version, $format));
}
try {
$url = $this->packages->getUrl($path, $packageName);
} catch (\Exception $e) {
if ($version) {
$v->setValue($package, $currentVersionStrategy);
}
throw $e;
}
if ($version) {
$v->setValue($package, $currentVersionStrategy);
}
if ($absolute) {
return $this->foundationExtension->generateAbsoluteUrl($url);
}
return $url;
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'asset';
}
}

View File

@ -0,0 +1,40 @@
<?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 Symfony\Bridge\Twig\Extension\AssetExtension;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class AssetExtensionTest extends \PHPUnit_Framework_TestCase
{
public function testLegacyGetAssetUrl()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$foundationExtension = $this->getMockBuilder('Symfony\Bridge\Twig\Extension\HttpFoundationExtension')->disableOriginalConstructor()->getMock();
$foundationExtension
->expects($this->any())
->method('generateAbsoluteUrl')
->will($this->returnCallback(function ($arg) { return 'http://localhost/'.$arg; }))
;
$package = new Package(new StaticVersionStrategy('22', '%s?version=%s'));
$packages = new Packages($package);
$extension = new AssetExtension($packages, $foundationExtension);
$this->assertEquals('me.png?version=42', $extension->getAssetUrl('me.png', null, false, '42'));
$this->assertEquals('http://localhost/me.png?version=22', $extension->getAssetUrl('me.png', null, true));
$this->assertEquals('http://localhost/me.png?version=42', $extension->getAssetUrl('me.png', null, true, '42'));
}
}

View File

@ -37,6 +37,7 @@
},
"suggest": {
"symfony/finder": "",
"symfony/asset": "For using the AssetExtension",
"symfony/form": "For using the FormExtension",
"symfony/http-kernel": "For using the HttpKernelExtension",
"symfony/routing": "For using the RoutingExtension",

View File

@ -16,6 +16,11 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
trigger_error('The '.__NAMESPACE__.'\TemplatingAssetHelperPass class is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
/**
* @deprecated since 2.7, will be removed in 3.0
*/
class TemplatingAssetHelperPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)

View File

@ -53,6 +53,44 @@ class Configuration implements ConfigurationInterface
return $v;
})
->end()
->validate()
->ifTrue(function ($v) { return isset($v['templating']); })
->then(function ($v) {
if ($v['templating']['assets_version']
|| count($v['templating']['assets_base_urls']['http'])
|| count($v['templating']['assets_base_urls']['ssl'])
|| count($v['templating']['packages'])
) {
trigger_error('The assets settings under framework.templating are deprecated since version 2.7 and will be removed in 3.0. Use the framework.assets configuration key instead', E_USER_DEPRECATED);
// convert the old configuration to the new one
if (isset($v['assets'])) {
throw new LogicException('You cannot use assets settings under "templating.templating" and "assets" configurations in the same project.');
}
$v['assets'] = array(
'version' => $v['templating']['assets_version'],
'version_format' => $v['templating']['assets_version_format'],
'base_path' => '',
'base_urls' => array_values(array_unique(array_merge($v['templating']['assets_base_urls']['http'], $v['templating']['assets_base_urls']['ssl']))),
'packages' => array(),
);
foreach ($v['templating']['packages'] as $name => $config) {
$v['assets']['packages'][$name] = array(
'version' => (string) $config['version'],
'version_format' => $config['version_format'],
'base_path' => '',
'base_urls' => array_values(array_unique(array_merge($config['base_urls']['http'], $config['base_urls']['ssl']))),
);
}
}
unset($v['templating']['assets_version'], $v['templating']['assets_version_format'], $v['templating']['assets_base_urls'], $v['templating']['packages']);
return $v;
})
->end()
->children()
->scalarNode('secret')->end()
->scalarNode('http_method_override')
@ -108,6 +146,7 @@ class Configuration implements ConfigurationInterface
$this->addSessionSection($rootNode);
$this->addRequestSection($rootNode);
$this->addTemplatingSection($rootNode);
$this->addAssetsSection($rootNode);
$this->addTranslatorSection($rootNode);
$this->addValidationSection($rootNode);
$this->addAnnotationsSection($rootNode);
@ -347,8 +386,8 @@ class Configuration implements ConfigurationInterface
->info('templating configuration')
->canBeUnset()
->children()
->scalarNode('assets_version')->defaultValue(null)->end()
->scalarNode('assets_version_format')->defaultValue('%%s?%%s')->end()
->scalarNode('assets_version')->defaultNull()->info('Deprecated since 2.7, will be removed in 3.0. Use the new assets entry instead.')->end()
->scalarNode('assets_version_format')->defaultValue('%%s?%%s')->info('Deprecated since 2.7, will be removed in 3.0. Use the new assets entry instead.')->end()
->scalarNode('hinclude_default_template')->defaultNull()->end()
->arrayNode('form')
->addDefaultsIfNotSet()
@ -370,6 +409,7 @@ class Configuration implements ConfigurationInterface
->fixXmlConfig('assets_base_url')
->children()
->arrayNode('assets_base_urls')
->info('Deprecated since 2.7, will be removed in 3.0. Use the new assets entry instead.')
->performNoDeepMerging()
->addDefaultsIfNotSet()
->beforeNormalization()
@ -417,6 +457,7 @@ class Configuration implements ConfigurationInterface
->fixXmlConfig('package')
->children()
->arrayNode('packages')
->info('Deprecated since 2.7, will be removed in 3.0. Use the new assets entry instead.')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('base_url')
@ -452,6 +493,54 @@ class Configuration implements ConfigurationInterface
;
}
private function addAssetsSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('assets')
->info('assets configuration')
->canBeUnset()
->fixXmlConfig('base_url')
->children()
->scalarNode('version')->defaultNull()->end()
->scalarNode('version_format')->defaultValue('%%s?%%s')->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
->beforeNormalization()
->ifTrue(function ($v) { return !is_array($v); })
->then(function ($v) { return array($v); })
->end()
->prototype('scalar')->end()
->end()
->end()
->fixXmlConfig('package')
->children()
->arrayNode('packages')
->useAttributeAsKey('name')
->prototype('array')
->fixXmlConfig('base_url')
->children()
->scalarNode('version')->defaultNull()->end()
->scalarNode('version_format')->defaultNull()->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
->beforeNormalization()
->ifTrue(function ($v) { return !is_array($v); })
->then(function ($v) { return array($v); })
->end()
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}
private function addTranslatorSection(ArrayNodeDefinition $rootNode)
{
$rootNode

View File

@ -49,6 +49,7 @@ class FrameworkExtension extends Extension
$loader->load('web.xml');
$loader->load('services.xml');
$loader->load('fragment_renderer.xml');
$loader->load('assets.xml');
// A translator must always be registered (as support is included by
// default in the Form component). If disabled, an identity translator
@ -101,6 +102,10 @@ class FrameworkExtension extends Extension
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
if (isset($config['assets'])) {
$this->registerAssetsConfiguration($config['assets'], $container, $loader);
}
if (isset($config['templating'])) {
$this->registerTemplatingConfiguration($config['templating'], $config['ide'], $container, $loader);
}
@ -469,23 +474,6 @@ class FrameworkExtension extends Extension
$container->setParameter('templating.helper.code.file_link_format', isset($links[$ide]) ? $links[$ide] : $ide);
$container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']);
$loader->load('old_assets.xml');
// create package definitions and add them to the assets helper
$defaultPackage = $this->createTemplatingPackageDefinition($container, $config['assets_base_urls']['http'], $config['assets_base_urls']['ssl'], $config['assets_version'], $config['assets_version_format']);
$container->setDefinition('templating.asset.default_package', $defaultPackage);
$namedPackages = array();
foreach ($config['packages'] as $name => $package) {
$namedPackage = $this->createTemplatingPackageDefinition($container, $package['base_urls']['http'], $package['base_urls']['ssl'], $package['version'], $package['version_format'], $name);
$container->setDefinition('templating.asset.package.'.$name, $namedPackage);
$namedPackages[$name] = new Reference('templating.asset.package.'.$name);
}
$container->getDefinition('templating.helper.assets')->setArguments(array(
new Reference('templating.asset.default_package'),
$namedPackages,
));
if ($container->getParameter('kernel.debug')) {
$logger = new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE);
@ -565,71 +553,81 @@ class FrameworkExtension extends Extension
}
}
/**
* Loads the assets configuration.
*
* @param array $config A assets configuration array
* @param ContainerBuilder $container A ContainerBuilder instance
* @param XmlFileLoader $loader An XmlFileLoader instance
*/
private function registerAssetsConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
$defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], '_default');
$defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion);
$container->setDefinition('assets._default_package', $defaultPackage);
$namedPackages = array();
foreach ($config['packages'] as $name => $package) {
if (null === $package['version']) {
$version = $defaultVersion;
} else {
$format = $package['version_format'] ?: $config['version_format'];
$version = $this->createVersion($container, $package['version'], $format, $name);
}
$container->setDefinition('assets._package_'.$name, $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version));
$namedPackages[$name] = new Reference('assets._package_'.$name);
}
$container->getDefinition('assets.packages')
->replaceArgument(0, new Reference('assets._default_package'))
->replaceArgument(1, $namedPackages)
;
}
/**
* Returns a definition for an asset package.
*/
private function createTemplatingPackageDefinition(ContainerBuilder $container, array $httpUrls, array $sslUrls, $version, $format, $name = null)
private function createPackageDefinition($basePath, array $baseUrls, Reference $version)
{
if (!$httpUrls) {
$package = new DefinitionDecorator('templating.asset.path_package');
$package
if ($basePath && $baseUrls) {
throw new \LogicException('An asset package cannot have base URLs and base paths.');
}
if (!$baseUrls) {
$package = new DefinitionDecorator('assets.path_package');
return $package
->setPublic(false)
->setScope('request')
->replaceArgument(0, $basePath)
->replaceArgument(1, $version)
->replaceArgument(2, $format)
;
return $package;
}
if ($httpUrls == $sslUrls) {
$package = new DefinitionDecorator('templating.asset.url_package');
$package
->setPublic(false)
->replaceArgument(0, $sslUrls)
->replaceArgument(1, $version)
->replaceArgument(2, $format)
;
return $package;
}
$prefix = $name ? 'templating.asset.package.'.$name : 'templating.asset.default_package';
$httpPackage = new DefinitionDecorator('templating.asset.url_package');
$httpPackage
->replaceArgument(0, $httpUrls)
->replaceArgument(1, $version)
->replaceArgument(2, $format)
;
$container->setDefinition($prefix.'.http', $httpPackage);
if ($sslUrls) {
$sslPackage = new DefinitionDecorator('templating.asset.url_package');
$sslPackage
->replaceArgument(0, $sslUrls)
->replaceArgument(1, $version)
->replaceArgument(2, $format)
;
} else {
$sslPackage = new DefinitionDecorator('templating.asset.path_package');
$sslPackage
->setScope('request')
->replaceArgument(1, $version)
->replaceArgument(2, $format)
;
}
$container->setDefinition($prefix.'.ssl', $sslPackage);
$package = new DefinitionDecorator('templating.asset.request_aware_package');
$package
$package = new DefinitionDecorator('assets.url_package');
return $package
->setPublic(false)
->setScope('request')
->replaceArgument(1, $prefix.'.http')
->replaceArgument(2, $prefix.'.ssl')
->replaceArgument(0, $baseUrls)
->replaceArgument(1, $version)
;
}
return $package;
private function createVersion(ContainerBuilder $container, $version, $format, $name)
{
if (null === $version) {
return new Reference('assets.empty_version_strategy');
}
$def = new DefinitionDecorator('assets.static_version_strategy');
$def
->replaceArgument(0, $version)
->replaceArgument(1, $format)
;
$container->setDefinition('assets._version_'.$name, $def);
return new Reference('assets._version_'.$name);
}
/**

View File

@ -16,7 +16,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddValidatorInit
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingAssetHelperPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
@ -76,7 +75,6 @@ class FrameworkBundle extends Bundle
// but as late as possible to get resolved parameters
$container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TemplatingPass());
$container->addCompilerPass(new TemplatingAssetHelperPass());
$container->addCompilerPass(new AddConstraintValidatorsPass());
$container->addCompilerPass(new AddValidatorInitializersPass());
$container->addCompilerPass(new AddConsoleCommandPass());

View File

@ -0,0 +1,42 @@
<?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="assets.packages" class="Symfony\Component\Asset\Packages">
<argument type="service" id="assets.empty_package" /> <!-- default package -->
<argument type="collection" /> <!-- named packages -->
</service>
<service id="assets.empty_package" class="Symfony\Component\Asset\Package" public="false">
<argument type="service" id="assets.empty_version_strategy" />
</service>
<service id="assets.context" class="Symfony\Component\Asset\Context\RequestStackContext">
<argument type="service" id="request_stack" />
</service>
<service id="assets.path_package" class="Symfony\Component\Asset\PathPackage" abstract="true">
<argument /> <!-- base path -->
<argument type="service" /> <!-- version strategy -->
<argument type="service" id="assets.context" />
</service>
<service id="assets.url_package" class="Symfony\Component\Asset\UrlPackage" abstract="true">
<argument /> <!-- base URLs -->
<argument type="service" /> <!-- version strategy -->
<argument type="service" id="assets.context" />
</service>
<service id="assets.static_version_strategy" class="Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy" abstract="true">
<argument /> <!-- version -->
<argument /> <!-- format -->
</service>
<service id="assets.empty_version_strategy" class="Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy" public="false" />
</services>
</container>

View File

@ -18,6 +18,7 @@
<xsd:complexType name="config">
<xsd:all>
<xsd:element name="assets" type="assets" minOccurs="0" maxOccurs="1" />
<xsd:element name="form" type="form" minOccurs="0" maxOccurs="1" />
<xsd:element name="csrf-protection" type="csrf_protection" minOccurs="0" maxOccurs="1" />
<xsd:element name="esi" type="esi" minOccurs="0" maxOccurs="1" />
@ -125,12 +126,34 @@
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="assets">
<xsd:sequence>
<xsd:element name="base-url" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="package" type="package" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="base-path" type="xsd:string" />
<xsd:attribute name="version" type="xsd:string" />
<xsd:attribute name="version-format" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="package">
<xsd:sequence>
<xsd:element name="base-url" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="base-path" type="xsd:string" />
<xsd:attribute name="version" type="xsd:string" />
<xsd:attribute name="version-format" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="templating">
<xsd:sequence>
<xsd:element name="loader" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="engine" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="assets-base-url" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="package" type="package" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="package" type="old-package" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="form" type="form-resources" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
@ -146,7 +169,7 @@
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="package">
<xsd:complexType name="old-package">
<xsd:sequence>
<xsd:element name="base-url" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>

View File

@ -14,7 +14,6 @@
<parameter key="templating.loader.cache.class">Symfony\Component\Templating\Loader\CacheLoader</parameter>
<parameter key="templating.loader.chain.class">Symfony\Component\Templating\Loader\ChainLoader</parameter>
<parameter key="templating.finder.class">Symfony\Bundle\FrameworkBundle\CacheWarmer\TemplateFinder</parameter>
<parameter key="templating.helper.assets.class">Symfony\Component\Templating\Helper\CoreAssetsHelper</parameter>
</parameters>
<services>
@ -59,15 +58,5 @@
</service>
<service id="templating.loader" alias="templating.loader.filesystem" />
<!--
This should be in templating_php.xml but unfortunately, Twig depends on this helper.
As the Twig extension depending on this service is deprecated, this will be removed in 3.0.
-->
<service id="templating.helper.assets" class="%templating.helper.assets.class%">
<tag name="templating.helper" alias="assets" />
<argument /> <!-- default package -->
<argument type="collection" /> <!-- named packages -->
</service>
</services>
</container>

View File

@ -15,6 +15,7 @@
<parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter>
<parameter key="templating.helper.form.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper</parameter>
<parameter key="templating.helper.stopwatch.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\StopwatchHelper</parameter>
<parameter key="templating.helper.assets.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper</parameter>
<parameter key="templating.form.engine.class">Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine</parameter>
<parameter key="templating.form.renderer.class">Symfony\Component\Form\FormRenderer</parameter>
<parameter key="templating.globals.class">Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables</parameter>
@ -65,6 +66,12 @@
<argument type="service" id="translator" />
</service>
<service id="templating.helper.assets" class="%templating.helper.assets.class%">
<tag name="templating.helper" alias="assets" />
<argument /> <!-- default package -->
<argument type="collection" /> <!-- named packages -->
</service>
<service id="templating.helper.form" class="%templating.helper.form.class%">
<tag name="templating.helper" alias="form" />
<argument type="service" id="templating.form.renderer" />

View File

@ -11,6 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\Templating\Asset;
trigger_error('The Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Templating\Asset\PackageInterface;
@ -19,6 +21,8 @@ use Symfony\Component\Templating\Asset\PackageInterface;
* Creates packages based on whether the current request is secure.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class PackageFactory
{

View File

@ -14,10 +14,14 @@ namespace Symfony\Bundle\FrameworkBundle\Templating\Asset;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Templating\Asset\PathPackage as BasePathPackage;
trigger_error('The Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
/**
* The path packages adds a version and a base path to asset URLs.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class PathPackage extends BasePathPackage
{

View File

@ -0,0 +1,126 @@
<?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\Bundle\FrameworkBundle\Templating\Helper;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\Templating\Helper\Helper;
/**
* AssetsHelper helps manage asset URLs.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AssetsHelper extends Helper
{
private $packages;
public function __construct(Packages $packages)
{
$this->packages = $packages;
}
/**
* Returns the public url/path of an asset.
*
* If the package used to generate the path is an instance of
* UrlPackage, you will always get a URL and not a path.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string The public path of the asset
*/
public function getUrl($path, $packageName = null, $version = null)
{
// BC layer to be removed in 3.0
if (3 === $count = func_num_args()) {
trigger_error('Forcing a version for an asset was deprecated in 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
$args = func_get_args();
return $this->getLegacyAssetUrl($path, $packageName, $args[2]);
}
return $this->packages->getUrl($path, $packageName);
}
/**
* Returns the version of an asset.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string The asset version
*/
public function getVersion($path = null, $packageName = null)
{
// no arguments means old getVersion() for default package
if (null === $path) {
trigger_error('The getVersion() method requires a path as a first argument since 2.7 and will be enforced as of 3.0.', E_USER_DEPRECATED);
return $this->packages->getVersion('/', $packageName);
}
// path and packageName can only be for the new version
if (null !== $packageName) {
return $this->packages->getVersion($path, $packageName);
}
// packageName is null and path not, so path is a path or a packageName
try {
$package = $this->packages->getPackage($path);
} catch (\InvalidArgumentException $e) {
// path is not a package, so it should be a path
return $this->packages->getVersion($path);
}
// path is a packageName, old version
trigger_error('The getVersion() method requires a path as a first argument since 2.7 and will be enforced as of 3.0.', E_USER_DEPRECATED);
return $this->packages->getVersion('/', $path);
}
private function getLegacyAssetUrl($path, $packageName = null, $version = null)
{
if ($version) {
$package = $this->packages->getPackage($packageName);
$v = new \ReflectionProperty($package, 'versionStrategy');
$v->setAccessible(true);
$currentVersionStrategy = $v->getValue($package);
$f = new \ReflectionProperty($currentVersionStrategy, 'format');
$f->setAccessible(true);
$format = $f->getValue($currentVersionStrategy);
$v->setValue($package, new StaticVersionStrategy($version, $format));
}
$url = $this->packages->getUrl($path, $packageName);
if ($version) {
$v->setValue($package, $currentVersionStrategy);
}
return $url;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'assets';
}
}

View File

@ -16,7 +16,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class TemplatingAssetHelperPassTest extends \PHPUnit_Framework_TestCase
class LegacyTemplatingAssetHelperPassTest extends \PHPUnit_Framework_TestCase
{
public function getScopesTests()
{

View File

@ -0,0 +1,25 @@
<?php
$container->loadFromExtension('framework', array(
'assets' => array(
'version' => 'SomeVersionScheme',
'base_urls' => 'http://cdn.example.com',
'version_format' => '%%s?version=%%s',
'packages' => array(
'images_path' => array(
'base_path' => '/foo',
),
'images' => array(
'version' => '1.0.0',
'base_urls' => array('http://images1.example.com', 'http://images2.example.com'),
),
'foo' => array(
'version' => '1.0.0',
'version_format' => '%%s-%%s',
),
'bar' => array(
'base_urls' => array('https://bar2.example.com'),
),
),
),
));

View File

@ -39,23 +39,9 @@ $container->loadFromExtension('framework', array(
'save_path' => '/path/to/sessions',
),
'templating' => array(
'assets_version' => 'SomeVersionScheme',
'assets_base_urls' => 'http://cdn.example.com',
'cache' => '/path/to/cache',
'engines' => array('php', 'twig'),
'loader' => array('loader.foo', 'loader.bar'),
'packages' => array(
'images' => array(
'version' => '1.0.0',
'base_urls' => array('http://images1.example.com', 'http://images2.example.com'),
),
'foo' => array(
'version' => '1.0.0',
),
'bar' => array(
'base_urls' => array('http://bar1.example.com', 'http://bar2.example.com'),
),
),
'form' => array(
'resources' => array('theme1', 'theme2'),
),

View File

@ -0,0 +1,23 @@
<?php
$container->loadFromExtension('framework', array(
'templating' => array(
'engines' => array('php'),
'assets_version' => 'SomeVersionScheme',
'assets_base_urls' => 'http://cdn.example.com',
'assets_version_format' => '%%s?version=%%s',
'packages' => array(
'images' => array(
'version' => '1.0.0',
'base_urls' => array('http://images1.example.com', 'http://images2.example.com'),
),
'foo' => array(
'version' => '1.0.0',
'version_format' => '%%s-%%s',
),
'bar' => array(
'base_urls' => array('https://bar2.example.com'),
),
),
),
));

View File

@ -0,0 +1,23 @@
<?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:assets version="SomeVersionScheme" version-format="%%s?version=%%s">
<framework:base-url>http://cdn.example.com</framework:base-url>
<framework:package name="images_path" base-path="/foo" version-format="%%s-%%s" />
<framework:package name="images" version="1.0.0">
<framework:base-url>http://images1.example.com</framework:base-url>
<framework:base-url>http://images2.example.com</framework:base-url>
</framework:package>
<framework:package name="foo" version="1.0.0" version-format="%%s-%%s" />
<framework:package name="bar">
<framework:base-url>https://bar2.example.com</framework:base-url>
</framework:package>
</framework:assets>
</framework:config>
</container>

View File

@ -24,21 +24,11 @@
<framework:mime-type>application/pdf</framework:mime-type>
</framework:format>
</framework:request>
<framework:templating assets-version="SomeVersionScheme" cache="/path/to/cache" hinclude-default-template="global_hinclude_template">
<framework:templating cache="/path/to/cache" hinclude-default-template="global_hinclude_template">
<framework:loader>loader.foo</framework:loader>
<framework:loader>loader.bar</framework:loader>
<framework:engine>php</framework:engine>
<framework:engine>twig</framework:engine>
<framework:assets-base-url>http://cdn.example.com</framework:assets-base-url>
<framework:package name="images" version="1.0.0">
<framework:base-url>http://images1.example.com</framework:base-url>
<framework:base-url>http://images2.example.com</framework:base-url>
</framework:package>
<framework:package name="foo" version="1.0.0" />
<framework:package name="bar">
<framework:base-url>http://bar1.example.com</framework:base-url>
<framework:base-url>http://bar2.example.com</framework:base-url>
</framework:package>
<framework:form>
<framework:resource>theme1</framework:resource>
<framework:resource>theme2</framework:resource>

View File

@ -0,0 +1,23 @@
<?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:templating assets-version="SomeVersionScheme" assets-version-format="%%s?version=%%s">
<framework:engine>php</framework:engine>
<framework:assets-base-url>http://cdn.example.com</framework:assets-base-url>
<framework:package name="images" version="1.0.0">
<framework:base-url>http://images1.example.com</framework:base-url>
<framework:base-url>http://images2.example.com</framework:base-url>
</framework:package>
<framework:package name="foo" version="1.0.0" version-format="%%s-%%s" />
<framework:package name="bar">
<framework:base-url>https://bar2.example.com</framework:base-url>
</framework:package>
</framework:templating>
</framework:config>
</container>

View File

@ -0,0 +1,16 @@
framework:
assets:
version: SomeVersionScheme
version_format: %%s?version=%%s
base_urls: http://cdn.example.com
packages:
images_path:
base_path: '/foo'
images:
version: 1.0.0
base_urls: ["http://images1.example.com", "http://images2.example.com"]
foo:
version: 1.0.0
version_format: %%s-%%s
bar:
base_urls: ["https://bar2.example.com"]

View File

@ -30,19 +30,9 @@ framework:
gc_maxlifetime: 90000
save_path: /path/to/sessions
templating:
assets_version: SomeVersionScheme
assets_base_urls: http://cdn.example.com
engines: [php, twig]
loader: [loader.foo, loader.bar]
cache: /path/to/cache
packages:
images:
version: 1.0.0
base_urls: ["http://images1.example.com", "http://images2.example.com"]
foo:
version: 1.0.0
bar:
base_urls: ["http://images1.example.com", "http://images2.example.com"]
form:
resources: [theme1, theme2]
hinclude_default_template: global_hinclude_template

View File

@ -0,0 +1,15 @@
framework:
templating:
engines: [php]
assets_version: SomeVersionScheme
assets_version_format: %%s?version=%%s
assets_base_urls: http://cdn.example.com
packages:
images:
version: 1.0.0
base_urls: ["http://images1.example.com", "http://images2.example.com"]
foo:
version: 1.0.0
version_format: %%s-%%s
bar:
base_urls: "https://bar2.example.com"

View File

@ -14,6 +14,8 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Validator\Validation;
@ -186,21 +188,6 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertTrue($container->hasDefinition('templating.name_parser'), '->registerTemplatingConfiguration() loads templating.xml');
// default package should have one HTTP base URL and path package SSL URL
$this->assertTrue($container->hasDefinition('templating.asset.default_package.http'));
$package = $container->getDefinition('templating.asset.default_package.http');
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\DefinitionDecorator', $package);
$this->assertEquals('templating.asset.url_package', $package->getParent());
$arguments = array_values($package->getArguments());
$this->assertEquals(array('http://cdn.example.com'), $arguments[0]);
$this->assertEquals('SomeVersionScheme', $arguments[1]);
$this->assertEquals('%%s?%%s', $arguments[2]);
$this->assertTrue($container->hasDefinition('templating.asset.default_package.ssl'));
$package = $container->getDefinition('templating.asset.default_package.ssl');
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\DefinitionDecorator', $package);
$this->assertEquals('templating.asset.path_package', $package->getParent());
$this->assertEquals('templating.engine.delegating', (string) $container->getAlias('templating'), '->registerTemplatingConfiguration() configures delegating loader if multiple engines are provided');
$this->assertEquals($container->getDefinition('templating.loader.chain'), $container->getDefinition('templating.loader.wrapped'), '->registerTemplatingConfiguration() configures loader chain if multiple loaders are provided');
@ -216,11 +203,16 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertEquals('global_hinclude_template', $container->getParameter('fragment.renderer.hinclude.global_template'), '->registerTemplatingConfiguration() registers the global hinclude.js template');
}
public function testTemplatingAssetsHelperScopeDependsOnPackageArgumentScopes()
public function testLegacyTemplatingAssets()
{
$container = $this->createContainerFromFile('templating_url_package');
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$this->assertNotEquals('request', $container->getDefinition('templating.helper.assets')->getScope(), '->registerTemplatingConfiguration() does not set request scope on assets helper if no packages are request-scoped');
$this->checkAssetsPackages($this->createContainerFromFile('legacy_templating_assets'), true);
}
public function testAssets()
{
$this->checkAssetsPackages($this->createContainerFromFile('assets'));
}
public function testTranslator()
@ -528,4 +520,71 @@ abstract class FrameworkExtensionTest extends TestCase
return $container;
}
protected function createContainerFromClosure($closure, $data = array())
{
$container = $this->createContainer($data);
$container->registerExtension(new FrameworkExtension());
$loader = new ClosureLoader($container);
$loader->load($closure);
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
$container->compile();
return $container;
}
private function checkAssetsPackages(ContainerBuilder $container, $legacy = false)
{
$packages = $container->getDefinition('assets.packages');
// default package
$defaultPackage = $container->getDefinition($packages->getArgument(0));
$this->assertUrlPackage($container, $defaultPackage, array('http://cdn.example.com'), 'SomeVersionScheme', '%%s?version=%%s');
// packages
$packages = $packages->getArgument(1);
$this->assertCount($legacy ? 3 : 4, $packages);
if (!$legacy) {
$package = $container->getDefinition($packages['images_path']);
$this->assertPathPackage($container, $package, '/foo', 'SomeVersionScheme', '%%s?version=%%s');
}
$package = $container->getDefinition($packages['images']);
$this->assertUrlPackage($container, $package, array('http://images1.example.com', 'http://images2.example.com'), '1.0.0', $legacy ? '%%s?%%s' : '%%s?version=%%s');
$package = $container->getDefinition($packages['foo']);
$this->assertPathPackage($container, $package, '', '1.0.0', '%%s-%%s');
$package = $container->getDefinition($packages['bar']);
$this->assertUrlPackage($container, $package, array('https://bar2.example.com'), $legacy ? '' : 'SomeVersionScheme', $legacy ? '%%s?%%s' : '%%s?version=%%s');
}
private function assertPathPackage(ContainerBuilder $container, Definition $package, $basePath, $version, $format)
{
$this->assertEquals('assets.path_package', $package->getParent());
$this->assertEquals($basePath, $package->getArgument(0));
$this->assertVersionStrategy($container, $package->getArgument(1), $version, $format);
}
private function assertUrlPackage(ContainerBuilder $container, Definition $package, $baseUrls, $version, $format)
{
$this->assertEquals('assets.url_package', $package->getParent());
$this->assertEquals($baseUrls, $package->getArgument(0));
$this->assertVersionStrategy($container, $package->getArgument(1), $version, $format);
}
private function assertVersionStrategy(ContainerBuilder $container, Reference $reference, $version, $format)
{
$versionStrategy = $container->getDefinition($reference);
if (null === $version) {
$this->assertEquals('assets.empty_version_strategy', (string) $reference);
} else {
$this->assertEquals('assets.static_version_strategy', $versionStrategy->getParent());
$this->assertEquals($version, $versionStrategy->getArgument(0));
$this->assertEquals($format, $versionStrategy->getArgument(1));
}
}
}

View File

@ -22,4 +22,38 @@ class PhpFrameworkExtensionTest extends FrameworkExtensionTest
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/Fixtures/php'));
$loader->load($file.'.php');
}
/**
* @expectedException \LogicException
*/
public function testAssetsCannotHavePathAndUrl()
{
$container = $this->createContainerFromClosure(function ($container) {
$container->loadFromExtension('framework', array(
'assets' => array(
'base_urls' => 'http://cdn.example.com',
'base_path' => '/foo',
),
));
});
}
/**
* @expectedException \LogicException
*/
public function testAssetPackageCannotHavePathAndUrl()
{
$container = $this->createContainerFromClosure(function ($container) {
$container->loadFromExtension('framework', array(
'assets' => array(
'packages' => array(
'impossible' => array(
'base_urls' => 'http://cdn.example.com',
'base_path' => '/foo',
),
),
),
));
});
}
}

View File

@ -0,0 +1,46 @@
<?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\Bundle\FrameworkBundle\Tests\Templating\Helper;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\AssetsHelper;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class AssetsHelperTest extends \PHPUnit_Framework_TestCase
{
public function testLegacyGetUrl()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$package = new Package(new StaticVersionStrategy('22', '%s?version=%s'));
$packages = new Packages($package);
$helper = new AssetsHelper($packages);
$this->assertEquals('me.png?version=42', $helper->getUrl('me.png', null, '42'));
}
public function testLegacyGetVersion()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$package = new Package(new StaticVersionStrategy('22'));
$imagePackage = new Package(new StaticVersionStrategy('42'));
$packages = new Packages($package, array('images' => $imagePackage));
$helper = new AssetsHelper($packages);
$this->assertEquals('22', $helper->getVersion());
$this->assertEquals('22', $helper->getVersion('/foo'));
$this->assertEquals('42', $helper->getVersion('images'));
$this->assertEquals('42', $helper->getVersion('/foo', 'images'));
}
}

View File

@ -32,6 +32,7 @@
"doctrine/annotations": "~1.0"
},
"require-dev": {
"symfony/asset": "~2.7|~3.0.0",
"symfony/browser-kit": "~2.4|~3.0.0",
"symfony/console": "~2.6|~3.0.0",
"symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
@ -47,6 +48,7 @@
"symfony/yaml": "~2.0,>=2.0.5|~3.0.0"
},
"suggest": {
"symfony/asset": "",
"symfony/console": "For using the console commands",
"symfony/finder": "For using the translation loader and cache warmer",
"symfony/form": "For using forms",

View File

@ -1,6 +1,12 @@
CHANGELOG
=========
2.7.0
-----
* added support for the new Asset component (from Twig bridge)
* deprecated the assets extension (use the one from the Twig bridge instead)
2.6.0
-----

View File

@ -14,10 +14,14 @@ namespace Symfony\Bundle\TwigBundle\Extension;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\RequestContext;
trigger_error('The '.__NAMESPACE__.'\AssetsExtension class is deprecated since version 2.7 and will be removed in 3.0. Use the Symfony\Bridge\Twig\Extension\AssetExtension class instead.', E_USER_DEPRECATED);
/**
* Twig extension for Symfony assets helper.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since 2.7, to be removed in 3.0. Use Symfony\Component\Twig\Extension\AssetExtension instead.
*/
class AssetsExtension extends \Twig_Extension
{

View File

@ -11,7 +11,6 @@
<parameter key="templating.engine.twig.class">Symfony\Bundle\TwigBundle\TwigEngine</parameter>
<parameter key="twig.cache_warmer.class">Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer</parameter>
<parameter key="twig.extension.trans.class">Symfony\Bridge\Twig\Extension\TranslationExtension</parameter>
<parameter key="twig.extension.assets.class">Symfony\Bundle\TwigBundle\Extension\AssetsExtension</parameter>
<parameter key="twig.extension.actions.class">Symfony\Bundle\TwigBundle\Extension\ActionsExtension</parameter>
<parameter key="twig.extension.code.class">Symfony\Bridge\Twig\Extension\CodeExtension</parameter>
<parameter key="twig.extension.routing.class">Symfony\Bridge\Twig\Extension\RoutingExtension</parameter>
@ -86,10 +85,10 @@
<argument type="service" id="translator" />
</service>
<service id="twig.extension.assets" class="%twig.extension.assets.class%" public="false">
<service id="twig.extension.assets" class="Symfony\Bridge\Twig\Extension\AssetExtension" public="false">
<tag name="twig.extension" />
<argument type="service" id="service_container" />
<argument type="service" id="router.request_context" on-invalid="null" />
<argument type="service" id="assets.packages" />
<argument type="service" id="twig.extension.httpfoundation" />
</service>
<service id="twig.extension.actions" class="%twig.extension.actions.class%" public="false">

View File

@ -1,7 +1,7 @@
{% extends 'TwigBundle::layout.html.twig' %}
{% block head %}
<link href="{{ asset('bundles/framework/css/exception.css', absolute=true) }}" rel="stylesheet" type="text/css" media="all" />
<link href="{{ absolute_url(asset('bundles/framework/css/exception.css')) }}" rel="stylesheet" type="text/css" media="all" />
{% endblock %}
{% block title %}

View File

@ -4,8 +4,8 @@
<meta charset="{{ _charset }}" />
<meta name="robots" content="noindex,nofollow" />
<title>{% block title %}{% endblock %}</title>
<link href="{{ asset('bundles/framework/css/structure.css', absolute=true) }}" rel="stylesheet" />
<link href="{{ asset('bundles/framework/css/body.css', absolute=true) }}" rel="stylesheet" />
<link href="{{ absolute_url(asset('bundles/framework/css/structure.css')) }}" rel="stylesheet" />
<link href="{{ absolute_url(asset('bundles/framework/css/body.css')) }}" rel="stylesheet" />
{% block head %}{% endblock %}
</head>
<body>

View File

@ -15,7 +15,7 @@ use Symfony\Bundle\TwigBundle\Extension\AssetsExtension;
use Symfony\Bundle\TwigBundle\Tests\TestCase;
use Symfony\Component\Routing\RequestContext;
class AssetsExtensionTest extends TestCase
class LegacyAssetsExtensionTest extends TestCase
{
/**
* @dataProvider provideGetGetAssetUrlArguments

View File

@ -52,9 +52,6 @@ class WebProfilerExtensionTest extends TestCase
$this->container->addScope(new Scope('request'));
$this->container->register('request', 'Symfony\\Component\\HttpFoundation\\Request')->setScope('request');
$this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'));
$this->container->register('templating.helper.assets', $this->getMockClass('Symfony\\Component\\Templating\\Helper\\AssetsHelper'));
$this->container->register('templating.helper.router', $this->getMockClass('Symfony\\Bundle\\FrameworkBundle\\Templating\\Helper\\RouterHelper'))
->addArgument(new Reference('router'));
$this->container->register('twig', 'Twig_Environment');
$this->container->setParameter('kernel.bundles', array());
$this->container->setParameter('kernel.cache_dir', __DIR__);

View File

@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@ -0,0 +1,7 @@
CHANGELOG
=========
2.7.0
-----
* added the component

View File

@ -0,0 +1,34 @@
<?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\Context;
/**
* Holds information about the current request.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ContextInterface
{
/**
* Gets the base path.
*
* @return string The base path
*/
public function getBasePath();
/**
* Checks whether the request is secure or not.
*
* @return bool true if the request is secure, false otherwise
*/
public function isSecure();
}

View File

@ -0,0 +1,38 @@
<?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\Context;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* A context that does nothing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NullContext implements ContextInterface
{
/**
* {@inheritdoc}
*/
public function getBasePath()
{
return '';
}
/**
* {@inheritdoc}
*/
public function isSecure()
{
return false;
}
}

View File

@ -0,0 +1,53 @@
<?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\Context;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Uses a RequestStack to populate the context.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RequestStackContext implements ContextInterface
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function getBasePath()
{
if (!$request = $this->requestStack->getMasterRequest()) {
return '';
}
return $request->getBasePath();
}
/**
* {@inheritdoc}
*/
public function isSecure()
{
if (!$request = $this->requestStack->getMasterRequest()) {
return false;
}
return $request->isSecure();
}
}

View File

@ -0,0 +1,21 @@
<?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\Exception;
/**
* Base ExceptionInterface for the Asset component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface
{
}

View File

@ -0,0 +1,21 @@
<?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\Exception;
/**
* Base InvalidArgumentException for the Asset component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -0,0 +1,21 @@
<?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\Exception;
/**
* Base LogicException for the Asset component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2004-2015 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.

View File

@ -0,0 +1,72 @@
<?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;
use Symfony\Component\Asset\Context\ContextInterface;
use Symfony\Component\Asset\Context\NullContext;
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
/**
* Basic package that adds a version to asset URLs.
*
* @author Kris Wallsmith <kris@symfony.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class Package implements PackageInterface
{
private $versionStrategy;
private $context;
public function __construct(VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
$this->versionStrategy = $versionStrategy;
$this->context = $context ?: new NullContext();
}
/**
* {@inheritdoc}
*/
public function getVersion($path)
{
return $this->versionStrategy->getVersion($path);
}
/**
* {@inheritdoc}
*/
public function getUrl($path)
{
if ($this->isAbsoluteUrl($path)) {
return $path;
}
return $this->versionStrategy->applyVersion($path);
}
/**
* @return ContextInterface
*/
protected function getContext()
{
return $this->context;
}
protected function getVersionStrategy()
{
return $this->versionStrategy;
}
protected function isAbsoluteUrl($url)
{
return false !== strpos($url, '://') || '//' === substr($url, 0, 2);
}
}

View File

@ -0,0 +1,38 @@
<?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;
/**
* Asset package interface.
*
* @author Kris Wallsmith <kris@symfony.com>
*/
interface PackageInterface
{
/**
* Returns the asset version for an asset.
*
* @param string $path A path
*
* @return string The version string
*/
public function getVersion($path);
/**
* Returns an absolute or root-relative public path.
*
* @param string $path A path
*
* @return string The public path
*/
public function getUrl($path);
}

View File

@ -0,0 +1,116 @@
<?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;
use Symfony\Component\Asset\Exception\InvalidArgumentException;
use Symfony\Component\Asset\Exception\LogicException;
/**
* Helps manage asset URLs.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Kris Wallsmith <kris@symfony.com>
*/
class Packages
{
private $defaultPackage;
private $packages = array();
/**
* @param PackageInterface $defaultPackage The default package
* @param PackageInterface[] $packages Additional packages indexed by name
*/
public function __construct(PackageInterface $defaultPackage = null, array $packages = array())
{
$this->defaultPackage = $defaultPackage;
foreach ($packages as $name => $package) {
$this->addPackage($name, $package);
}
}
/**
* Sets the default package.
*
* @param PackageInterface $defaultPackage The default package
*/
public function setDefaultPackage(PackageInterface $defaultPackage)
{
$this->defaultPackage = $defaultPackage;
}
/**
* Adds a package.
*
* @param string $name The package name
* @param PackageInterface $package The package
*/
public function addPackage($name, PackageInterface $package)
{
$this->packages[$name] = $package;
}
/**
* Returns an asset package.
*
* @param string $name The name of the package or null for the default package
*
* @return PackageInterface An asset package
*
* @throws InvalidArgumentException If there is no package by that name
* @throws LogicException If no default package is defined
*/
public function getPackage($name = null)
{
if (null === $name) {
if (null === $this->defaultPackage) {
throw new LogicException('There is no default asset package, configure one first.');
}
return $this->defaultPackage;
}
if (!isset($this->packages[$name])) {
throw new InvalidArgumentException(sprintf('There is no "%s" asset package.', $name));
}
return $this->packages[$name];
}
/**
* Gets the version to add to public URL.
*
* @param string $path A public path
* @param string $packageName A package name
*
* @return string The current version
*/
public function getVersion($path, $packageName = null)
{
return $this->getPackage($packageName)->getVersion($path);
}
/**
* Returns the public path.
*
* Absolute paths (i.e. http://...) are returned unmodified.
*
* @param string $path A public path
* @param string $packageName The name of the asset package to use
*
* @return string A public path which takes into account the base path and URL path
*/
public function getUrl($path, $packageName = null)
{
return $this->getPackage($packageName)->getUrl($path);
}
}

View File

@ -0,0 +1,71 @@
<?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;
use Symfony\Component\Asset\Context\ContextInterface;
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
/**
* Package that adds a base path to asset URLs in addition to a version.
*
* In addition to the provided base path, this package also automatically
* prepends the current request base path if a Context is available to
* allow a website to be hosted easily under any given path under the Web
* Server root directory.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PathPackage extends Package
{
private $basePath;
/**
* @param string $basePath The base path to be prepended to relative paths
* @param VersionStrategyInterface $versionStrategy The version strategy
*/
public function __construct($basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
parent::__construct($versionStrategy, $context);
if (!$basePath) {
$this->basePath = '/';
} else {
if ('/' != $basePath[0]) {
$basePath = '/'.$basePath;
}
$this->basePath = rtrim($basePath, '/').'/';
}
}
/**
* {@inheritdoc}
*/
public function getUrl($path)
{
if ($this->isAbsoluteUrl($path)) {
return $path;
}
return $this->getBasePath().ltrim($this->getVersionStrategy()->applyVersion($path), '/');
}
/**
* Returns the base path.
*
* @return string The base path
*/
public function getBasePath()
{
return $this->getContext()->getBasePath().$this->basePath;
}
}

View File

@ -0,0 +1,166 @@
Asset Component
===============
The Asset component manages asset URLs.
Versioned Asset URLs
--------------------
The basic `Package` adds a version to generated asset URLs:
```php
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
$package = new Package(new StaticVersionStrategy('v1'));
echo $package->getUrl('/me.png');
// /me.png?v1
```
The default format can be configured:
```php
$package = new Package(new StaticVersionStrategy('v1', '%s?version=%s'));
echo $package->getUrl('/me.png');
// /me.png?version=v1
// put the version before the path
$package = new Package(new StaticVersionStrategy('v1', 'version-%2$s/%1$s'));
echo $package->getUrl('/me.png');
// /version-v1/me.png
```
Asset URLs Base Path
--------------------
When all assets are stored in a common path, use the `PathPackage` to avoid
repeating yourself:
```php
use Symfony\Component\Asset\PathPackage;
$package = new PathPackage('/images', new StaticVersionStrategy('v1'));
echo $package->getUrl('/me.png');
// /images/me.png?v1
```
Asset URLs Base URLs
--------------------
If your assets are hosted on different domain name than the main website, use
the `UrlPackage` class:
```php
use Symfony\Component\Asset\UrlPackage;
$package = new UrlPackage('http://assets.example.com/images/', new StaticVersionStrategy('v1'));
echo $package->getUrl('/me.png');
// http://assets.example.com/images/me.png?v1
```
One technique used to speed up page rendering in browsers is to use several
domains for assets; this is possible by passing more than one base URLs:
```php
use Symfony\Component\Asset\UrlPackage;
$urls = array(
'http://a1.example.com/images/',
'http://a2.example.com/images/',
);
$package = new UrlPackage($urls, new StaticVersionStrategy('v1'));
echo $package->getUrl('/me.png');
// http://a1.example.com/images/me.png?v1
```
Note that it's also guaranteed that any given path will always use the same
base URL to be nice with HTTP caching mechanisms.
HttpFoundation Integration
--------------------------
If you are using HttpFoundation for your project, set the Context to get
additional features for free:
```php
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
$package = new PathPackage('images', new StaticVersionStrategy('v1'));
$package->setContext(new RequestStackContext($requestStack));
echo $package->getUrl('/me.png');
// /somewhere/images/me.png?v1
```
In addition to the configured base path, `PathPackage` now also automatically
prepends the current request base URL to assets to allow your website to be
hosted anywhere under the web server root directory.
```php
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Context\RequestStackContext;
$package = new UrlPackage(array('http://example.com/', 'https://example.com/'), new StaticVersionStrategy('v1'));
$package->setContext(new RequestStackContext($requestStack));
echo $package->getUrl('/me.png');
// https://example.com/images/me.png?v1
```
`UrlPackage` now uses the current request scheme (HTTP or HTTPs) to select an
appropriate base URL (HTTPs or protocol-relative URLs for HTTPs requests, any
base URL for HTTP requests).
Named Packages
--------------
The `Packages` class allows to easily manages several packages in a single
project by naming packages:
```php
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\Packages;
// by default, just add a version to all assets
$versionStrategy = new StaticVersionStrategy('v1');
$defaultPackage = new Asset\Package($versionStrategy);
$namedPackages = array(
// images are hosted on another web server
'img' => new Asset\UrlPackage('http://img.example.com/', $versionStrategy),
// documents are stored deeply under the web root directory
// let's create a shortcut
'doc' => new Asset\PathPackage('/somewhere/deep/for/documents', $versionStrategy),
);
// bundle all packages to make it easy to use them
$packages = new Asset\Packages($defaultPackage, $namedPackages);
echo $packages->getUrl('/some.css');
// /some.css?v1
echo $packages->getUrl('/me.png', 'img');
// http://img.example.com/me.png?v1
echo $packages->getUrl('/me.pdf', 'doc');
// /somewhere/deep/for/documents/me.pdf?v1
```
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/Asset/
$ composer update
$ phpunit

View File

@ -0,0 +1,54 @@
<?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\Tests;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
class PackageTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($version, $format, $path, $expected)
{
$package = new Package($version ? new StaticVersionStrategy($version, $format) : new EmptyVersionStrategy());
$this->assertEquals($expected, $package->getUrl($path));
}
public function getConfigs()
{
return array(
array('v1', '', 'http://example.com/foo', 'http://example.com/foo'),
array('v1', '', 'https://example.com/foo', 'https://example.com/foo'),
array('v1', '', '//example.com/foo', '//example.com/foo'),
array('v1', '', '/foo', '/foo?v1'),
array('v1', '', 'foo', 'foo?v1'),
array(null, '', '/foo', '/foo'),
array(null, '', 'foo', 'foo'),
array('v1', 'version-%2$s/%1$s', '/foo', '/version-v1/foo'),
array('v1', 'version-%2$s/%1$s', 'foo', 'version-v1/foo'),
array('v1', 'version-%2$s/%1$s', 'foo/', 'version-v1/foo/'),
array('v1', 'version-%2$s/%1$s', '/foo/', '/version-v1/foo/'),
);
}
public function testGetVersion()
{
$package = new Package(new StaticVersionStrategy('v1'));
$this->assertEquals('v1', $package->getVersion('/foo'));
}
}

View File

@ -0,0 +1,76 @@
<?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\Tests;
use Symfony\Component\Asset\Package;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\Asset\Exception\InvalidArgumentException;
use Symfony\Component\Asset\Exception\LogicException;
class PackagesTest extends \PHPUnit_Framework_TestCase
{
public function testGetterSetters()
{
$packages = new Packages();
$packages->setDefaultPackage($default = $this->getMock('Symfony\Component\Asset\PackageInterface'));
$packages->addPackage('a', $a = $this->getMock('Symfony\Component\Asset\PackageInterface'));
$this->assertEquals($default, $packages->getPackage());
$this->assertEquals($a, $packages->getPackage('a'));
$packages = new Packages($default, array('a' => $a));
$this->assertEquals($default, $packages->getPackage());
$this->assertEquals($a, $packages->getPackage('a'));
}
public function testGetVersion()
{
$packages = new Packages(
new Package(new StaticVersionStrategy('default')),
array('a' => new Package(new StaticVersionStrategy('a')))
);
$this->assertEquals('default', $packages->getVersion('/foo'));
$this->assertEquals('a', $packages->getVersion('/foo', 'a'));
}
public function testGetUrl()
{
$packages = new Packages(
new Package(new StaticVersionStrategy('default')),
array('a' => new Package(new StaticVersionStrategy('a')))
);
$this->assertEquals('/foo?default', $packages->getUrl('/foo'));
$this->assertEquals('/foo?a', $packages->getUrl('/foo', 'a'));
}
/**
* @expectedException LogicException
*/
public function testNoDefaultPackage()
{
$packages = new Packages();
$packages->getPackage();
}
/**
* @expectedException InvalidArgumentException
*/
public function testUndefinedPackage()
{
$packages = new Packages();
$packages->getPackage('a');
}
}

View File

@ -0,0 +1,84 @@
<?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\Tests;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
class PathPackageTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($basePath, $format, $path, $expected)
{
$package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format));
$this->assertEquals($expected, $package->getUrl($path));
}
public function getConfigs()
{
return array(
array('/foo', '', 'http://example.com/foo', 'http://example.com/foo'),
array('/foo', '', 'https://example.com/foo', 'https://example.com/foo'),
array('/foo', '', '//example.com/foo', '//example.com/foo'),
array('', '', '/foo', '/foo?v1'),
array('/foo', '', '/foo', '/foo/foo?v1'),
array('/foo', '', 'foo', '/foo/foo?v1'),
array('foo', '', 'foo', '/foo/foo?v1'),
array('foo/', '', 'foo', '/foo/foo?v1'),
array('/foo/', '', 'foo', '/foo/foo?v1'),
array('/foo', 'version-%2$s/%1$s', '/foo', '/foo/version-v1/foo'),
array('/foo', 'version-%2$s/%1$s', 'foo', '/foo/version-v1/foo'),
array('/foo', 'version-%2$s/%1$s', 'foo/', '/foo/version-v1/foo/'),
array('/foo', 'version-%2$s/%1$s', '/foo/', '/foo/version-v1/foo/'),
);
}
/**
* @dataProvider getContextConfigs
*/
public function testGetUrlWithContext($basePathRequest, $basePath, $format, $path, $expected)
{
$package = new PathPackage($basePath, new StaticVersionStrategy('v1', $format), $this->getContext($basePathRequest));
$this->assertEquals($expected, $package->getUrl($path));
}
public function getContextConfigs()
{
return array(
array('', '/foo', '', '/foo', '/foo/foo?v1'),
array('', '/foo', '', 'foo', '/foo/foo?v1'),
array('', 'foo', '', 'foo', '/foo/foo?v1'),
array('', 'foo/', '', 'foo', '/foo/foo?v1'),
array('', '/foo/', '', 'foo', '/foo/foo?v1'),
array('/bar', '/foo', '', '/foo', '/bar/foo/foo?v1'),
array('/bar', '/foo', '', 'foo', '/bar/foo/foo?v1'),
array('/bar', 'foo', '', 'foo', '/bar/foo/foo?v1'),
array('/bar', 'foo/', '', 'foo', '/bar/foo/foo?v1'),
array('/bar', '/foo/', '', 'foo', '/bar/foo/foo?v1'),
);
}
private function getContext($basePath)
{
$context = $this->getMock('Symfony\Component\Asset\Context\ContextInterface');
$context->expects($this->any())->method('getBasePath')->will($this->returnValue($basePath));
return $context;
}
}

View File

@ -0,0 +1,104 @@
<?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\Tests;
use Symfony\Component\Asset\UrlPackage;
use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
use Symfony\Component\Asset\Exception\InvalidArgumentException;
use Symfony\Component\Asset\Exception\LogicException;
class UrlPackageTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getConfigs
*/
public function testGetUrl($baseUrls, $format, $path, $expected)
{
$package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format));
$this->assertEquals($expected, $package->getUrl($path));
}
public function getConfigs()
{
return array(
array('http://example.net', '', 'http://example.com/foo', 'http://example.com/foo'),
array('http://example.net', '', 'https://example.com/foo', 'https://example.com/foo'),
array('http://example.net', '', '//example.com/foo', '//example.com/foo'),
array('http://example.com', '', '/foo', 'http://example.com/foo?v1'),
array('http://example.com', '', 'foo', 'http://example.com/foo?v1'),
array('http://example.com/', '', 'foo', 'http://example.com/foo?v1'),
array('http://example.com/foo', '', 'foo', 'http://example.com/foo/foo?v1'),
array('http://example.com/foo/', '', 'foo', 'http://example.com/foo/foo?v1'),
array(array('http://example.com'), '', '/foo', 'http://example.com/foo?v1'),
array(array('http://example.com', 'http://example.net'), '', '/foo', 'http://example.com/foo?v1'),
array(array('http://example.com', 'http://example.net'), '', '/fooa', 'http://example.net/fooa?v1'),
array('http://example.com', 'version-%2$s/%1$s', '/foo', 'http://example.com/version-v1/foo'),
array('http://example.com', 'version-%2$s/%1$s', 'foo', 'http://example.com/version-v1/foo'),
array('http://example.com', 'version-%2$s/%1$s', 'foo/', 'http://example.com/version-v1/foo/'),
array('http://example.com', 'version-%2$s/%1$s', '/foo/', 'http://example.com/version-v1/foo/'),
);
}
/**
* @dataProvider getContextConfigs
*/
public function testGetUrlWithContext($secure, $baseUrls, $format, $path, $expected)
{
$package = new UrlPackage($baseUrls, new StaticVersionStrategy('v1', $format), $this->getContext($secure));
$this->assertEquals($expected, $package->getUrl($path));
}
public function getContextConfigs()
{
return array(
array(false, 'http://example.com', '', 'foo', 'http://example.com/foo?v1'),
array(false, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'),
array(false, array('http://example.com', 'https://example.com'), '', 'foo', 'http://example.com/foo?v1'),
array(false, array('http://example.com', 'https://example.com'), '', 'fooa', 'https://example.com/fooa?v1'),
array(false, array('http://example.com/bar'), '', 'foo', 'http://example.com/bar/foo?v1'),
array(false, array('http://example.com/bar/'), '', 'foo', 'http://example.com/bar/foo?v1'),
array(false, array('//example.com/bar/'), '', 'foo', '//example.com/bar/foo?v1'),
array(true, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'),
array(true, array('http://example.com', 'https://example.com'), '', 'foo', 'https://example.com/foo?v1'),
);
}
/**
* @expectedException LogicException
*/
public function testNoBaseUrls()
{
new UrlPackage(array(), new EmptyVersionStrategy());
}
/**
* @expectedException InvalidArgumentException
*/
public function testWrongBaseUrl()
{
new UrlPackage(array('not-a-url'), new EmptyVersionStrategy());
}
private function getContext($secure)
{
$context = $this->getMock('Symfony\Component\Asset\Context\ContextInterface');
$context->expects($this->any())->method('isSecure')->will($this->returnValue($secure));
return $context;
}
}

View File

@ -0,0 +1,135 @@
<?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;
use Symfony\Component\Asset\Context\ContextInterface;
use Symfony\Component\Asset\VersionStrategy\VersionStrategyInterface;
use Symfony\Component\Asset\Exception\InvalidArgumentException;
use Symfony\Component\Asset\Exception\LogicException;
/**
* Package that adds a base URL to asset URLs in addition to a version.
*
* The package allows to use more than one base URLs in which case
* it randomly chooses one for each asset; it also guarantees that
* any given path will always use the same base URL to be nice with
* HTTP caching mechanisms.
*
* When the request context is available, this package can choose the
* best base URL to use based on the current request scheme:
*
* * For HTTP request, it chooses between all base URLs;
* * For HTTPs requests, it chooses between HTTPs base URLs and relative protocol URLs
* or falls back to any base URL if no secure ones are available.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class UrlPackage extends Package
{
private $baseUrls = array();
private $sslUrls;
private $sslPackage;
/**
* @param string|array $baseUrls Base asset URLs
* @param VersionStrategyInterface $versionStrategy The version strategy
*/
public function __construct($baseUrls = array(), VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
parent::__construct($versionStrategy, $context);
if (!is_array($baseUrls)) {
$baseUrls = (array) $baseUrls;
}
if (!$baseUrls) {
throw new LogicException('You must provide at least one base URL.');
}
foreach ($baseUrls as $baseUrl) {
$this->baseUrls[] = rtrim($baseUrl, '/');
}
$sslUrls = $this->getSslUrls($baseUrls);
if ($sslUrls && $baseUrls !== $sslUrls) {
$this->sslPackage = new UrlPackage($sslUrls, $versionStrategy);
}
}
/**
* {@inheritdoc}
*/
public function getUrl($path)
{
if ($this->isAbsoluteUrl($path)) {
return $path;
}
if (null !== $this->sslPackage && $this->getContext()->isSecure()) {
return $this->sslPackage->getUrl($path);
}
$url = $this->getVersionStrategy()->applyVersion($path);
if ($url && '/' != $url[0]) {
$url = '/'.$url;
}
return $this->getBaseUrl($path).$url;
}
/**
* Returns the base URL for a path.
*
* @param string $path
*
* @return string The base URL
*/
public function getBaseUrl($path)
{
if (1 === count($this->baseUrls)) {
return $this->baseUrls[0];
}
return $this->baseUrls[$this->chooseBaseUrl($path)];
}
/**
* Determines which base URL to use for the given path.
*
* Override this method to change the default distribution strategy.
* This method should always return the same base URL for a given path.
*
* @param string $path
*
* @return string The base URL for the given path
*/
protected function chooseBaseUrl($path)
{
return fmod(hexdec(substr(hash('sha256', $path), 0, 10)), count($this->baseUrls));
}
private function getSslUrls($urls)
{
$sslUrls = array();
foreach ($urls as $url) {
if ('https://' === substr($url, 0, 8) || '//' === substr($url, 0, 2)) {
$sslUrls[] = $url;
} elseif ('http://' !== substr($url, 0, 7)) {
throw new InvalidArgumentException(sprintf('"%s" is not a valid URL', $url));
}
}
return $sslUrls;
}
}

View File

@ -0,0 +1,36 @@
<?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\VersionStrategy;
/**
* Disable version for all assets.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EmptyVersionStrategy implements VersionStrategyInterface
{
/**
* {@inheritdoc}
*/
public function getVersion($path)
{
return '';
}
/**
* {@inheritdoc}
*/
public function applyVersion($path)
{
return $path;
}
}

View File

@ -0,0 +1,51 @@
<?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\VersionStrategy;
/**
* Returns the same version for all assets.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StaticVersionStrategy implements VersionStrategyInterface
{
private $version;
private $format;
public function __construct($version, $format = null)
{
$this->version = $version;
$this->format = $format ?: '%s?%s';
}
/**
* {@inheritdoc}
*/
public function getVersion($path)
{
return $this->version;
}
/**
* {@inheritdoc}
*/
public function applyVersion($path)
{
$versionized = sprintf($this->format, ltrim($path, '/'), $this->getVersion($path));
if ($path && '/' == $path[0]) {
return '/'.$versionized;
}
return $versionized;
}
}

View File

@ -0,0 +1,38 @@
<?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\VersionStrategy;
/**
* Asset version strategy interface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface VersionStrategyInterface
{
/**
* Returns the asset version for an asset.
*
* @param string $path A path
*
* @return string The version string
*/
public function getVersion($path);
/**
* Applies version to the supplied path.
*
* @param string $path A path
*
* @return string The versionized path
*/
public function applyVersion($path);
}

View File

@ -0,0 +1,36 @@
{
"name": "symfony/asset",
"type": "library",
"description": "Symfony Asset Component",
"keywords": [],
"homepage": "http://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.3"
},
"suggest": {
"symfony/http-foundation": ""
},
"require-dev": {
"symfony/http-foundation": "~2.4"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Asset\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
}
}

View File

@ -0,0 +1,24 @@
<?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"
>
<testsuites>
<testsuite name="Symfony Asset Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./vendor</directory>
<directory>./Tests</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -11,10 +11,14 @@
namespace Symfony\Component\Templating\Asset;
trigger_error('The Symfony\Component\Templating\Asset\Package is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
/**
* The basic package will add a version to asset URLs.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class Package implements PackageInterface
{

View File

@ -11,10 +11,14 @@
namespace Symfony\Component\Templating\Asset;
trigger_error('The Symfony\Component\Templating\Asset\PackageInterface is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
/**
* Asset package interface.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
interface PackageInterface
{

View File

@ -11,10 +11,14 @@
namespace Symfony\Component\Templating\Asset;
trigger_error('The Symfony\Component\Templating\Asset\PathPackage is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
/**
* The path packages adds a version and a base path to asset URLs.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class PathPackage extends Package
{

View File

@ -11,10 +11,14 @@
namespace Symfony\Component\Templating\Asset;
trigger_error('The Symfony\Component\Templating\Asset\UrlPackage is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
/**
* The URL packages adds a version and a base URL to asset URLs.
*
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class UrlPackage extends Package
{

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Templating\Helper;
trigger_error('The Symfony\Component\Templating\Helper\AssetsHelper is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
use Symfony\Component\Templating\Asset\PathPackage;
use Symfony\Component\Templating\Asset\UrlPackage;
@ -25,6 +27,8 @@ use Symfony\Component\Templating\Asset\UrlPackage;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class AssetsHelper extends CoreAssetsHelper
{

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Templating\Helper;
trigger_error('The Symfony\Component\Templating\Helper\CoreAssetsHelper is deprecated since version 2.7 and will be removed in 3.0. Use the Asset component instead.', E_USER_DEPRECATED);
use Symfony\Component\Templating\Asset\PackageInterface;
/**
@ -24,6 +26,8 @@ use Symfony\Component\Templating\Asset\PackageInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Kris Wallsmith <kris@symfony.com>
*
* @deprecated since 2.7, will be removed in 3.0. Use the Asset component instead.
*/
class CoreAssetsHelper extends Helper implements PackageInterface
{

View File

@ -13,8 +13,13 @@ namespace Symfony\Component\Templating\Tests\Helper;
use Symfony\Component\Templating\Helper\AssetsHelper;
class AssetsHelperTest extends \PHPUnit_Framework_TestCase
class LegacyAssetsHelperTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
}
public function testGetVersion()
{
$helper = new AssetsHelper(null, array(), 'foo');

View File

@ -13,12 +13,14 @@ namespace Symfony\Component\Templating\Tests\Helper;
use Symfony\Component\Templating\Helper\CoreAssetsHelper;
class CoreAssetsHelperTest extends \PHPUnit_Framework_TestCase
class LegacyCoreAssetsHelperTest extends \PHPUnit_Framework_TestCase
{
protected $package;
protected function setUp()
{
$this->iniSet('error_reporting', -1 & ~E_USER_DEPRECATED);
$this->package = $this->getMock('Symfony\Component\Templating\Asset\PackageInterface');
}