diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php index 5b627ded6a..647dc0fd58 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/AssetsInstallCommand.php @@ -11,20 +11,34 @@ namespace Symfony\Bundle\FrameworkBundle\Command; +use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; /** * Command that places bundle web assets into a given directory. * * @author Fabien Potencier + * @author Gábor Egyed */ class AssetsInstallCommand extends ContainerAwareCommand { + const METHOD_COPY = 'copy'; + const METHOD_ABSOLUTE_SYMLINK = 'absolute symlink'; + const METHOD_RELATIVE_SYMLINK = 'relative symlink'; + + /** + * @var Filesystem + */ + private $filesystem; + /** * {@inheritdoc} */ @@ -63,8 +77,6 @@ EOT /** * {@inheritdoc} - * - * @throws \InvalidArgumentException When the target directory does not exist or symlink cannot be used */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -74,77 +86,164 @@ EOT throw new \InvalidArgumentException(sprintf('The target directory "%s" does not exist.', $input->getArgument('target'))); } - $filesystem = $this->getContainer()->get('filesystem'); + $this->filesystem = $this->getContainer()->get('filesystem'); // Create the bundles directory otherwise symlink will fail. $bundlesDir = $targetArg.'/bundles/'; - $filesystem->mkdir($bundlesDir, 0777); + $this->filesystem->mkdir($bundlesDir, 0777); - // relative implies symlink - $symlink = $input->getOption('symlink') || $input->getOption('relative'); + $io = new SymfonyStyle($input, $output); + $io->newLine(); - if ($symlink) { - $output->writeln('Trying to install assets as symbolic links.'); + if ($input->getOption('relative')) { + $expectedMethod = self::METHOD_RELATIVE_SYMLINK; + $io->text('Trying to install assets as relative symbolic links.'); + } elseif ($input->getOption('symlink')) { + $expectedMethod = self::METHOD_ABSOLUTE_SYMLINK; + $io->text('Trying to install assets as absolute symbolic links.'); } else { - $output->writeln('Installing assets as hard copies.'); + $expectedMethod = self::METHOD_COPY; + $io->text('Installing assets as hard copies.'); } + $io->newLine(); + + $rows = array(); + $copyUsed = false; + $exitCode = 0; + /** @var BundleInterface $bundle */ foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) { - if (is_dir($originDir = $bundle->getPath().'/Resources/public')) { - $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); - - $output->writeln(sprintf('Installing assets for %s into %s', $bundle->getNamespace(), $targetDir)); - - $filesystem->remove($targetDir); - - if ($symlink) { - if ($input->getOption('relative')) { - $relativeOriginDir = $filesystem->makePathRelative($originDir, realpath($bundlesDir)); - } else { - $relativeOriginDir = $originDir; - } - - try { - $filesystem->symlink($relativeOriginDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('The assets were installed using symbolic links.'); - } catch (IOException $e) { - if (!$input->getOption('relative')) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - - // try again without the relative option - try { - $filesystem->symlink($originDir, $targetDir); - if (!file_exists($targetDir)) { - throw new IOException('Symbolic link is broken'); - } - $output->writeln('It looks like your system doesn\'t support relative symbolic links, so the assets were installed by using absolute symbolic links.'); - } catch (IOException $e) { - $this->hardCopy($originDir, $targetDir); - $output->writeln('It looks like your system doesn\'t support symbolic links, so the assets were installed by copying them.'); - } - } - } else { - $this->hardCopy($originDir, $targetDir); - } + if (!is_dir($originDir = $bundle->getPath().'/Resources/public')) { + continue; } + + $targetDir = $bundlesDir.preg_replace('/bundle$/', '', strtolower($bundle->getName())); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $message = sprintf("%s\n-> %s", $bundle->getName(), $targetDir); + } else { + $message = $bundle->getName(); + } + + try { + $this->filesystem->remove($targetDir); + + if (self::METHOD_RELATIVE_SYMLINK === $expectedMethod) { + $method = $this->relativeSymlinkWithFallback($originDir, $targetDir); + } elseif (self::METHOD_ABSOLUTE_SYMLINK === $expectedMethod) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } else { + $method = $this->hardCopy($originDir, $targetDir); + } + + if (self::METHOD_COPY === $method) { + $copyUsed = true; + } + + if ($method === $expectedMethod) { + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'OK' : "\xE2\x9C\x94" /* HEAVY CHECK MARK (U+2714) */), $message, $method); + } else { + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method); + } + } catch (\Exception $e) { + $exitCode = 1; + $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()); + } + } + + $io->table(array('', 'Bundle', 'Method / Error'), $rows); + + if (0 !== $exitCode) { + $io->error('Some errors occurred while installing assets.'); + } else { + if ($copyUsed) { + $io->note('Some assets were installed via copy. If you make changes to these assets you have to run this command again.'); + } + $io->success('All assets were successfully installed.'); + } + + return $exitCode; + } + + /** + * Try to create relative symlink. + * + * Falling back to absolute symlink and finally hard copy. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function relativeSymlinkWithFallback($originDir, $targetDir) + { + try { + $this->symlink($originDir, $targetDir, true); + $method = self::METHOD_RELATIVE_SYMLINK; + } catch (IOException $e) { + $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); + } + + return $method; + } + + /** + * Try to create absolute symlink. + * + * Falling back to hard copy. + * + * @param string $originDir + * @param string $targetDir + * + * @return string + */ + private function absoluteSymlinkWithFallback($originDir, $targetDir) + { + try { + $this->symlink($originDir, $targetDir); + $method = self::METHOD_ABSOLUTE_SYMLINK; + } catch (IOException $e) { + // fall back to copy + $method = $this->hardCopy($originDir, $targetDir); + } + + return $method; + } + + /** + * Creates symbolic link. + * + * @param string $originDir + * @param string $targetDir + * @param bool $relative + * + * @throws IOException If link can not be created. + */ + private function symlink($originDir, $targetDir, $relative = false) + { + if ($relative) { + $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir))); + } + $this->filesystem->symlink($originDir, $targetDir); + if (!file_exists($targetDir)) { + throw new IOException(sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), 0, null, $targetDir); } } /** + * Copies origin to target. + * * @param string $originDir * @param string $targetDir + * + * @return string */ private function hardCopy($originDir, $targetDir) { - $filesystem = $this->getContainer()->get('filesystem'); - - $filesystem->mkdir($targetDir, 0777); + $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files - $filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + $this->filesystem->mirror($originDir, $targetDir, Finder::create()->ignoreDotFiles(false)->in($originDir)); + + return self::METHOD_COPY; } }