vendor/symfony/framework-bundle/Command/CacheClearCommand.php line 71

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Bundle\FrameworkBundle\Command;
  11. use Symfony\Component\Console\Command\Command;
  12. use Symfony\Component\Console\Exception\RuntimeException;
  13. use Symfony\Component\Console\Input\InputInterface;
  14. use Symfony\Component\Console\Input\InputOption;
  15. use Symfony\Component\Console\Output\OutputInterface;
  16. use Symfony\Component\Console\Style\SymfonyStyle;
  17. use Symfony\Component\DependencyInjection\Dumper\Preloader;
  18. use Symfony\Component\EventDispatcher\EventDispatcher;
  19. use Symfony\Component\Filesystem\Exception\IOException;
  20. use Symfony\Component\Filesystem\Filesystem;
  21. use Symfony\Component\Finder\Finder;
  22. use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
  23. use Symfony\Component\HttpKernel\RebootableInterface;
  24. /**
  25.  * Clear and Warmup the cache.
  26.  *
  27.  * @author Francis Besset <francis.besset@gmail.com>
  28.  * @author Fabien Potencier <fabien@symfony.com>
  29.  *
  30.  * @final
  31.  */
  32. class CacheClearCommand extends Command
  33. {
  34.     protected static $defaultName = 'cache:clear';
  35.     protected static $defaultDescription = 'Clear the cache';
  36.     private $cacheClearer;
  37.     private $filesystem;
  38.     public function __construct(CacheClearerInterface $cacheClearer, ?Filesystem $filesystem = null)
  39.     {
  40.         parent::__construct();
  41.         $this->cacheClearer = $cacheClearer;
  42.         $this->filesystem = $filesystem ?? new Filesystem();
  43.     }
  44.     protected function configure()
  45.     {
  46.         $this
  47.             ->setDefinition([
  48.                 new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
  49.                 new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
  50.             ])
  51.             ->setDescription(self::$defaultDescription)
  52.             ->setHelp(<<<'EOF'
  53. The <info>%command.name%</info> command clears and warms up the application cache for a given environment
  54. and debug mode:
  55.   <info>php %command.full_name% --env=dev</info>
  56.   <info>php %command.full_name% --env=prod --no-debug</info>
  57. EOF
  58.             )
  59.         ;
  60.     }
  61.     protected function execute(InputInterface $input, OutputInterface $output): int
  62.     {
  63.         $fs = $this->filesystem;
  64.         $io = new SymfonyStyle($input, $output);
  65.         $kernel = $this->getApplication()->getKernel();
  66.         $realCacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
  67.         $realBuildDir = $kernel->getContainer()->hasParameter('kernel.build_dir') ? $kernel->getContainer()->getParameter('kernel.build_dir') : $realCacheDir;
  68.         // the old cache dir name must not be longer than the real one to avoid exceeding
  69.         // the maximum length of a directory or file path within it (esp. Windows MAX_PATH)
  70.         $oldCacheDir = substr($realCacheDir, 0, -1).(str_ends_with($realCacheDir, '~') ? '+' : '~');
  71.         $fs->remove($oldCacheDir);
  72.         if (!is_writable($realCacheDir)) {
  73.             throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realCacheDir));
  74.         }
  75.         $useBuildDir = $realBuildDir !== $realCacheDir;
  76.         $oldBuildDir = substr($realBuildDir, 0, -1).('~' === substr($realBuildDir, -1) ? '+' : '~');
  77.         if ($useBuildDir) {
  78.             $fs->remove($oldBuildDir);
  79.             if (!is_writable($realBuildDir)) {
  80.                 throw new RuntimeException(sprintf('Unable to write in the "%s" directory.', $realBuildDir));
  81.             }
  82.             if ($this->isNfs($realCacheDir)) {
  83.                 $fs->remove($realCacheDir);
  84.             } else {
  85.                 $fs->rename($realCacheDir, $oldCacheDir);
  86.             }
  87.             $fs->mkdir($realCacheDir);
  88.         }
  89.         $io->comment(sprintf('Clearing the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
  90.         if ($useBuildDir) {
  91.             $this->cacheClearer->clear($realBuildDir);
  92.         }
  93.         $this->cacheClearer->clear($realCacheDir);
  94.         // The current event dispatcher is stale, let's not use it anymore
  95.         $this->getApplication()->setDispatcher(new EventDispatcher());
  96.         $containerFile = (new \ReflectionObject($kernel->getContainer()))->getFileName();
  97.         $containerDir = basename(\dirname($containerFile));
  98.         // the warmup cache dir name must have the same length as the real one
  99.         // to avoid the many problems in serialized resources files
  100.         $warmupDir = substr($realBuildDir, 0, -1).('_' === substr($realBuildDir, -1) ? '-' : '_');
  101.         if ($output->isVerbose() && $fs->exists($warmupDir)) {
  102.             $io->comment('Clearing outdated warmup directory...');
  103.         }
  104.         $fs->remove($warmupDir);
  105.         if ($_SERVER['REQUEST_TIME'] <= filemtime($containerFile) && filemtime($containerFile) <= time()) {
  106.             if ($output->isVerbose()) {
  107.                 $io->comment('Cache is fresh.');
  108.             }
  109.             if (!$input->getOption('no-warmup') && !$input->getOption('no-optional-warmers')) {
  110.                 if ($output->isVerbose()) {
  111.                     $io->comment('Warming up optional cache...');
  112.                 }
  113.                 $this->warmupOptionals($realCacheDir, $realBuildDir);
  114.             }
  115.         } else {
  116.             $fs->mkdir($warmupDir);
  117.             if (!$input->getOption('no-warmup')) {
  118.                 if ($output->isVerbose()) {
  119.                     $io->comment('Warming up cache...');
  120.                 }
  121.                 $this->warmup($warmupDir, $realBuildDir);
  122.                 if (!$input->getOption('no-optional-warmers')) {
  123.                     if ($output->isVerbose()) {
  124.                         $io->comment('Warming up optional cache...');
  125.                     }
  126.                     $this->warmupOptionals($useBuildDir ? $realCacheDir : $warmupDir, $warmupDir);
  127.                 }
  128.             }
  129.             if (!$fs->exists($warmupDir.'/'.$containerDir)) {
  130.                 $fs->rename($realBuildDir.'/'.$containerDir, $warmupDir.'/'.$containerDir);
  131.                 touch($warmupDir.'/'.$containerDir.'.legacy');
  132.             }
  133.             if ($this->isNfs($realBuildDir)) {
  134.                 $io->note('For better performance, you should move the cache and log directories to a non-shared folder of the VM.');
  135.                 $fs->remove($realBuildDir);
  136.             } else {
  137.                 $fs->rename($realBuildDir, $oldBuildDir);
  138.             }
  139.             $fs->rename($warmupDir, $realBuildDir);
  140.             if ($output->isVerbose()) {
  141.                 $io->comment('Removing old build and cache directory...');
  142.             }
  143.             if ($useBuildDir) {
  144.                 try {
  145.                     $fs->remove($oldBuildDir);
  146.                 } catch (IOException $e) {
  147.                     if ($output->isVerbose()) {
  148.                         $io->warning($e->getMessage());
  149.                     }
  150.                 }
  151.             }
  152.             try {
  153.                 $fs->remove($oldCacheDir);
  154.             } catch (IOException $e) {
  155.                 if ($output->isVerbose()) {
  156.                     $io->warning($e->getMessage());
  157.                 }
  158.             }
  159.         }
  160.         if ($output->isVerbose()) {
  161.             $io->comment('Finished');
  162.         }
  163.         $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
  164.         return 0;
  165.     }
  166.     private function isNfs(string $dir): bool
  167.     {
  168.         static $mounts = null;
  169.         if (null === $mounts) {
  170.             $mounts = [];
  171.             if ('/' === \DIRECTORY_SEPARATOR && @is_readable('/proc/mounts') && $files = @file('/proc/mounts')) {
  172.                 foreach ($files as $mount) {
  173.                     $mount = \array_slice(explode(' ', $mount), 1, -3);
  174.                     if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) {
  175.                         continue;
  176.                     }
  177.                     $mounts[] = implode(' ', $mount).'/';
  178.                 }
  179.             }
  180.         }
  181.         foreach ($mounts as $mount) {
  182.             if (0 === strpos($dir, $mount)) {
  183.                 return true;
  184.             }
  185.         }
  186.         return false;
  187.     }
  188.     private function warmup(string $warmupDir, string $realBuildDir): void
  189.     {
  190.         // create a temporary kernel
  191.         $kernel = $this->getApplication()->getKernel();
  192.         if (!$kernel instanceof RebootableInterface) {
  193.             throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.');
  194.         }
  195.         $kernel->reboot($warmupDir);
  196.         // fix references to cached files with the real cache directory name
  197.         $search = [$warmupDir, str_replace('\\', '\\\\', $warmupDir)];
  198.         $replace = str_replace('\\', '/', $realBuildDir);
  199.         foreach (Finder::create()->files()->in($warmupDir) as $file) {
  200.             $content = str_replace($search, $replace, file_get_contents($file), $count);
  201.             if ($count) {
  202.                 file_put_contents($file, $content);
  203.             }
  204.         }
  205.     }
  206.     private function warmupOptionals(string $cacheDir, string $warmupDir): void
  207.     {
  208.         $kernel = $this->getApplication()->getKernel();
  209.         $warmer = $kernel->getContainer()->get('cache_warmer');
  210.         // non optional warmers already ran during container compilation
  211.         $warmer->enableOnlyOptionalWarmers();
  212.         $preload = (array) $warmer->warmUp($cacheDir);
  213.         if ($preload && file_exists($preloadFile = $warmupDir.'/'.$kernel->getContainer()->getParameter('kernel.container_class').'.preload.php')) {
  214.             Preloader::append($preloadFile, $preload);
  215.         }
  216.     }
  217. }