Skip to content

Commit

Permalink
feature #23792 [HttpKernel][FrameworkBundle] Add RebootableInterface,…
Browse files Browse the repository at this point in the history
… fix and un-deprecate cache:clear with warmup (nicolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpKernel][FrameworkBundle] Add RebootableInterface, fix and un-deprecate cache:clear with warmup

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #23592, #8500
| License       | MIT
| Doc PR        | -

This PR fixes the reasons why we deprecated cache:clear without --no-warmup.

Internally, it requires some cooperation from the Kernel, so that a deprecation is needed.
This allows the same kernel instance to be rebooted under a new cache directory, used during the warmup.
It leverages the new capability to create two independent non colliding containers.

Commits
-------

a4fc492 [HttpKernel][FrameworkBundle] Add RebootableInterface, fix and un-deprecate cache:clear with warmup
  • Loading branch information
fabpot committed Aug 18, 2017
2 parents f751ac9 + a4fc492 commit 19e6f0c
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 33 deletions.
3 changes: 3 additions & 0 deletions UPGRADE-3.4.md
Expand Up @@ -143,6 +143,9 @@ HttpKernel
tags: ['console.command']
```

* The `getCacheDir()` method of your kernel should not be called while building the container.
Use the `%kernel.cache_dir%` parameter instead. Not doing so may break the `cache:clear` command.

Process
-------

Expand Down
3 changes: 3 additions & 0 deletions UPGRADE-4.0.md
Expand Up @@ -510,6 +510,9 @@ HttpKernel
by Symfony. Use the `%env()%` syntax to get the value of any environment
variable from configuration files instead.

* The `getCacheDir()` method of your kernel should not be called while building the container.
Use the `%kernel.cache_dir%` parameter instead. Not doing so may break the `cache:clear` command.

Ldap
----

Expand Down
58 changes: 38 additions & 20 deletions src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
Expand Up @@ -19,6 +19,7 @@
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\RebootableInterface;
use Symfony\Component\Finder\Finder;

/**
Expand All @@ -33,6 +34,7 @@ class CacheClearCommand extends ContainerAwareCommand
{
private $cacheClearer;
private $filesystem;
private $warning;

/**
* @param CacheClearerInterface $cacheClearer
Expand Down Expand Up @@ -112,6 +114,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->filesystem->rename($realCacheDir, $oldCacheDir);
} else {
$this->warmupCache($input, $output, $realCacheDir, $oldCacheDir);

if ($this->warning) {
@trigger_error($this->warning, E_USER_DEPRECATED);
$io->warning($this->warning);
$this->warning = null;
}
}

if ($output->isVerbose()) {
Expand Down Expand Up @@ -167,17 +175,23 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr
{
// create a temporary kernel
$realKernel = $this->getApplication()->getKernel();
$realKernelClass = get_class($realKernel);
$namespace = '';
if (false !== $pos = strrpos($realKernelClass, '\\')) {
$namespace = substr($realKernelClass, 0, $pos);
$realKernelClass = substr($realKernelClass, $pos + 1);
}
$tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir);
$tempKernel->boot();
if ($realKernel instanceof RebootableInterface) {
$realKernel->reboot($warmupDir);
$tempKernel = $realKernel;
} else {
$this->warning = 'Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is deprecated since version 3.4 and will be unsupported in 4.0.';
$realKernelClass = get_class($realKernel);
$namespace = '';
if (false !== $pos = strrpos($realKernelClass, '\\')) {
$namespace = substr($realKernelClass, 0, $pos);
$realKernelClass = substr($realKernelClass, $pos + 1);
}
$tempKernel = $this->getTempKernel($realKernel, $namespace, $realKernelClass, $warmupDir);
$tempKernel->boot();

$tempKernelReflection = new \ReflectionObject($tempKernel);
$tempKernelFile = $tempKernelReflection->getFileName();
$tempKernelReflection = new \ReflectionObject($tempKernel);
$tempKernelFile = $tempKernelReflection->getFileName();
}

// warmup temporary dir
$warmer = $tempKernel->getContainer()->get('cache_warmer');
Expand All @@ -186,6 +200,20 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr
}
$warmer->warmUp($warmupDir);

// fix references to cached files with the real cache directory name
$search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir));
$replace = str_replace('\\', '/', $realCacheDir);
foreach (Finder::create()->files()->in($warmupDir) as $file) {
$content = str_replace($search, $replace, file_get_contents($file), $count);
if ($count) {
file_put_contents($file, $content);
}
}

if ($realKernel instanceof RebootableInterface) {
return;
}

// fix references to the Kernel in .meta files
$safeTempKernel = str_replace('\\', '\\\\', get_class($tempKernel));
$realKernelFQN = get_class($realKernel);
Expand All @@ -198,16 +226,6 @@ protected function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = tr
));
}

// fix references to cached files with the real cache directory name
$search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir));
$replace = str_replace('\\', '/', $realCacheDir);
foreach (Finder::create()->files()->in($warmupDir) as $file) {
$content = str_replace($search, $replace, file_get_contents($file), $count);
if ($count) {
file_put_contents($file, $content);
}
}

// fix references to container's class
$tempContainerClass = $tempKernel->getContainerClass();
$realContainerClass = $tempKernel->getRealContainerClass();
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpKernel/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
3.4.0
-----

* added `RebootableInterface` and implemented it in `Kernel`
* deprecated commands auto registration
* added `AddCacheClearerPass`
* added `AddCacheWarmerPass`
Expand Down
36 changes: 23 additions & 13 deletions src/Symfony/Component/HttpKernel/Kernel.php
Expand Up @@ -43,7 +43,7 @@
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Kernel implements KernelInterface, TerminableInterface
abstract class Kernel implements KernelInterface, RebootableInterface, TerminableInterface
{
/**
* @var BundleInterface[]
Expand All @@ -61,6 +61,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
protected $loadClassCache;

private $projectDir;
private $warmupDir;

const VERSION = '3.4.0-DEV';
const VERSION_ID = 30400;
Expand Down Expand Up @@ -127,6 +128,16 @@ public function boot()
$this->booted = true;
}

/**
* {@inheritdoc}
*/
public function reboot($warmupDir)
{
$this->shutdown();
$this->warmupDir = $warmupDir;
$this->boot();
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -373,15 +384,15 @@ public function setClassCache(array $classes)
@trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED);
}

file_put_contents($this->getCacheDir().'/classes.map', sprintf('<?php return %s;', var_export($classes, true)));
file_put_contents(($this->warmupDir ?: $this->getCacheDir()).'/classes.map', sprintf('<?php return %s;', var_export($classes, true)));
}

/**
* @internal
*/
public function setAnnotatedClassCache(array $annotatedClasses)
{
file_put_contents($this->getCacheDir().'/annotations.map', sprintf('<?php return %s;', var_export($annotatedClasses, true)));
file_put_contents(($this->warmupDir ?: $this->getCacheDir()).'/annotations.map', sprintf('<?php return %s;', var_export($annotatedClasses, true)));
}

/**
Expand Down Expand Up @@ -424,9 +435,10 @@ protected function doLoadClassCache($name, $extension)
if (\PHP_VERSION_ID >= 70000) {
@trigger_error(__METHOD__.'() is deprecated since version 3.3, to be removed in 4.0.', E_USER_DEPRECATED);
}
$cacheDir = $this->warmupDir ?: $this->getCacheDir();

if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) {
ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension);
if (!$this->booted && is_file($cacheDir.'/classes.map')) {
ClassCollectionLoader::load(include($cacheDir.'/classes.map'), $cacheDir, $name, $this->debug, false, $extension);
}
}

Expand Down Expand Up @@ -536,7 +548,8 @@ protected function getContainerBaseClass()
protected function initializeContainer()
{
$class = $this->getContainerClass();
$cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug);
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
$fresh = true;
if (!$cache->isFresh()) {
if ($this->debug) {
Expand Down Expand Up @@ -580,8 +593,8 @@ protected function initializeContainer()
if ($this->debug) {
restore_error_handler();

file_put_contents($this->getCacheDir().'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs)));
file_put_contents($this->getCacheDir().'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : '');
file_put_contents($cacheDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs)));
file_put_contents($cacheDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : '');
}
}

Expand Down Expand Up @@ -636,7 +649,7 @@ protected function getKernelParameters()
'kernel.environment' => $this->environment,
'kernel.debug' => $this->debug,
'kernel.name' => $this->name,
'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(),
'kernel.cache_dir' => realpath($cacheDir = $this->warmupDir ?: $this->getCacheDir()) ?: $cacheDir,
'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(),
'kernel.bundles' => $bundles,
'kernel.bundles_metadata' => $bundlesMetadata,
Expand Down Expand Up @@ -682,7 +695,7 @@ protected function getEnvParameters()
*/
protected function buildContainer()
{
foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) {
foreach (array('cache' => $this->warmupDir ?: $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) {
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir));
Expand Down Expand Up @@ -786,9 +799,6 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container
@chmod($dir.$file, 0666 & ~umask());
}

// track changes made to the container directory
$container->fileExists(dirname($dir.$file));

$cache->write($rootCode, $container->getResources());
}

Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/HttpKernel/RebootableInterface.php
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel;

/**
* Allows the Kernel to be rebooted using a temporary cache directory.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface RebootableInterface
{
/**
* Reboots a kernel.
*
* The getCacheDir() method of a rebootable kernel should not be called
* while building the container. Use the %kernel.cache_dir% parameter instead.
*
* @param string|null $warmupDir pass null to reboot in the regular cache directory
*/
public function reboot($warmupDir);
}

0 comments on commit 19e6f0c

Please sign in to comment.