diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index d9be5d61fcd98..132904c303f1d 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1353,13 +1353,17 @@ private function registerAssetMapperConfiguration(array $config, ContainerBuilde
->setArgument(0, $paths)
->setArgument(2, $excludedPathPatterns);
- $publicDirName = $this->getPublicDirectoryName($container);
$container->getDefinition('asset_mapper.public_assets_path_resolver')
- ->setArgument(1, $config['public_prefix'])
- ->setArgument(2, $publicDirName);
+ ->setArgument(0, $config['public_prefix']);
- $container->getDefinition('asset_mapper.command.compile')
- ->setArgument(5, $publicDirName);
+ $publicDirectory = $this->getPublicDirectory($container);
+ $publicAssetsDirectory = rtrim($publicDirectory.'/'.ltrim($config['public_prefix'], '/'), '/');
+ $container->getDefinition('asset_mapper.local_public_assets_filesystem')
+ ->setArgument(0, $publicDirectory)
+ ;
+
+ $container->getDefinition('asset_mapper.compiled_asset_mapper_config_reader')
+ ->setArgument(0, $publicAssetsDirectory);
if (!$config['server']) {
$container->removeDefinition('asset_mapper.dev_server_subscriber');
@@ -3163,11 +3167,12 @@ private function writeConfigEnabled(string $path, bool $value, array &$config):
$config['enabled'] = $value;
}
- private function getPublicDirectoryName(ContainerBuilder $container): string
+ private function getPublicDirectory(ContainerBuilder $container): string
{
- $defaultPublicDir = 'public';
+ $projectDir = $container->getParameter('kernel.project_dir');
+ $defaultPublicDir = $projectDir.'/public';
- $composerFilePath = $container->getParameter('kernel.project_dir').'/composer.json';
+ $composerFilePath = $projectDir.'/composer.json';
if (!file_exists($composerFilePath)) {
return $defaultPublicDir;
@@ -3176,6 +3181,6 @@ private function getPublicDirectoryName(ContainerBuilder $container): string
$container->addResource(new FileResource($composerFilePath));
$composerConfig = json_decode(file_get_contents($composerFilePath), true);
- return $composerConfig['extra']['public-dir'] ?? $defaultPublicDir;
+ return isset($composerConfig['extra']['public-dir']) ? $projectDir.'/'.$composerConfig['extra']['public-dir'] : $defaultPublicDir;
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php
index 046bd8c6c5fde..9139a6c898fc9 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/asset_mapper.php
@@ -24,6 +24,7 @@
use Symfony\Component\AssetMapper\Command\ImportMapRemoveCommand;
use Symfony\Component\AssetMapper\Command\ImportMapRequireCommand;
use Symfony\Component\AssetMapper\Command\ImportMapUpdateCommand;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
use Symfony\Component\AssetMapper\Compiler\CssAssetUrlCompiler;
use Symfony\Component\AssetMapper\Compiler\JavaScriptImportPathCompiler;
use Symfony\Component\AssetMapper\Compiler\SourceMappingUrlsCompiler;
@@ -40,6 +41,7 @@
use Symfony\Component\AssetMapper\ImportMap\RemotePackageStorage;
use Symfony\Component\AssetMapper\ImportMap\Resolver\JsDelivrEsmResolver;
use Symfony\Component\AssetMapper\MapperAwareAssetPackage;
+use Symfony\Component\AssetMapper\Path\LocalPublicAssetsFilesystem;
use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolver;
return static function (ContainerConfigurator $container) {
@@ -48,7 +50,7 @@
->args([
service('asset_mapper.repository'),
service('asset_mapper.mapped_asset_factory'),
- service('asset_mapper.public_assets_path_resolver'),
+ service('asset_mapper.compiled_asset_mapper_config_reader'),
])
->alias(AssetMapperInterface::class, 'asset_mapper')
@@ -76,9 +78,17 @@
->set('asset_mapper.public_assets_path_resolver', PublicAssetsPathResolver::class)
->args([
- param('kernel.project_dir'),
abstract_arg('asset public prefix'),
- abstract_arg('public directory name'),
+ ])
+
+ ->set('asset_mapper.local_public_assets_filesystem', LocalPublicAssetsFilesystem::class)
+ ->args([
+ abstract_arg('public directory'),
+ ])
+
+ ->set('asset_mapper.compiled_asset_mapper_config_reader', CompiledAssetMapperConfigReader::class)
+ ->args([
+ abstract_arg('public assets directory'),
])
->set('asset_mapper.asset_package', MapperAwareAssetPackage::class)
@@ -100,12 +110,11 @@
->set('asset_mapper.command.compile', AssetMapperCompileCommand::class)
->args([
- service('asset_mapper.public_assets_path_resolver'),
+ service('asset_mapper.compiled_asset_mapper_config_reader'),
service('asset_mapper'),
service('asset_mapper.importmap.generator'),
- service('filesystem'),
+ service('asset_mapper.local_public_assets_filesystem'),
param('kernel.project_dir'),
- abstract_arg('public directory name'),
param('kernel.debug'),
service('event_dispatcher')->nullOnInvalid(),
])
@@ -163,7 +172,7 @@
->set('asset_mapper.importmap.generator', ImportMapGenerator::class)
->args([
service('asset_mapper'),
- service('asset_mapper.public_assets_path_resolver'),
+ service('asset_mapper.compiled_asset_mapper_config_reader'),
service('asset_mapper.importmap.config_reader'),
])
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php
index 23d9ecfef3ad3..822409f706bc3 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/XmlFrameworkExtensionTest.php
@@ -79,7 +79,7 @@ public function testAssetMapper()
$container = $this->createContainerFromFile('asset_mapper');
$definition = $container->getDefinition('asset_mapper.public_assets_path_resolver');
- $this->assertSame('/assets_path/', $definition->getArgument(1));
+ $this->assertSame('/assets_path/', $definition->getArgument(0));
$definition = $container->getDefinition('asset_mapper.dev_server_subscriber');
$this->assertSame(['zip' => 'application/zip'], $definition->getArgument(2));
diff --git a/src/Symfony/Component/AssetMapper/AssetMapper.php b/src/Symfony/Component/AssetMapper/AssetMapper.php
index fc681cb4bf73e..4afcf6336368b 100644
--- a/src/Symfony/Component/AssetMapper/AssetMapper.php
+++ b/src/Symfony/Component/AssetMapper/AssetMapper.php
@@ -12,7 +12,6 @@
namespace Symfony\Component\AssetMapper;
use Symfony\Component\AssetMapper\Factory\MappedAssetFactoryInterface;
-use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
/**
* Finds and returns assets in the pipeline.
@@ -28,7 +27,7 @@ class AssetMapper implements AssetMapperInterface
public function __construct(
private readonly AssetMapperRepository $mapperRepository,
private readonly MappedAssetFactoryInterface $mappedAssetFactory,
- private readonly PublicAssetsPathResolverInterface $assetsPathResolver,
+ private readonly CompiledAssetMapperConfigReader $compiledConfigReader,
) {
}
@@ -78,12 +77,10 @@ public function getPublicPath(string $logicalPath): ?string
private function loadManifest(): array
{
if (null === $this->manifestData) {
- $path = $this->assetsPathResolver->getPublicFilesystemPath().'/'.self::MANIFEST_FILE_NAME;
-
- if (!is_file($path)) {
+ if (!$this->compiledConfigReader->configExists(self::MANIFEST_FILE_NAME)) {
$this->manifestData = [];
} else {
- $this->manifestData = json_decode(file_get_contents($path), true);
+ $this->manifestData = $this->compiledConfigReader->loadConfig(self::MANIFEST_FILE_NAME);
}
}
diff --git a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php
index cc392a6b9960a..7bb8accd74abf 100644
--- a/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php
+++ b/src/Symfony/Component/AssetMapper/Command/AssetMapperCompileCommand.php
@@ -13,16 +13,15 @@
use Symfony\Component\AssetMapper\AssetMapper;
use Symfony\Component\AssetMapper\AssetMapperInterface;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
use Symfony\Component\AssetMapper\Event\PreAssetsCompileEvent;
use Symfony\Component\AssetMapper\ImportMap\ImportMapGenerator;
-use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
+use Symfony\Component\AssetMapper\Path\PublicAssetsFilesystemInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
-use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
-use Symfony\Component\Filesystem\Filesystem;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
@@ -36,12 +35,11 @@
final class AssetMapperCompileCommand extends Command
{
public function __construct(
- private readonly PublicAssetsPathResolverInterface $publicAssetsPathResolver,
+ private readonly CompiledAssetMapperConfigReader $compiledConfigReader,
private readonly AssetMapperInterface $assetMapper,
private readonly ImportMapGenerator $importMapGenerator,
- private readonly Filesystem $filesystem,
+ private readonly PublicAssetsFilesystemInterface $assetsFilesystem,
private readonly string $projectDir,
- private readonly string $publicDirName,
private readonly bool $isDebug,
private readonly ?EventDispatcherInterface $eventDispatcher = null,
) {
@@ -51,7 +49,6 @@ public function __construct(
protected function configure(): void
{
$this
- ->addOption('clean', null, null, 'Whether to clean the public directory before compiling assets')
->setHelp(<<<'EOT'
The %command.name% command compiles and dumps all the assets in
the asset mapper into the final public directory (usually public/assets).
@@ -64,61 +61,36 @@ protected function configure(): void
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
- $publicDir = $this->projectDir.'/'.$this->publicDirName;
- if (!is_dir($publicDir)) {
- throw new InvalidArgumentException(sprintf('The public directory "%s" does not exist.', $publicDir));
- }
-
- $outputDir = $this->publicAssetsPathResolver->getPublicFilesystemPath();
- if ($input->getOption('clean')) {
- $io->comment(sprintf('Cleaning %s', $outputDir));
- $this->filesystem->remove($outputDir);
- $this->filesystem->mkdir($outputDir);
- }
-
- // set up the file paths
- $files = [];
- $manifestPath = $outputDir.'/'.AssetMapper::MANIFEST_FILE_NAME;
- $files[] = $manifestPath;
- $importMapPath = $outputDir.'/'.ImportMapGenerator::IMPORT_MAP_CACHE_FILENAME;
- $files[] = $importMapPath;
+ $this->eventDispatcher?->dispatch(new PreAssetsCompileEvent($output));
- $entrypointFilePaths = [];
+ // remove existing config files
+ $this->compiledConfigReader->removeConfig(AssetMapper::MANIFEST_FILE_NAME);
+ $this->compiledConfigReader->removeConfig(ImportMapGenerator::IMPORT_MAP_CACHE_FILENAME);
+ $entrypointFiles = [];
foreach ($this->importMapGenerator->getEntrypointNames() as $entrypointName) {
- $dumpedEntrypointPath = $outputDir.'/'.sprintf(ImportMapGenerator::ENTRYPOINT_CACHE_FILENAME_PATTERN, $entrypointName);
- $files[] = $dumpedEntrypointPath;
- $entrypointFilePaths[$entrypointName] = $dumpedEntrypointPath;
+ $path = sprintf(ImportMapGenerator::ENTRYPOINT_CACHE_FILENAME_PATTERN, $entrypointName);
+ $this->compiledConfigReader->removeConfig($path);
+ $entrypointFiles[$entrypointName] = $path;
}
- // remove existing files
- foreach ($files as $file) {
- if (is_file($file)) {
- $this->filesystem->remove($file);
- }
- }
-
- $this->eventDispatcher?->dispatch(new PreAssetsCompileEvent($outputDir, $output));
-
- // dump new files
- $manifest = $this->createManifestAndWriteFiles($io, $publicDir);
- $this->filesystem->dumpFile($manifestPath, json_encode($manifest, \JSON_PRETTY_PRINT));
+ $manifest = $this->createManifestAndWriteFiles($io);
+ $manifestPath = $this->compiledConfigReader->saveConfig(AssetMapper::MANIFEST_FILE_NAME, $manifest);
$io->comment(sprintf('Manifest written to %s', $this->shortenPath($manifestPath)));
- $this->filesystem->dumpFile($importMapPath, json_encode($this->importMapGenerator->getRawImportMapData(), \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG));
+ $importMapPath = $this->compiledConfigReader->saveConfig(ImportMapGenerator::IMPORT_MAP_CACHE_FILENAME, $this->importMapGenerator->getRawImportMapData());
$io->comment(sprintf('Import map data written to %s.', $this->shortenPath($importMapPath)));
- $entrypointNames = $this->importMapGenerator->getEntrypointNames();
- foreach ($entrypointFilePaths as $entrypointName => $path) {
- $this->filesystem->dumpFile($path, json_encode($this->importMapGenerator->findEagerEntrypointImports($entrypointName), \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_HEX_TAG));
+ foreach ($entrypointFiles as $entrypointName => $path) {
+ $this->compiledConfigReader->saveConfig($path, $this->importMapGenerator->findEagerEntrypointImports($entrypointName));
}
- $styledEntrypointNames = array_map(fn (string $entrypointName) => sprintf('%s>', $entrypointName), $entrypointNames);
- $io->comment(sprintf('Entrypoint metadata written for %d> entrypoints (%s).', \count($entrypointNames), implode(', ', $styledEntrypointNames)));
+ $styledEntrypointNames = array_map(fn (string $entrypointName) => sprintf('%s>', $entrypointName), array_keys($entrypointFiles));
+ $io->comment(sprintf('Entrypoint metadata written for %d> entrypoints (%s).', \count($entrypointFiles), implode(', ', $styledEntrypointNames)));
if ($this->isDebug) {
$io->warning(sprintf(
- 'You are compiling assets in development. Symfony will not serve any changed assets until you delete the "%s" directory.',
- $this->shortenPath($outputDir)
+ 'You are compiling assets in development. Symfony will not serve any changed assets until you delete the files in the "%s" directory.',
+ $this->shortenPath(\dirname($manifestPath))
));
}
@@ -130,20 +102,18 @@ private function shortenPath(string $path): string
return str_replace($this->projectDir.'/', '', $path);
}
- private function createManifestAndWriteFiles(SymfonyStyle $io, string $publicDir): array
+ private function createManifestAndWriteFiles(SymfonyStyle $io): array
{
$allAssets = $this->assetMapper->allAssets();
- $io->comment(sprintf('Compiling assets to %s%s', $publicDir, $this->publicAssetsPathResolver->resolvePublicPath('')));
+ $io->comment(sprintf('Compiling and writing asset files to %s', $this->shortenPath($this->assetsFilesystem->getDestinationPath())));
$manifest = [];
foreach ($allAssets as $asset) {
- // $asset->getPublicPath() will start with a "/"
- $targetPath = $publicDir.$asset->publicPath;
if (null !== $asset->content) {
// The original content has been modified by the AssetMapperCompiler
- $this->filesystem->dumpFile($targetPath, $asset->content);
+ $this->assetsFilesystem->write($asset->publicPath, $asset->content);
} else {
- $this->filesystem->copy($asset->sourcePath, $targetPath, true);
+ $this->assetsFilesystem->copy($asset->sourcePath, $asset->publicPath);
}
$manifest[$asset->logicalPath] = $asset->publicPath;
diff --git a/src/Symfony/Component/AssetMapper/CompiledAssetMapperConfigReader.php b/src/Symfony/Component/AssetMapper/CompiledAssetMapperConfigReader.php
new file mode 100644
index 0000000000000..daa656805fe9d
--- /dev/null
+++ b/src/Symfony/Component/AssetMapper/CompiledAssetMapperConfigReader.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\AssetMapper;
+
+use Symfony\Component\Filesystem\Path;
+
+/**
+ * Reads and writes compiled configuration files for asset mapper.
+ */
+class CompiledAssetMapperConfigReader
+{
+ public function __construct(private readonly string $directory)
+ {
+ }
+
+ public function configExists(string $filename): bool
+ {
+ return is_file(Path::join($this->directory, $filename));
+ }
+
+ public function loadConfig(string $filename): array
+ {
+ return json_decode(file_get_contents(Path::join($this->directory, $filename)), true, 512, \JSON_THROW_ON_ERROR);
+ }
+
+ public function saveConfig(string $filename, array $data): string
+ {
+ $path = Path::join($this->directory, $filename);
+ @mkdir(\dirname($path), 0777, true);
+ file_put_contents($path, json_encode($data, \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR));
+
+ return $path;
+ }
+
+ public function removeConfig(string $filename): void
+ {
+ $path = Path::join($this->directory, $filename);
+
+ if (is_file($path)) {
+ unlink($path);
+ }
+ }
+}
diff --git a/src/Symfony/Component/AssetMapper/Event/PreAssetsCompileEvent.php b/src/Symfony/Component/AssetMapper/Event/PreAssetsCompileEvent.php
index a55a2e8e6a77a..972e78ae9802e 100644
--- a/src/Symfony/Component/AssetMapper/Event/PreAssetsCompileEvent.php
+++ b/src/Symfony/Component/AssetMapper/Event/PreAssetsCompileEvent.php
@@ -21,20 +21,13 @@
*/
class PreAssetsCompileEvent extends Event
{
- private string $outputDir;
private OutputInterface $output;
- public function __construct(string $outputDir, OutputInterface $output)
+ public function __construct(OutputInterface $output)
{
- $this->outputDir = $outputDir;
$this->output = $output;
}
- public function getOutputDir(): string
- {
- return $this->outputDir;
- }
-
public function getOutput(): OutputInterface
{
return $this->output;
diff --git a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapGenerator.php b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapGenerator.php
index bca5897c03c53..f75593be85e52 100644
--- a/src/Symfony/Component/AssetMapper/ImportMap/ImportMapGenerator.php
+++ b/src/Symfony/Component/AssetMapper/ImportMap/ImportMapGenerator.php
@@ -12,9 +12,9 @@
namespace Symfony\Component\AssetMapper\ImportMap;
use Symfony\Component\AssetMapper\AssetMapperInterface;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
use Symfony\Component\AssetMapper\Exception\LogicException;
use Symfony\Component\AssetMapper\MappedAsset;
-use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
/**
* Provides data needed to write the importmap & preloads.
@@ -26,7 +26,7 @@ class ImportMapGenerator
public function __construct(
private readonly AssetMapperInterface $assetMapper,
- private readonly PublicAssetsPathResolverInterface $assetsPathResolver,
+ private readonly CompiledAssetMapperConfigReader $compiledConfigReader,
private readonly ImportMapConfigReader $importMapConfigReader,
) {
}
@@ -87,9 +87,8 @@ public function getImportMapData(array $entrypointNames): array
*/
public function getRawImportMapData(): array
{
- $dumpedImportMapPath = $this->assetsPathResolver->getPublicFilesystemPath().'/'.self::IMPORT_MAP_CACHE_FILENAME;
- if (is_file($dumpedImportMapPath)) {
- return json_decode(file_get_contents($dumpedImportMapPath), true, 512, \JSON_THROW_ON_ERROR);
+ if ($this->compiledConfigReader->configExists(self::IMPORT_MAP_CACHE_FILENAME)) {
+ return $this->compiledConfigReader->loadConfig(self::IMPORT_MAP_CACHE_FILENAME);
}
$allEntries = [];
@@ -122,9 +121,8 @@ public function getRawImportMapData(): array
*/
public function findEagerEntrypointImports(string $entryName): array
{
- $dumpedEntrypointPath = $this->assetsPathResolver->getPublicFilesystemPath().'/'.sprintf(self::ENTRYPOINT_CACHE_FILENAME_PATTERN, $entryName);
- if (is_file($dumpedEntrypointPath)) {
- return json_decode(file_get_contents($dumpedEntrypointPath), true, 512, \JSON_THROW_ON_ERROR);
+ if ($this->compiledConfigReader->configExists(sprintf(self::ENTRYPOINT_CACHE_FILENAME_PATTERN, $entryName))) {
+ return $this->compiledConfigReader->loadConfig(sprintf(self::ENTRYPOINT_CACHE_FILENAME_PATTERN, $entryName));
}
$rootImportEntries = $this->importMapConfigReader->getEntries();
@@ -202,13 +200,6 @@ private function addImplicitEntries(ImportMapEntry $entry, array $currentImportE
return $currentImportEntries;
}
- private function findRootImportMapEntry(string $moduleName): ?ImportMapEntry
- {
- $entries = $this->importMapConfigReader->getEntries();
-
- return $entries->has($moduleName) ? $entries->get($moduleName) : null;
- }
-
/**
* Finds the MappedAsset allowing for a "logical path", relative or absolute filesystem path.
*/
diff --git a/src/Symfony/Component/AssetMapper/Path/LocalPublicAssetsFilesystem.php b/src/Symfony/Component/AssetMapper/Path/LocalPublicAssetsFilesystem.php
new file mode 100644
index 0000000000000..c6302515927f7
--- /dev/null
+++ b/src/Symfony/Component/AssetMapper/Path/LocalPublicAssetsFilesystem.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\AssetMapper\Path;
+
+use Symfony\Component\Filesystem\Filesystem;
+
+class LocalPublicAssetsFilesystem implements PublicAssetsFilesystemInterface
+{
+ private Filesystem $filesystem;
+
+ public function __construct(private readonly string $publicDir)
+ {
+ $this->filesystem = new Filesystem();
+ }
+
+ public function write(string $path, string $contents): void
+ {
+ $targetPath = $this->publicDir.'/'.ltrim($path, '/');
+
+ $this->filesystem->dumpFile($targetPath, $contents);
+ }
+
+ public function copy(string $originPath, string $path): void
+ {
+ $targetPath = $this->publicDir.'/'.ltrim($path, '/');
+
+ $this->filesystem->copy($originPath, $targetPath, true);
+ }
+
+ public function getDestinationPath(): string
+ {
+ return $this->publicDir;
+ }
+}
diff --git a/src/Symfony/Component/AssetMapper/Path/PublicAssetsFilesystemInterface.php b/src/Symfony/Component/AssetMapper/Path/PublicAssetsFilesystemInterface.php
new file mode 100644
index 0000000000000..b33c126301215
--- /dev/null
+++ b/src/Symfony/Component/AssetMapper/Path/PublicAssetsFilesystemInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\AssetMapper\Path;
+
+/**
+ * Writes asset files to their public location.
+ */
+interface PublicAssetsFilesystemInterface
+{
+ /**
+ * Write the contents of a file to the public location.
+ */
+ public function write(string $path, string $contents): void;
+
+ /**
+ * Copy a local file to the public location.
+ */
+ public function copy(string $originPath, string $path): void;
+
+ /**
+ * A string representation of the public directory, used for feedback.
+ */
+ public function getDestinationPath(): string;
+}
diff --git a/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolver.php b/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolver.php
index c05c6c5ad3afc..fe839d591a99e 100644
--- a/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolver.php
+++ b/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolver.php
@@ -16,9 +16,7 @@ class PublicAssetsPathResolver implements PublicAssetsPathResolverInterface
private readonly string $publicPrefix;
public function __construct(
- private readonly string $projectRootDir,
string $publicPrefix = '/assets/',
- private readonly string $publicDirName = 'public',
) {
// ensure that the public prefix always ends with a single slash
$this->publicPrefix = rtrim($publicPrefix, '/').'/';
@@ -28,9 +26,4 @@ public function resolvePublicPath(string $logicalPath): string
{
return $this->publicPrefix.ltrim($logicalPath, '/');
}
-
- public function getPublicFilesystemPath(): string
- {
- return rtrim(rtrim($this->projectRootDir, '/').'/'.$this->publicDirName.$this->publicPrefix, '/');
- }
}
diff --git a/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolverInterface.php b/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolverInterface.php
index 802d1ce07ecff..5046fb8d034ba 100644
--- a/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolverInterface.php
+++ b/src/Symfony/Component/AssetMapper/Path/PublicAssetsPathResolverInterface.php
@@ -17,9 +17,4 @@ interface PublicAssetsPathResolverInterface
* The path that should be prefixed on all asset paths to point to the output location.
*/
public function resolvePublicPath(string $logicalPath): string;
-
- /**
- * Returns the filesystem path to where assets are stored when compiled.
- */
- public function getPublicFilesystemPath(): string;
}
diff --git a/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php
index 8829089fb93f6..ef31a1c7fb714 100644
--- a/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/AssetMapperTest.php
@@ -15,9 +15,9 @@
use PHPUnit\Framework\TestCase;
use Symfony\Component\AssetMapper\AssetMapper;
use Symfony\Component\AssetMapper\AssetMapperRepository;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
use Symfony\Component\AssetMapper\Factory\MappedAssetFactoryInterface;
use Symfony\Component\AssetMapper\MappedAsset;
-use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
class AssetMapperTest extends TestCase
{
@@ -90,17 +90,21 @@ private function createAssetMapper(): AssetMapper
{
$dirs = ['dir1' => '', 'dir2' => '', 'dir3' => ''];
$repository = new AssetMapperRepository($dirs, __DIR__.'/Fixtures');
- $pathResolver = $this->createMock(PublicAssetsPathResolverInterface::class);
- $pathResolver->expects($this->any())
- ->method('getPublicFilesystemPath')
- ->willReturn(__DIR__.'/Fixtures/test_public/final-assets');
+ $compiledConfigReader = $this->createMock(CompiledAssetMapperConfigReader::class);
+ $compiledConfigReader->expects($this->any())
+ ->method('configExists')
+ ->with(AssetMapper::MANIFEST_FILE_NAME)
+ ->willReturn(true);
+ $compiledConfigReader->expects($this->any())
+ ->method('loadConfig')
+ ->willReturn(['file4.js' => '/final-assets/file4.checksumfrommanifest.js']);
$this->mappedAssetFactory = $this->createMock(MappedAssetFactoryInterface::class);
return new AssetMapper(
$repository,
$this->mappedAssetFactory,
- $pathResolver,
+ $compiledConfigReader,
);
}
}
diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php
index 595f2a7509873..05283f33df5d7 100644
--- a/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/Command/AssetMapperCompileCommandTest.php
@@ -51,7 +51,7 @@ public function testAssetsAreCompiled()
$this->filesystem->mkdir($targetBuildDir);
file_put_contents($targetBuildDir.'/manifest.json', '{}');
file_put_contents($targetBuildDir.'/importmap.json', '{}');
- file_put_contents($targetBuildDir.'/entrypoint.file6.json', '[]]');
+ file_put_contents($targetBuildDir.'/entrypoint.file6.json', '[]');
$command = $application->find('asset-map:compile');
$tester = new CommandTester($command);
@@ -119,7 +119,6 @@ public function testEventIsDispatched()
$listenerCalled = false;
$dispatcher->addListener(PreAssetsCompileEvent::class, function (PreAssetsCompileEvent $event) use (&$listenerCalled) {
$listenerCalled = true;
- $this->assertSame(realpath(__DIR__.'/../Fixtures').'/public/assets', $event->getOutputDir());
$this->assertInstanceOf(OutputInterface::class, $event->getOutput());
});
diff --git a/src/Symfony/Component/AssetMapper/Tests/CompiledAssetMapperConfigReaderTest.php b/src/Symfony/Component/AssetMapper/Tests/CompiledAssetMapperConfigReaderTest.php
new file mode 100644
index 0000000000000..03ea5fce21f1f
--- /dev/null
+++ b/src/Symfony/Component/AssetMapper/Tests/CompiledAssetMapperConfigReaderTest.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\AssetMapper\Tests;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
+use Symfony\Component\Filesystem\Filesystem;
+
+class CompiledAssetMapperConfigReaderTest extends TestCase
+{
+ private Filesystem $filesystem;
+ private string $writableRoot;
+
+ protected function setUp(): void
+ {
+ $this->filesystem = new Filesystem();
+ $this->writableRoot = __DIR__.'/../Fixtures/importmaps_for_writing';
+ if (!file_exists(__DIR__.'/../Fixtures/importmaps_for_writing')) {
+ $this->filesystem->mkdir($this->writableRoot);
+ }
+ // realpath to help path comparisons in the tests
+ $this->writableRoot = realpath($this->writableRoot);
+ }
+
+ protected function tearDown(): void
+ {
+ $this->filesystem->remove($this->writableRoot);
+ }
+
+ public function testConfigExists()
+ {
+ $reader = new CompiledAssetMapperConfigReader($this->writableRoot);
+ $this->assertFalse($reader->configExists('foo.json'));
+ $this->filesystem->touch($this->writableRoot.'/foo.json');
+ $this->assertTrue($reader->configExists('foo.json'));
+ }
+
+ public function testLoadConfig()
+ {
+ $reader = new CompiledAssetMapperConfigReader($this->writableRoot);
+ $this->filesystem->dumpFile($this->writableRoot.'/foo.json', '{"foo": "bar"}');
+ $this->assertEquals(['foo' => 'bar'], $reader->loadConfig('foo.json'));
+ }
+
+ public function testSaveConfig()
+ {
+ $reader = new CompiledAssetMapperConfigReader($this->writableRoot);
+ $this->assertEquals($this->writableRoot.'/foo.json', $reader->saveConfig('foo.json', ['foo' => 'bar']));
+ $this->assertEquals(['foo' => 'bar'], json_decode(file_get_contents($this->writableRoot.'/foo.json'), true));
+ }
+
+ public function testRemoveConfig()
+ {
+ $reader = new CompiledAssetMapperConfigReader($this->writableRoot);
+ $this->filesystem->touch($this->writableRoot.'/foo.json');
+ $this->assertTrue($reader->configExists('foo.json'));
+ $reader->removeConfig('foo.json');
+ $this->assertFalse($reader->configExists('foo.json'));
+ }
+}
diff --git a/src/Symfony/Component/AssetMapper/Tests/Fixtures/test_public/final-assets/manifest.json b/src/Symfony/Component/AssetMapper/Tests/Fixtures/test_public/final-assets/manifest.json
deleted file mode 100644
index b32c6a99d4bef..0000000000000
--- a/src/Symfony/Component/AssetMapper/Tests/Fixtures/test_public/final-assets/manifest.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "file4.js": "/final-assets/file4.checksumfrommanifest.js"
-}
diff --git a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php
index b8f6e767ae6b0..6c8ab752d286d 100644
--- a/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/ImportMap/ImportMapGeneratorTest.php
@@ -14,6 +14,7 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\AssetMapper\AssetMapperInterface;
+use Symfony\Component\AssetMapper\CompiledAssetMapperConfigReader;
use Symfony\Component\AssetMapper\ImportMap\ImportMapConfigReader;
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntries;
use Symfony\Component\AssetMapper\ImportMap\ImportMapEntry;
@@ -21,13 +22,12 @@
use Symfony\Component\AssetMapper\ImportMap\ImportMapType;
use Symfony\Component\AssetMapper\ImportMap\JavaScriptImport;
use Symfony\Component\AssetMapper\MappedAsset;
-use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface;
use Symfony\Component\Filesystem\Filesystem;
class ImportMapGeneratorTest extends TestCase
{
private AssetMapperInterface&MockObject $assetMapper;
- private PublicAssetsPathResolverInterface&MockObject $pathResolver;
+ private CompiledAssetMapperConfigReader&MockObject $compiledConfigReader;
private ImportMapConfigReader&MockObject $configReader;
private ImportMapGenerator $importMapGenerator;
@@ -565,10 +565,13 @@ public function testGetRawImportDataUsesCacheFile()
'path' => 'https://anyurl.com/stimulus',
],
];
- $this->writeFile('public/assets/importmap.json', json_encode($importmapData));
- $this->pathResolver->expects($this->once())
- ->method('getPublicFilesystemPath')
- ->willReturn(self::$writableRoot.'/public/assets');
+ $this->compiledConfigReader->expects($this->once())
+ ->method('configExists')
+ ->with('importmap.json')
+ ->willReturn(true);
+ $this->compiledConfigReader->expects($this->once())
+ ->method('loadConfig')
+ ->willReturn($importmapData);
$this->assertEquals($importmapData, $manager->getRawImportMapData());
}
@@ -651,17 +654,20 @@ public function testFindEagerEntrypointImportsUsesCacheFile()
'app',
'/assets/foo.js',
];
- $this->writeFile('public/assets/entrypoint.foo.json', json_encode($entrypointData));
- $this->pathResolver->expects($this->once())
- ->method('getPublicFilesystemPath')
- ->willReturn(self::$writableRoot.'/public/assets');
+ $this->compiledConfigReader->expects($this->once())
+ ->method('configExists')
+ ->with('entrypoint.foo.json')
+ ->willReturn(true);
+ $this->compiledConfigReader->expects($this->once())
+ ->method('loadConfig')
+ ->willReturn($entrypointData);
$this->assertEquals($entrypointData, $manager->findEagerEntrypointImports('foo'));
}
private function createImportMapGenerator(): ImportMapGenerator
{
- $this->pathResolver = $this->createMock(PublicAssetsPathResolverInterface::class);
+ $this->compiledConfigReader = $this->createMock(CompiledAssetMapperConfigReader::class);
$this->assetMapper = $this->createMock(AssetMapperInterface::class);
$this->configReader = $this->createMock(ImportMapConfigReader::class);
@@ -676,7 +682,7 @@ private function createImportMapGenerator(): ImportMapGenerator
return $this->importMapGenerator = new ImportMapGenerator(
$this->assetMapper,
- $this->pathResolver,
+ $this->compiledConfigReader,
$this->configReader,
);
}
@@ -689,15 +695,6 @@ private function mockImportMap(array $importMapEntries): void
;
}
- private function writeFile(string $filename, string $content): void
- {
- $path = \dirname(self::$writableRoot.'/'.$filename);
- if (!is_dir($path)) {
- mkdir($path, 0777, true);
- }
- file_put_contents(self::$writableRoot.'/'.$filename, $content);
- }
-
private static function createLocalEntry(string $importName, string $path, ImportMapType $type = ImportMapType::JS, bool $isEntrypoint = false): ImportMapEntry
{
return ImportMapEntry::createLocal($importName, $type, path: $path, isEntrypoint: $isEntrypoint);
diff --git a/src/Symfony/Component/AssetMapper/Tests/Path/LocalPublicAssetsFilesystemTest.php b/src/Symfony/Component/AssetMapper/Tests/Path/LocalPublicAssetsFilesystemTest.php
new file mode 100644
index 0000000000000..4363ccbf577a8
--- /dev/null
+++ b/src/Symfony/Component/AssetMapper/Tests/Path/LocalPublicAssetsFilesystemTest.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\AssetMapper\Tests\Path;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\AssetMapper\Path\LocalPublicAssetsFilesystem;
+use Symfony\Component\Filesystem\Filesystem;
+
+class LocalPublicAssetsFilesystemTest extends TestCase
+{
+ private Filesystem $filesystem;
+ private static string $writableRoot = __DIR__.'/../Fixtures/importmaps_for_writing';
+
+ protected function setUp(): void
+ {
+ $this->filesystem = new Filesystem();
+ if (!file_exists(__DIR__.'/../Fixtures/importmaps_for_writing')) {
+ $this->filesystem->mkdir(self::$writableRoot);
+ }
+ }
+
+ protected function tearDown(): void
+ {
+ $this->filesystem->remove(self::$writableRoot);
+ }
+
+ public function testWrite()
+ {
+ $filesystem = new LocalPublicAssetsFilesystem(self::$writableRoot);
+ $filesystem->write('foo/bar.js', 'foobar');
+ $this->assertFileExists(self::$writableRoot.'/foo/bar.js');
+ $this->assertSame('foobar', file_get_contents(self::$writableRoot.'/foo/bar.js'));
+
+ // with a directory
+ $filesystem->write('foo/baz/bar.js', 'foobar');
+ $this->assertFileExists(self::$writableRoot.'/foo/baz/bar.js');
+ }
+
+ public function testCopy()
+ {
+ $filesystem = new LocalPublicAssetsFilesystem(self::$writableRoot);
+ $filesystem->copy(__DIR__.'/../Fixtures/importmaps/assets/pizza/index.js', 'foo/bar.js');
+ $this->assertFileExists(self::$writableRoot.'/foo/bar.js');
+ $this->assertSame("console.log('pizza/index.js');", trim(file_get_contents(self::$writableRoot.'/foo/bar.js')));
+ }
+}
diff --git a/src/Symfony/Component/AssetMapper/Tests/Path/PublicAssetsPathResolverTest.php b/src/Symfony/Component/AssetMapper/Tests/Path/PublicAssetsPathResolverTest.php
index af2fa7f74f109..2144b98919527 100644
--- a/src/Symfony/Component/AssetMapper/Tests/Path/PublicAssetsPathResolverTest.php
+++ b/src/Symfony/Component/AssetMapper/Tests/Path/PublicAssetsPathResolverTest.php
@@ -19,38 +19,17 @@ class PublicAssetsPathResolverTest extends TestCase
public function testResolvePublicPath()
{
$resolver = new PublicAssetsPathResolver(
- '/projectRootDir/',
'/assets-prefix/',
- 'publicDirName',
);
$this->assertSame('/assets-prefix/', $resolver->resolvePublicPath(''));
$this->assertSame('/assets-prefix/foo/bar', $resolver->resolvePublicPath('/foo/bar'));
$this->assertSame('/assets-prefix/foo/bar', $resolver->resolvePublicPath('foo/bar'));
$resolver = new PublicAssetsPathResolver(
- '/projectRootDir/',
'/assets-prefix', // The trailing slash should be added automatically
- 'publicDirName',
);
$this->assertSame('/assets-prefix/', $resolver->resolvePublicPath(''));
$this->assertSame('/assets-prefix/foo/bar', $resolver->resolvePublicPath('/foo/bar'));
$this->assertSame('/assets-prefix/foo/bar', $resolver->resolvePublicPath('foo/bar'));
}
-
- public function testGetPublicFilesystemPath()
- {
- $resolver = new PublicAssetsPathResolver(
- '/path/to/projectRootDir/',
- '/assets-prefix',
- 'publicDirName',
- );
- $this->assertSame('/path/to/projectRootDir/publicDirName/assets-prefix', $resolver->getPublicFilesystemPath());
-
- $resolver = new PublicAssetsPathResolver(
- '/path/to/projectRootDir',
- '/assets-prefix/',
- 'publicDirName',
- );
- $this->assertSame('/path/to/projectRootDir/publicDirName/assets-prefix', $resolver->getPublicFilesystemPath());
- }
}