CacheClearCommand.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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\Input\InputInterface;
  12. use Symfony\Component\Console\Input\InputOption;
  13. use Symfony\Component\Console\Output\OutputInterface;
  14. use Symfony\Component\Console\Style\SymfonyStyle;
  15. use Symfony\Component\Finder\Finder;
  16. use Symfony\Component\HttpKernel\KernelInterface;
  17. /**
  18. * Clear and Warmup the cache.
  19. *
  20. * @author Francis Besset <francis.besset@gmail.com>
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. */
  23. class CacheClearCommand extends ContainerAwareCommand
  24. {
  25. /**
  26. * {@inheritdoc}
  27. */
  28. protected function configure()
  29. {
  30. $this
  31. ->setName('cache:clear')
  32. ->setDefinition(array(
  33. new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
  34. new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
  35. ))
  36. ->setDescription('Clears the cache')
  37. ->setHelp(<<<'EOF'
  38. The <info>%command.name%</info> command clears the application cache for a given environment
  39. and debug mode:
  40. <info>php %command.full_name% --env=dev</info>
  41. <info>php %command.full_name% --env=prod --no-debug</info>
  42. EOF
  43. )
  44. ;
  45. }
  46. /**
  47. * {@inheritdoc}
  48. */
  49. protected function execute(InputInterface $input, OutputInterface $output)
  50. {
  51. $outputIsVerbose = $output->isVerbose();
  52. $io = new SymfonyStyle($input, $output);
  53. $realCacheDir = $this->getContainer()->getParameter('kernel.cache_dir');
  54. // the old cache dir name must not be longer than the real one to avoid exceeding
  55. // the maximum length of a directory or file path within it (esp. Windows MAX_PATH)
  56. $oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~');
  57. $filesystem = $this->getContainer()->get('filesystem');
  58. if (!is_writable($realCacheDir)) {
  59. throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir));
  60. }
  61. if ($filesystem->exists($oldCacheDir)) {
  62. $filesystem->remove($oldCacheDir);
  63. }
  64. $kernel = $this->getContainer()->get('kernel');
  65. $io->comment(sprintf('Clearing the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
  66. $this->getContainer()->get('cache_clearer')->clear($realCacheDir);
  67. if ($input->getOption('no-warmup')) {
  68. $filesystem->rename($realCacheDir, $oldCacheDir);
  69. } else {
  70. // the warmup cache dir name must have the same length than the real one
  71. // to avoid the many problems in serialized resources files
  72. $realCacheDir = realpath($realCacheDir);
  73. $warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_');
  74. if ($filesystem->exists($warmupDir)) {
  75. if ($outputIsVerbose) {
  76. $io->comment('Clearing outdated warmup directory...');
  77. }
  78. $filesystem->remove($warmupDir);
  79. }
  80. if ($outputIsVerbose) {
  81. $io->comment('Warming up cache...');
  82. }
  83. $this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
  84. $filesystem->rename($realCacheDir, $oldCacheDir);
  85. if ('\\' === \DIRECTORY_SEPARATOR) {
  86. sleep(1); // workaround for Windows PHP rename bug
  87. }
  88. $filesystem->rename($warmupDir, $realCacheDir);
  89. }
  90. if ($outputIsVerbose) {
  91. $io->comment('Removing old cache directory...');
  92. }
  93. $filesystem->remove($oldCacheDir);
  94. if ($outputIsVerbose) {
  95. $io->comment('Finished');
  96. }
  97. $io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
  98. }
  99. /**
  100. * @param string $warmupDir
  101. * @param string $realCacheDir
  102. * @param bool $enableOptionalWarmers
  103. */
  104. protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true)
  105. {
  106. // create a temporary kernel
  107. $realKernel = $this->getContainer()->get('kernel');
  108. $realKernelClass = \get_class($realKernel);
  109. $namespace = '';
  110. if (false !== $pos = strrpos($realKernelClass, '\\')) {
  111. $namespace = substr($realKernelClass, 0, $pos);
  112. $realKernelClass = substr($realKernelClass, $pos + 1);
  113. }
  114. $tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir);
  115. $tempKernel->boot();
  116. $tempKernelReflection = new \ReflectionObject($tempKernel);
  117. $tempKernelFile = $tempKernelReflection->getFileName();
  118. // warmup temporary dir
  119. $warmer = $tempKernel->getContainer()->get('cache_warmer');
  120. if ($enableOptionalWarmers) {
  121. $warmer->enableOptionalWarmers();
  122. }
  123. $warmer->warmUp($warmupDir);
  124. // fix references to the Kernel in .meta files
  125. $safeTempKernel = str_replace('\\', '\\\\', \get_class($tempKernel));
  126. $realKernelFQN = \get_class($realKernel);
  127. foreach (Finder::create()->files()->depth('<3')->name('*.meta')->in($warmupDir) as $file) {
  128. file_put_contents($file, preg_replace(
  129. '/(C\:\d+\:)"'.$safeTempKernel.'"/',
  130. sprintf('$1"%s"', $realKernelFQN),
  131. file_get_contents($file)
  132. ));
  133. }
  134. // fix references to cached files with the real cache directory name
  135. $search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir));
  136. $replace = str_replace('\\', '/', $realCacheDir);
  137. foreach (Finder::create()->files()->in($warmupDir) as $file) {
  138. $content = str_replace($search, $replace, file_get_contents($file), $count);
  139. if ($count) {
  140. file_put_contents($file, $content);
  141. }
  142. }
  143. // fix references to container's class
  144. $tempContainerClass = \get_class($tempKernel->getContainer());
  145. $realContainerClass = \get_class($realKernel->getContainer());
  146. foreach (Finder::create()->files()->depth('<2')->name($tempContainerClass.'*')->in($warmupDir) as $file) {
  147. $content = str_replace($tempContainerClass, $realContainerClass, file_get_contents($file));
  148. file_put_contents($file, $content);
  149. rename($file, str_replace(\DIRECTORY_SEPARATOR.$tempContainerClass, \DIRECTORY_SEPARATOR.$realContainerClass, $file));
  150. }
  151. // remove temp kernel file after cache warmed up
  152. @unlink($tempKernelFile);
  153. }
  154. /**
  155. * @param KernelInterface $parent
  156. * @param string $namespace
  157. * @param string $parentClass
  158. * @param string $warmupDir
  159. *
  160. * @return KernelInterface
  161. */
  162. protected function getTempKernel(KernelInterface $parent, $namespace, $parentClass, $warmupDir)
  163. {
  164. $cacheDir = var_export($warmupDir, true);
  165. $rootDir = var_export(realpath($parent->getRootDir()), true);
  166. $logDir = var_export(realpath($parent->getLogDir()), true);
  167. // the temp kernel class name must have the same length than the real one
  168. // to avoid the many problems in serialized resources files
  169. $class = substr($parentClass, 0, -1).'_';
  170. // the temp container class must be changed too
  171. $containerClass = var_export(substr(\get_class($parent->getContainer()), 0, -1).'_', true);
  172. $code = <<<EOF
  173. <?php
  174. namespace $namespace
  175. {
  176. class $class extends $parentClass
  177. {
  178. public function getCacheDir()
  179. {
  180. return $cacheDir;
  181. }
  182. public function getRootDir()
  183. {
  184. return $rootDir;
  185. }
  186. public function getLogDir()
  187. {
  188. return $logDir;
  189. }
  190. protected function getContainerClass()
  191. {
  192. return $containerClass;
  193. }
  194. protected function buildContainer()
  195. {
  196. \$container = parent::buildContainer();
  197. // filter container's resources, removing reference to temp kernel file
  198. \$resources = \$container->getResources();
  199. \$filteredResources = array();
  200. foreach (\$resources as \$resource) {
  201. if ((string) \$resource !== __FILE__) {
  202. \$filteredResources[] = \$resource;
  203. }
  204. }
  205. \$container->setResources(\$filteredResources);
  206. return \$container;
  207. }
  208. }
  209. }
  210. EOF;
  211. $this->getContainer()->get('filesystem')->mkdir($warmupDir);
  212. file_put_contents($file = $warmupDir.'/kernel.tmp', $code);
  213. require_once $file;
  214. $class = "$namespace\\$class";
  215. return new $class($parent->getEnvironment(), $parent->isDebug());
  216. }
  217. }