[Asset] Adding a new version strategy that reads from a manifest JSON file

This commit is contained in:
Ryan Weaver 2017-03-17 15:10:06 -04:00 committed by Fabien Potencier
parent bafa8e29e0
commit 07fec2bbad
16 changed files with 264 additions and 46 deletions

View File

@ -37,5 +37,7 @@ return PhpCsFixer\Config::create()
->notPath('Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php')
// explicit heredoc test
->notPath('Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Resources/views/translation.html.php')
// purposefully invalid JSON
->notPath('Symfony/Component/Asset/Tests/fixtures/manifest-invalid.json')
)
;

View File

@ -4,6 +4,8 @@ CHANGELOG
3.3.0
-----
* Added a new new version strategy option called json_manifest_path
that allows you to use the `JsonManifestVersionStrategy`.
* Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions
* Deprecated `cache:clear` with warmup (always call it with `--no-warmup`)
* Deprecated the "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter

View File

@ -551,6 +551,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('version_strategy')->defaultNull()->end()
->scalarNode('version')->defaultNull()->end()
->scalarNode('version_format')->defaultValue('%%s?%%s')->end()
->scalarNode('json_manifest_path')->defaultNull()->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
@ -567,6 +568,18 @@ class Configuration implements ConfigurationInterface
})
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets".')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets".')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets".')
->end()
->fixXmlConfig('package')
->children()
->arrayNode('packages')
@ -582,6 +595,7 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->scalarNode('version_format')->defaultNull()->end()
->scalarNode('json_manifest_path')->defaultNull()->end()
->scalarNode('base_path')->defaultValue('')->end()
->arrayNode('base_urls')
->requiresAtLeastOneElement()
@ -598,6 +612,18 @@ class Configuration implements ConfigurationInterface
})
->thenInvalid('You cannot use both "version_strategy" and "version" at the same time under "assets" packages.')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version_strategy']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets" packages.')
->end()
->validate()
->ifTrue(function ($v) {
return isset($v['version']) && isset($v['json_manifest_path']);
})
->thenInvalid('You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.')
->end()
->end()
->end()
->end()

View File

@ -810,7 +810,7 @@ class FrameworkExtension extends Extension
if ($config['version_strategy']) {
$defaultVersion = new Reference($config['version_strategy']);
} else {
$defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], '_default');
$defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default');
}
$defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion);
@ -820,11 +820,14 @@ class FrameworkExtension extends Extension
foreach ($config['packages'] as $name => $package) {
if (null !== $package['version_strategy']) {
$version = new Reference($package['version_strategy']);
} elseif (!array_key_exists('version', $package)) {
} elseif (!array_key_exists('version', $package) && null === $package['json_manifest_path']) {
// if neither version nor json_manifest_path are specified, use the default
$version = $defaultVersion;
} else {
// let format fallback to main version_format
$format = $package['version_format'] ?: $config['version_format'];
$version = $this->createVersion($container, $package['version'], $format, $name);
$version = isset($package['version']) ? $package['version'] : null;
$version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name);
}
$container->setDefinition('assets._package_'.$name, $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version));
@ -856,20 +859,29 @@ class FrameworkExtension extends Extension
return $package;
}
private function createVersion(ContainerBuilder $container, $version, $format, $name)
private function createVersion(ContainerBuilder $container, $version, $format, $jsonManifestPath, $name)
{
if (null === $version) {
return new Reference('assets.empty_version_strategy');
// Configuration prevents $version and $jsonManifestPath from being set
if (null !== $version) {
$def = new ChildDefinition('assets.static_version_strategy');
$def
->replaceArgument(0, $version)
->replaceArgument(1, $format)
;
$container->setDefinition('assets._version_'.$name, $def);
return new Reference('assets._version_'.$name);
}
$def = new ChildDefinition('assets.static_version_strategy');
$def
->replaceArgument(0, $version)
->replaceArgument(1, $format)
;
$container->setDefinition('assets._version_'.$name, $def);
if (null !== $jsonManifestPath) {
$def = new ChildDefinition('assets.json_manifest_version_strategy');
$def->replaceArgument(0, $jsonManifestPath);
$container->setDefinition('assets._version_'.$name, $def);
return new Reference('assets._version_'.$name);
return new Reference('assets._version_'.$name);
}
return new Reference('assets.empty_version_strategy');
}
/**

View File

@ -39,6 +39,10 @@
<service id="assets.empty_version_strategy" class="Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy" public="false" />
<service id="assets.json_manifest_version_strategy" class="Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy" abstract="true" public="false">
<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">

View File

@ -131,6 +131,7 @@
<xsd:attribute name="version-strategy" type="xsd:string" />
<xsd:attribute name="version" type="xsd:string" />
<xsd:attribute name="version-format" type="xsd:string" />
<xsd:attribute name="json-manifest-path" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="package">
@ -143,6 +144,7 @@
<xsd:attribute name="version-strategy" type="xsd:string" />
<xsd:attribute name="version" type="xsd:string" />
<xsd:attribute name="version-format" type="xsd:string" />
<xsd:attribute name="json-manifest-path" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="templating">

View File

@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration;
use Symfony\Bundle\FullStack;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Processor;
class ConfigurationTest extends TestCase
@ -120,54 +121,67 @@ class ConfigurationTest extends TestCase
'base_path' => '',
'base_urls' => array(),
'packages' => array(),
'json_manifest_path' => null,
);
$this->assertEquals($defaultConfig, $config['assets']);
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage You cannot use both "version_strategy" and "version" at the same time under "assets".
* @dataProvider provideInvalidAssetConfigurationTests
*/
public function testInvalidVersionStrategy()
public function testInvalidAssetsConfiguration(array $assetConfig, $expectedMessage)
{
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(
InvalidConfigurationException::class,
$expectedMessage
);
if (method_exists($this, 'expectExceptionMessage')) {
$this->expectExceptionMessage($expectedMessage);
}
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'assets' => array(
'base_urls' => '//example.com',
'version' => 1,
'version_strategy' => 'foo',
array(
'assets' => $assetConfig,
),
),
));
));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage You cannot use both "version_strategy" and "version" at the same time under "assets" packages.
*/
public function testInvalidPackageVersionStrategy()
public function provideInvalidAssetConfigurationTests()
{
$processor = new Processor();
$configuration = new Configuration(true);
$processor->processConfiguration($configuration, array(
array(
'assets' => array(
'base_urls' => '//example.com',
'version' => 1,
'packages' => array(
'foo' => array(
'base_urls' => '//example.com',
'version' => 1,
'version_strategy' => 'foo',
),
),
// helper to turn config into embedded package config
$createPackageConfig = function (array $packageConfig) {
return array(
'base_urls' => '//example.com',
'version' => 1,
'packages' => array(
'foo' => $packageConfig,
),
),
));
);
};
$config = array(
'version' => 1,
'version_strategy' => 'foo',
);
yield array($config, 'You cannot use both "version_strategy" and "version" at the same time under "assets".');
yield array($createPackageConfig($config), 'You cannot use both "version_strategy" and "version" at the same time under "assets" packages.');
$config = array(
'json_manifest_path' => '/foo.json',
'version_strategy' => 'foo',
);
yield array($config, 'You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets".');
yield array($createPackageConfig($config), 'You cannot use both "version_strategy" and "json_manifest_path" at the same time under "assets" packages.');
$config = array(
'json_manifest_path' => '/foo.json',
'version' => '1',
);
yield array($config, 'You cannot use both "version" and "json_manifest_path" at the same time under "assets".');
yield array($createPackageConfig($config), 'You cannot use both "version" and "json_manifest_path" at the same time under "assets" packages.');
}
protected static function getBundleDefaultConfig()
@ -274,6 +288,7 @@ class ConfigurationTest extends TestCase
'base_path' => '',
'base_urls' => array(),
'packages' => array(),
'json_manifest_path' => null,
),
'cache' => array(
'pools' => array(),

View File

@ -24,6 +24,9 @@ $container->loadFromExtension('framework', array(
'base_urls' => array('https://bar2.example.com'),
'version_strategy' => 'assets.custom_version_strategy',
),
'json_manifest_strategy' => array(
'json_manifest_path' => '/path/to/manifest.json',
),
),
),
));

View File

@ -21,6 +21,7 @@
<framework:package name="bar_version_strategy" version-strategy="assets.custom_version_strategy">
<framework:base-url>https://bar_version_strategy.example.com</framework:base-url>
</framework:package>
<framework:package name="json_manifest_strategy" json-manifest-path="/path/to/manifest.json" />
</framework:assets>
</framework:config>
</container>

View File

@ -17,3 +17,5 @@ framework:
bar_version_strategy:
base_urls: ["https://bar_version_strategy.example.com"]
version_strategy: assets.custom_version_strategy
json_manifest_strategy:
json_manifest_path: '/path/to/manifest.json'

View File

@ -374,7 +374,7 @@ abstract class FrameworkExtensionTest extends TestCase
// packages
$packages = $packages->getArgument(1);
$this->assertCount(5, $packages);
$this->assertCount(6, $packages);
$package = $container->getDefinition((string) $packages['images_path']);
$this->assertPathPackage($container, $package, '/foo', 'SomeVersionScheme', '%%s?version=%%s');
@ -390,6 +390,11 @@ abstract class FrameworkExtensionTest extends TestCase
$package = $container->getDefinition((string) $packages['bar_version_strategy']);
$this->assertEquals('assets.custom_version_strategy', (string) $package->getArgument(1));
$package = $container->getDefinition((string) $packages['json_manifest_strategy']);
$versionStrategy = $container->getDefinition((string) $package->getArgument(1));
$this->assertEquals('assets.json_manifest_version_strategy', $versionStrategy->getParent());
$this->assertEquals('/path/to/manifest.json', $versionStrategy->getArgument(0));
}
public function testAssetsDefaultVersionStrategyAsService()

View File

@ -1,6 +1,11 @@
CHANGELOG
=========
3.3.0
-----
* Added `JsonManifestVersionStrategy` as a way to read final,
versioned paths from a JSON manifest file.
2.7.0
-----

View File

@ -0,0 +1,63 @@
<?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\VersionStrategy;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Asset\VersionStrategy\JsonManifestVersionStrategy;
class JsonManifestVersionStrategyTest extends TestCase
{
public function testGetVersion()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertEquals('main.123abc.js', $strategy->getVersion('main.js'));
}
public function testApplyVersion()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertEquals('css/styles.555def.css', $strategy->getVersion('css/styles.css'));
}
public function testApplyVersionWhenKeyDoesNotExistInManifest()
{
$strategy = $this->createStrategy('manifest-valid.json');
$this->assertEquals('css/other.css', $strategy->getVersion('css/other.css'));
}
/**
* @expectedException \RuntimeException
*/
public function testMissingManifestFileThrowsException()
{
$strategy = $this->createStrategy('non-existent-file.json');
$strategy->getVersion('main.js');
}
/**
* @expectedException \RuntimeException
* @expectedExceptionMessage Error parsing JSON
*/
public function testManifestFileWithBadJSONThrowsException()
{
$strategy = $this->createStrategy('manifest-invalid.json');
$strategy->getVersion('main.js');
}
private function createStrategy($manifestFilename)
{
return new JsonManifestVersionStrategy(__DIR__.'/../fixtures/'.$manifestFilename);
}
}

View File

@ -0,0 +1,4 @@
{
"main.js": main.123abc.js",
"css/styles.css": "css/styles.555def.css"
}

View File

@ -0,0 +1,4 @@
{
"main.js": "main.123abc.js",
"css/styles.css": "css/styles.555def.css"
}

View File

@ -0,0 +1,68 @@
<?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;
/**
* Reads the versioned path of an asset from a JSON manifest file.
*
* For example, the manifest file might look like this:
* {
* "main.js": "main.abc123.js",
* "css/styles.css": "css/styles.555abc.css"
* }
*
* You could then ask for the version of "main.js" or "css/styles.css".
*/
class JsonManifestVersionStrategy implements VersionStrategyInterface
{
private $manifestPath;
private $manifestData;
/**
* @param string $manifestPath Absolute path to the manifest file
*/
public function __construct($manifestPath)
{
$this->manifestPath = $manifestPath;
}
/**
* With a manifest, we don't really know or care about what
* the version is. Instead, this returns the path to the
* versioned file.
*/
public function getVersion($path)
{
return $this->applyVersion($path);
}
public function applyVersion($path)
{
return $this->getManifestPath($path) ?: $path;
}
private function getManifestPath($path)
{
if (null === $this->manifestData) {
if (!file_exists($this->manifestPath)) {
throw new \RuntimeException(sprintf('Asset manifest file "%s" does not exist.', $this->manifestPath));
}
$this->manifestData = json_decode(file_get_contents($this->manifestPath), true);
if (0 < json_last_error()) {
throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s" - %s', $this->manifestPath, json_last_error_msg()));
}
}
return isset($this->manifestData[$path]) ? $this->manifestData[$path] : null;
}
}