Skip to content

Commit

Permalink
Merge branch '3.4'
Browse files Browse the repository at this point in the history
* 3.4:
  [HttpKernel][FrameworkBundle] Add RebootableInterface, fix and un-deprecate cache:clear with warmup
  [DI] Fix merging of env vars in configs
  Allow to get alternatives when ServiceNotFoundException occurs.
  [DI] Rererence parameter arrays when possible
  Revert "feature #21038 [FrameworkBundle] deprecated cache:clear with warmup (fabpot)"
  • Loading branch information
nicolas-grekas committed Aug 18, 2017
2 parents 8a58d96 + 3fa5b3a commit c8d7a6f
Show file tree
Hide file tree
Showing 19 changed files with 329 additions and 42 deletions.
3 changes: 0 additions & 3 deletions UPGRADE-3.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,6 @@ Form
FrameworkBundle
---------------

* The `cache:clear` command should always be called with the `--no-warmup` option.
Warmup should be done via the `cache:warmup` command.

* [BC BREAK] The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies"
parameter have been removed. Use the Request::setTrustedProxies() method in your front controller instead.

Expand Down
3 changes: 3 additions & 0 deletions UPGRADE-3.4.md
Original file line number Diff line number Diff line change
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
6 changes: 3 additions & 3 deletions UPGRADE-4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,6 @@ FrameworkBundle

* The `validator.mapping.cache.doctrine.apc` service has been removed.

* The `cache:clear` command does not warmup the cache anymore. Warmup should
be done via the `cache:warmup` command.

* The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter have been removed. Use the `Request::setTrustedProxies()` method in your front controller instead.

* The default value of the `framework.workflows.[name].type` configuration options is now `state_machine`.
Expand Down Expand Up @@ -516,6 +513,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
1 change: 0 additions & 1 deletion src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ CHANGELOG
the same helpers as the `Controller` class, but does not allow accessing the dependency
injection container, in order to encourage explicit dependency declarations.
* Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions
* Deprecated `cache:clear` with warmup (always call it with `--no-warmup`)
* Changed default configuration for
assets/forms/validation/translation/serialization/csrf from `canBeEnabled()` to
`canBeDisabled()` when Flex is used
Expand Down
88 changes: 82 additions & 6 deletions src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\RebootableInterface;
use Symfony\Component\Finder\Finder;

/**
* Clear and Warmup the cache.
Expand Down Expand Up @@ -53,7 +55,8 @@ protected function configure()
$this
->setName('cache:clear')
->setDefinition(array(
new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Noop. Will be deprecated in 4.1 to be removed in 5.0.'),
new InputOption('no-warmup', '', InputOption::VALUE_NONE, 'Do not warm up the cache'),
new InputOption('no-optional-warmers', '', InputOption::VALUE_NONE, 'Skip optional cache warmers (faster)'),
))
->setDescription('Clears the cache')
->setHelp(<<<'EOF'
Expand All @@ -75,20 +78,33 @@ protected function execute(InputInterface $input, OutputInterface $output)
$io = new SymfonyStyle($input, $output);

$kernel = $this->getApplication()->getKernel();
$cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
$realCacheDir = isset($realCacheDir) ? $realCacheDir : $kernel->getContainer()->getParameter('kernel.cache_dir');
// the old cache dir name must not be longer than the real one to avoid exceeding
// the maximum length of a directory or file path within it (esp. Windows MAX_PATH)
$oldCacheDir = substr($realCacheDir, 0, -1).('~' === substr($realCacheDir, -1) ? '+' : '~');

if (!is_writable($cacheDir)) {
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $cacheDir));
if (!is_writable($realCacheDir)) {
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir));
}

if ($this->filesystem->exists($oldCacheDir)) {
$this->filesystem->remove($oldCacheDir);
}

$io->comment(sprintf('Clearing the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
$this->cacheClearer->clear($cacheDir);
$this->cacheClearer->clear($realCacheDir);

if ($input->getOption('no-warmup')) {
$this->filesystem->rename($realCacheDir, $oldCacheDir);
} else {
$this->warmupCache($input, $output, $realCacheDir, $oldCacheDir);
}

if ($output->isVerbose()) {
$io->comment('Removing old cache directory...');
}

$this->filesystem->remove($cacheDir);
$this->filesystem->remove($oldCacheDir);

// The current event dispatcher is stale, let's not use it anymore
$this->getApplication()->setDispatcher(new EventDispatcher());
Expand All @@ -99,4 +115,64 @@ protected function execute(InputInterface $input, OutputInterface $output)

$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
}

private function warmupCache(InputInterface $input, OutputInterface $output, $realCacheDir, $oldCacheDir)
{
$io = new SymfonyStyle($input, $output);

// the warmup cache dir name must have the same length than the real one
// to avoid the many problems in serialized resources files
$realCacheDir = realpath($realCacheDir);
$warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_');

if ($this->filesystem->exists($warmupDir)) {
if ($output->isVerbose()) {
$io->comment('Clearing outdated warmup directory...');
}
$this->filesystem->remove($warmupDir);
}

if ($output->isVerbose()) {
$io->comment('Warming up cache...');
}
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));

$this->filesystem->rename($realCacheDir, $oldCacheDir);
if ('\\' === DIRECTORY_SEPARATOR) {
sleep(1); // workaround for Windows PHP rename bug
}
$this->filesystem->rename($warmupDir, $realCacheDir);
}

/**
* @param string $warmupDir
* @param string $realCacheDir
* @param bool $enableOptionalWarmers
*/
private function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true)
{
// create a temporary kernel
$kernel = $this->getApplication()->getKernel();
if (!$kernel instanceof RebootableInterface) {
throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.');
}
$kernel->reboot($warmupDir);

// warmup temporary dir
$warmer = $kernel->getContainer()->get('cache_warmer');
if ($enableOptionalWarmers) {
$warmer->enableOptionalWarmers();
}
$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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture\TestAppKernel;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Component\Config\ConfigCacheFactory;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;

class CacheClearCommandTest extends TestCase
{
Expand All @@ -40,14 +43,47 @@ protected function tearDown()
$this->fs->remove($this->rootDir);
}

public function testCacheIsCleared()
public function testCacheIsFreshAfterCacheClearedWithWarmup()
{
$input = new ArrayInput(array('cache:clear'));
$application = new Application($this->kernel);
$application->setCatchExceptions(false);

$application->doRun($input, new NullOutput());

$this->assertDirectoryNotExists($this->kernel->getCacheDir());
// Ensure that all *.meta files are fresh
$finder = new Finder();
$metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta');
// simply check that cache is warmed up
$this->assertGreaterThanOrEqual(1, count($metaFiles));
$configCacheFactory = new ConfigCacheFactory(true);

foreach ($metaFiles as $file) {
$configCacheFactory->cache(substr($file, 0, -5), function () use ($file) {
$this->fail(sprintf('Meta file "%s" is not fresh', (string) $file));
});
}

// check that app kernel file present in meta file of container's cache
$containerClass = $this->kernel->getContainer()->getParameter('kernel.container_class');
$containerRef = new \ReflectionClass($containerClass);
$containerFile = dirname(dirname($containerRef->getFileName())).'/'.$containerClass.'.php';
$containerMetaFile = $containerFile.'.meta';
$kernelRef = new \ReflectionObject($this->kernel);
$kernelFile = $kernelRef->getFileName();
/** @var ResourceInterface[] $meta */
$meta = unserialize(file_get_contents($containerMetaFile));
$found = false;
foreach ($meta as $resource) {
if ((string) $resource === $kernelFile) {
$found = true;
break;
}
}
$this->assertTrue($found, 'Kernel file should present as resource');

$containerRef = new \ReflectionClass(require $containerFile);
$containerFile = str_replace('tes_'.DIRECTORY_SEPARATOR, 'test'.DIRECTORY_SEPARATOR, $containerRef->getFileName());
$this->assertRegExp(sprintf('/\'kernel.container_class\'\s*=>\s*\'%s\'/', $containerClass), file_get_contents($containerFile), 'kernel.container_class is properly set on the dumped container');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Parameterbag\EnvPlaceholderParameterBag;

/**
* Merges extension configs into the container builder.
Expand Down Expand Up @@ -43,7 +45,10 @@ public function process(ContainerBuilder $container)
// this extension was not called
continue;
}
$config = $container->getParameterBag()->resolveValue($config);
// EnvPlaceholderParameterBag tracks env vars when calling resolveValue().
// Clone so that tracking is done in a dedicated bag.
$resolvingBag = clone $container->getParameterBag();
$config = $resolvingBag->resolveValue($config);

$tmpContainer = new ContainerBuilder($container->getParameterBag());
$tmpContainer->setResourceTracking($container->isTrackingResources());
Expand All @@ -58,6 +63,15 @@ public function process(ContainerBuilder $container)

$extension->load($config, $tmpContainer);

if ($resolvingBag instanceof EnvPlaceholderParameterBag) {
// $resolvingBag keeps track of env vars encoutered *before* merging configs
if ($extension instanceof Extension) {
// but we don't want to keep track of env vars that are *overridden* when configs are merged
$resolvingBag = new MergeExtensionConfigurationParameterBag($extension, $resolvingBag);
}
$container->getParameterBag()->mergeEnvPlaceholders($resolvingBag);
}

$container->merge($tmpContainer);
$container->getParameterBag()->add($parameters);
}
Expand All @@ -66,3 +80,58 @@ public function process(ContainerBuilder $container)
$container->addAliases($aliases);
}
}

/**
* @internal
*/
class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
{
private $beforeProcessingEnvPlaceholders;

public function __construct(Extension $extension, parent $resolvingBag)
{
$this->beforeProcessingEnvPlaceholders = $resolvingBag->getEnvPlaceholders();
$config = $this->resolveEnvPlaceholders($extension->getProcessedConfigs());
parent::__construct($this->resolveEnvReferences($config));
}

/**
* {@inheritdoc}
*/
public function getEnvPlaceholders()
{
// contains the list of env vars that are still used after configs have been merged
$envPlaceholders = parent::getEnvPlaceholders();

foreach ($envPlaceholders as $env => $placeholders) {
if (isset($this->beforeProcessingEnvPlaceholders[$env])) {
// for still-used env vars, keep track of their before-processing placeholders
$envPlaceholders[$env] += $this->beforeProcessingEnvPlaceholders[$env];
}
}

return $envPlaceholders;
}

/**
* Replaces-back env placeholders to their original "%env(FOO)%" version.
*/
private function resolveEnvPlaceholders($value)
{
if (is_array($value)) {
foreach ($value as $k => $v) {
$value[$this->resolveEnvPlaceholders($k)] = $this->resolveEnvPlaceholders($v);
}
} elseif (is_string($value)) {
foreach ($this->beforeProcessingEnvPlaceholders as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
$value = str_ireplace($placeholder, "%env($env)%", $value);
}
}
}
}

return $value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,9 @@ public function compile(bool $resolveEnvPlaceholders = false)
$bag = $this->getParameterBag();

if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
$bag->resolveEnvReferences();
$this->parameterBag = new ParameterBag($bag->all());
$this->parameterBag = new ParameterBag($bag->resolveEnvReferences($bag->all()));
$this->envPlaceholders = $bag->getEnvPlaceholders();
$this->parameterBag = $bag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
$this->parameterBag = $bag = new ParameterBag($this->resolveEnvPlaceholders($this->parameterBag->all(), true));
}

$compiler->compile($this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,9 @@ private function hasReference($id, array $arguments, $deep = false, array &$visi
private function dumpValue($value, $interpolate = true)
{
if (is_array($value)) {
if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) {
return $this->dumpValue("%$param%");
}
$code = array();
foreach ($value as $k => $v) {
$code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo
{
private $id;
private $sourceId;
private $alternatives;

public function __construct($id, $sourceId = null, \Exception $previous = null, array $alternatives = array())
{
Expand All @@ -44,6 +45,7 @@ public function __construct($id, $sourceId = null, \Exception $previous = null,

$this->id = $id;
$this->sourceId = $sourceId;
$this->alternatives = $alternatives;
}

public function getId()
Expand All @@ -55,4 +57,9 @@ public function getSourceId()
{
return $this->sourceId;
}

public function getAlternatives()
{
return $this->alternatives;
}
}
Loading

0 comments on commit c8d7a6f

Please sign in to comment.