From 4ba12a80e563fcfa022e965a1c760c455dce8afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20TAMARELLE?= Date: Sun, 15 Dec 2019 22:23:37 +0100 Subject: [PATCH] =?UTF-8?q?[Asset]=C2=A0Allows=20to=20download=20json=20ma?= =?UTF-8?q?nifest=20from=20a=20remote=20url?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle URL in json_manifest_path Download the manifest using the HttpClient --- .../FrameworkExtension.php | 7 +- .../Resources/config/assets.xml | 5 ++ .../Fixtures/php/assets.php | 3 + .../Fixtures/xml/assets.xml | 1 + .../Fixtures/yml/assets.yml | 2 + .../FrameworkExtensionTest.php | 7 +- .../Bundle/FrameworkBundle/composer.json | 4 +- src/Symfony/Component/Asset/CHANGELOG.md | 5 ++ .../RemoteJsonManifestVersionStrategyTest.php | 73 +++++++++++++++++++ .../RemoteJsonManifestVersionStrategy.php | 62 ++++++++++++++++ src/Symfony/Component/Asset/composer.json | 1 + 11 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php create mode 100644 src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4a23d52314..8bafc23657 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1049,7 +1049,12 @@ class FrameworkExtension extends Extension } if (null !== $jsonManifestPath) { - $def = new ChildDefinition('assets.json_manifest_version_strategy'); + $definitionName = 'assets.json_manifest_version_strategy'; + if (0 === strpos(parse_url($jsonManifestPath, PHP_URL_SCHEME), 'http')) { + $definitionName = 'assets.remote_json_manifest_version_strategy'; + } + + $def = new ChildDefinition($definitionName); $def->replaceArgument(0, $jsonManifestPath); $container->setDefinition('assets._version_'.$name, $def); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml index 4aaa702df5..eebb28161d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml @@ -50,5 +50,10 @@ + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php index c05c6fe3a1..ef2fd77013 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/assets.php @@ -27,6 +27,9 @@ $container->loadFromExtension('framework', [ 'json_manifest_strategy' => [ 'json_manifest_path' => '/path/to/manifest.json', ], + 'remote_manifest' => [ + 'json_manifest_path' => 'https://cdn.example.com/manifest.json', + ], ], ], ]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml index 7ae57afaab..24bfdc6456 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/assets.xml @@ -22,6 +22,7 @@ https://bar_version_strategy.example.com + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml index a1679e389d..4a4a57bc43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/assets.yml @@ -19,3 +19,5 @@ framework: version_strategy: assets.custom_version_strategy json_manifest_strategy: json_manifest_path: '/path/to/manifest.json' + remote_manifest: + json_manifest_path: 'https://cdn.example.com/manifest.json' diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 30ebae8852..f9ebac230f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -535,7 +535,7 @@ abstract class FrameworkExtensionTest extends TestCase // packages $packages = $packages->getArgument(1); - $this->assertCount(6, $packages); + $this->assertCount(7, $packages); $package = $container->getDefinition((string) $packages['images_path']); $this->assertPathPackage($container, $package, '/foo', 'SomeVersionScheme', '%%s?version=%%s'); @@ -556,6 +556,11 @@ abstract class FrameworkExtensionTest extends TestCase $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)); + + $package = $container->getDefinition($packages['remote_manifest']); + $versionStrategy = $container->getDefinition($package->getArgument(1)); + $this->assertSame('assets.remote_json_manifest_version_strategy', $versionStrategy->getParent()); + $this->assertSame('https://cdn.example.com/manifest.json', $versionStrategy->getArgument(0)); } public function testAssetsDefaultVersionStrategyAsService() diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index eddb25a372..f27225600c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -32,7 +32,7 @@ "require-dev": { "doctrine/annotations": "~1.7", "doctrine/cache": "~1.0", - "symfony/asset": "^4.4|^5.0", + "symfony/asset": "^5.1", "symfony/browser-kit": "^4.4|^5.0", "symfony/console": "^4.4|^5.0", "symfony/css-selector": "^4.4|^5.0", @@ -68,7 +68,7 @@ "phpdocumentor/reflection-docblock": "<3.0", "phpdocumentor/type-resolver": "<0.2.1", "phpunit/phpunit": "<5.4.3", - "symfony/asset": "<4.4", + "symfony/asset": "<5.1", "symfony/browser-kit": "<4.4", "symfony/console": "<4.4", "symfony/dotenv": "<5.1", diff --git a/src/Symfony/Component/Asset/CHANGELOG.md b/src/Symfony/Component/Asset/CHANGELOG.md index 1c473dd1e5..9df5fc14d0 100644 --- a/src/Symfony/Component/Asset/CHANGELOG.md +++ b/src/Symfony/Component/Asset/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.1.0 +----- + + * added `RemoteJsonManifestVersionStrategy` to download manifest over HTTP. + 4.2.0 ----- diff --git a/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php b/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php new file mode 100644 index 0000000000..64f7e6c169 --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/VersionStrategy/RemoteJsonManifestVersionStrategyTest.php @@ -0,0 +1,73 @@ + + * + * 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\RemoteJsonManifestVersionStrategy; +use Symfony\Component\HttpClient\Exception\JsonException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; + +class RemoteJsonManifestVersionStrategyTest extends TestCase +{ + public function testGetVersion() + { + $strategy = $this->createStrategy('https://cdn.example.com/manifest-valid.json'); + + $this->assertSame('main.123abc.js', $strategy->getVersion('main.js')); + } + + public function testApplyVersion() + { + $strategy = $this->createStrategy('https://cdn.example.com/manifest-valid.json'); + + $this->assertSame('css/styles.555def.css', $strategy->getVersion('css/styles.css')); + } + + public function testApplyVersionWhenKeyDoesNotExistInManifest() + { + $strategy = $this->createStrategy('https://cdn.example.com/manifest-valid.json'); + + $this->assertSame('css/other.css', $strategy->getVersion('css/other.css')); + } + + public function testMissingManifestFileThrowsException() + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('HTTP 404 returned for "https://cdn.example.com/non-existent-file.json"'); + $strategy = $this->createStrategy('https://cdn.example.com/non-existent-file.json'); + $strategy->getVersion('main.js'); + } + + public function testManifestFileWithBadJSONThrowsException() + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Syntax error'); + $strategy = $this->createStrategy('https://cdn.example.com/manifest-invalid.json'); + $strategy->getVersion('main.js'); + } + + private function createStrategy($manifestUrl) + { + $httpClient = new MockHttpClient(function ($method, $url, $options) { + $filename = __DIR__.'/../fixtures/'.basename($url); + + if (file_exists($filename)) { + return new MockResponse(file_get_contents($filename), ['http_headers' => ['content-type' => 'application/json']]); + } + + return new MockResponse('{}', ['http_code' => 404]); + }); + + return new RemoteJsonManifestVersionStrategy($manifestUrl, $httpClient); + } +} diff --git a/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php new file mode 100644 index 0000000000..db45b3b7ec --- /dev/null +++ b/src/Symfony/Component/Asset/VersionStrategy/RemoteJsonManifestVersionStrategy.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\VersionStrategy; + +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * Reads the versioned path of an asset from a remote 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 RemoteJsonManifestVersionStrategy implements VersionStrategyInterface +{ + private $manifestData; + private $manifestUrl; + private $httpClient; + + /** + * @param string $manifestUrl Absolute URL to the manifest file + */ + public function __construct(string $manifestUrl, HttpClientInterface $httpClient) + { + $this->manifestUrl = $manifestUrl; + $this->httpClient = $httpClient; + } + + /** + * 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(string $path) + { + return $this->applyVersion($path); + } + + public function applyVersion(string $path) + { + if (null === $this->manifestData) { + $this->manifestData = $this->httpClient->request('GET', $this->manifestUrl, [ + 'headers' => ['accept' => 'application/json'], + ])->toArray(); + } + + return $this->manifestData[$path] ?? $path; + } +} diff --git a/src/Symfony/Component/Asset/composer.json b/src/Symfony/Component/Asset/composer.json index 0a82b5dd0c..79fac11262 100644 --- a/src/Symfony/Component/Asset/composer.json +++ b/src/Symfony/Component/Asset/composer.json @@ -22,6 +22,7 @@ "symfony/http-foundation": "" }, "require-dev": { + "symfony/http-client": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0" },