Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c8d7a6f

Browse files
Merge branch '3.4'
* 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)"
2 parents 8a58d96 + 3fa5b3a commit c8d7a6f

19 files changed

+329
-42
lines changed

UPGRADE-3.3.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,6 @@ Form
167167
FrameworkBundle
168168
---------------
169169

170-
* The `cache:clear` command should always be called with the `--no-warmup` option.
171-
Warmup should be done via the `cache:warmup` command.
172-
173170
* [BC BREAK] The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies"
174171
parameter have been removed. Use the Request::setTrustedProxies() method in your front controller instead.
175172

UPGRADE-3.4.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ HttpKernel
143143
tags: ['console.command']
144144
```
145145
146+
* The `getCacheDir()` method of your kernel should not be called while building the container.
147+
Use the `%kernel.cache_dir%` parameter instead. Not doing so may break the `cache:clear` command.
148+
146149
Process
147150
-------
148151

UPGRADE-4.0.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,6 @@ FrameworkBundle
284284

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

287-
* The `cache:clear` command does not warmup the cache anymore. Warmup should
288-
be done via the `cache:warmup` command.
289-
290287
* 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.
291288

292289
* The default value of the `framework.workflows.[name].type` configuration options is now `state_machine`.
@@ -516,6 +513,9 @@ HttpKernel
516513
by Symfony. Use the `%env()%` syntax to get the value of any environment
517514
variable from configuration files instead.
518515

516+
* The `getCacheDir()` method of your kernel should not be called while building the container.
517+
Use the `%kernel.cache_dir%` parameter instead. Not doing so may break the `cache:clear` command.
518+
519519
Ldap
520520
----
521521

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ CHANGELOG
5757
the same helpers as the `Controller` class, but does not allow accessing the dependency
5858
injection container, in order to encourage explicit dependency declarations.
5959
* Added support for the `controller.service_arguments` tag, for injecting services into controllers' actions
60-
* Deprecated `cache:clear` with warmup (always call it with `--no-warmup`)
6160
* Changed default configuration for
6261
assets/forms/validation/translation/serialization/csrf from `canBeEnabled()` to
6362
`canBeDisabled()` when Flex is used

src/Symfony/Bundle/FrameworkBundle/Command/CacheClearCommand.php

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use Symfony\Component\EventDispatcher\EventDispatcher;
2020
use Symfony\Component\Filesystem\Filesystem;
2121
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
22+
use Symfony\Component\HttpKernel\RebootableInterface;
23+
use Symfony\Component\Finder\Finder;
2224

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

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

80-
if (!is_writable($cacheDir)) {
81-
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $cacheDir));
86+
if (!is_writable($realCacheDir)) {
87+
throw new \RuntimeException(sprintf('Unable to write in the "%s" directory', $realCacheDir));
88+
}
89+
90+
if ($this->filesystem->exists($oldCacheDir)) {
91+
$this->filesystem->remove($oldCacheDir);
8292
}
8393

8494
$io->comment(sprintf('Clearing the cache for the <info>%s</info> environment with debug <info>%s</info>', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
85-
$this->cacheClearer->clear($cacheDir);
95+
$this->cacheClearer->clear($realCacheDir);
96+
97+
if ($input->getOption('no-warmup')) {
98+
$this->filesystem->rename($realCacheDir, $oldCacheDir);
99+
} else {
100+
$this->warmupCache($input, $output, $realCacheDir, $oldCacheDir);
101+
}
86102

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

91-
$this->filesystem->remove($cacheDir);
107+
$this->filesystem->remove($oldCacheDir);
92108

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

100116
$io->success(sprintf('Cache for the "%s" environment (debug=%s) was successfully cleared.', $kernel->getEnvironment(), var_export($kernel->isDebug(), true)));
101117
}
118+
119+
private function warmupCache(InputInterface $input, OutputInterface $output, $realCacheDir, $oldCacheDir)
120+
{
121+
$io = new SymfonyStyle($input, $output);
122+
123+
// the warmup cache dir name must have the same length than the real one
124+
// to avoid the many problems in serialized resources files
125+
$realCacheDir = realpath($realCacheDir);
126+
$warmupDir = substr($realCacheDir, 0, -1).('_' === substr($realCacheDir, -1) ? '-' : '_');
127+
128+
if ($this->filesystem->exists($warmupDir)) {
129+
if ($output->isVerbose()) {
130+
$io->comment('Clearing outdated warmup directory...');
131+
}
132+
$this->filesystem->remove($warmupDir);
133+
}
134+
135+
if ($output->isVerbose()) {
136+
$io->comment('Warming up cache...');
137+
}
138+
$this->warmup($warmupDir, $realCacheDir, !$input->getOption('no-optional-warmers'));
139+
140+
$this->filesystem->rename($realCacheDir, $oldCacheDir);
141+
if ('\\' === DIRECTORY_SEPARATOR) {
142+
sleep(1); // workaround for Windows PHP rename bug
143+
}
144+
$this->filesystem->rename($warmupDir, $realCacheDir);
145+
}
146+
147+
/**
148+
* @param string $warmupDir
149+
* @param string $realCacheDir
150+
* @param bool $enableOptionalWarmers
151+
*/
152+
private function warmup($warmupDir, $realCacheDir, $enableOptionalWarmers = true)
153+
{
154+
// create a temporary kernel
155+
$kernel = $this->getApplication()->getKernel();
156+
if (!$kernel instanceof RebootableInterface) {
157+
throw new \LogicException('Calling "cache:clear" with a kernel that does not implement "Symfony\Component\HttpKernel\RebootableInterface" is not supported.');
158+
}
159+
$kernel->reboot($warmupDir);
160+
161+
// warmup temporary dir
162+
$warmer = $kernel->getContainer()->get('cache_warmer');
163+
if ($enableOptionalWarmers) {
164+
$warmer->enableOptionalWarmers();
165+
}
166+
$warmer->warmUp($warmupDir);
167+
168+
// fix references to cached files with the real cache directory name
169+
$search = array($warmupDir, str_replace('\\', '\\\\', $warmupDir));
170+
$replace = str_replace('\\', '/', $realCacheDir);
171+
foreach (Finder::create()->files()->in($warmupDir) as $file) {
172+
$content = str_replace($search, $replace, file_get_contents($file), $count);
173+
if ($count) {
174+
file_put_contents($file, $content);
175+
}
176+
}
177+
}
102178
}

src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
use Symfony\Bundle\FrameworkBundle\Console\Application;
1515
use Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture\TestAppKernel;
1616
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
17+
use Symfony\Component\Config\ConfigCacheFactory;
18+
use Symfony\Component\Config\Resource\ResourceInterface;
1719
use Symfony\Component\Console\Input\ArrayInput;
1820
use Symfony\Component\Console\Output\NullOutput;
1921
use Symfony\Component\Filesystem\Filesystem;
22+
use Symfony\Component\Finder\Finder;
2023

2124
class CacheClearCommandTest extends TestCase
2225
{
@@ -40,14 +43,47 @@ protected function tearDown()
4043
$this->fs->remove($this->rootDir);
4144
}
4245

43-
public function testCacheIsCleared()
46+
public function testCacheIsFreshAfterCacheClearedWithWarmup()
4447
{
4548
$input = new ArrayInput(array('cache:clear'));
4649
$application = new Application($this->kernel);
4750
$application->setCatchExceptions(false);
4851

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

51-
$this->assertDirectoryNotExists($this->kernel->getCacheDir());
54+
// Ensure that all *.meta files are fresh
55+
$finder = new Finder();
56+
$metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta');
57+
// simply check that cache is warmed up
58+
$this->assertGreaterThanOrEqual(1, count($metaFiles));
59+
$configCacheFactory = new ConfigCacheFactory(true);
60+
61+
foreach ($metaFiles as $file) {
62+
$configCacheFactory->cache(substr($file, 0, -5), function () use ($file) {
63+
$this->fail(sprintf('Meta file "%s" is not fresh', (string) $file));
64+
});
65+
}
66+
67+
// check that app kernel file present in meta file of container's cache
68+
$containerClass = $this->kernel->getContainer()->getParameter('kernel.container_class');
69+
$containerRef = new \ReflectionClass($containerClass);
70+
$containerFile = dirname(dirname($containerRef->getFileName())).'/'.$containerClass.'.php';
71+
$containerMetaFile = $containerFile.'.meta';
72+
$kernelRef = new \ReflectionObject($this->kernel);
73+
$kernelFile = $kernelRef->getFileName();
74+
/** @var ResourceInterface[] $meta */
75+
$meta = unserialize(file_get_contents($containerMetaFile));
76+
$found = false;
77+
foreach ($meta as $resource) {
78+
if ((string) $resource === $kernelFile) {
79+
$found = true;
80+
break;
81+
}
82+
}
83+
$this->assertTrue($found, 'Kernel file should present as resource');
84+
85+
$containerRef = new \ReflectionClass(require $containerFile);
86+
$containerFile = str_replace('tes_'.DIRECTORY_SEPARATOR, 'test'.DIRECTORY_SEPARATOR, $containerRef->getFileName());
87+
$this->assertRegExp(sprintf('/\'kernel.container_class\'\s*=>\s*\'%s\'/', $containerClass), file_get_contents($containerFile), 'kernel.container_class is properly set on the dumped container');
5288
}
5389
}

src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
use Symfony\Component\DependencyInjection\ContainerBuilder;
1515
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
16+
use Symfony\Component\DependencyInjection\Extension\Extension;
1617
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
18+
use Symfony\Component\DependencyInjection\Parameterbag\EnvPlaceholderParameterBag;
1719

1820
/**
1921
* Merges extension configs into the container builder.
@@ -43,7 +45,10 @@ public function process(ContainerBuilder $container)
4345
// this extension was not called
4446
continue;
4547
}
46-
$config = $container->getParameterBag()->resolveValue($config);
48+
// EnvPlaceholderParameterBag tracks env vars when calling resolveValue().
49+
// Clone so that tracking is done in a dedicated bag.
50+
$resolvingBag = clone $container->getParameterBag();
51+
$config = $resolvingBag->resolveValue($config);
4752

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

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

66+
if ($resolvingBag instanceof EnvPlaceholderParameterBag) {
67+
// $resolvingBag keeps track of env vars encoutered *before* merging configs
68+
if ($extension instanceof Extension) {
69+
// but we don't want to keep track of env vars that are *overridden* when configs are merged
70+
$resolvingBag = new MergeExtensionConfigurationParameterBag($extension, $resolvingBag);
71+
}
72+
$container->getParameterBag()->mergeEnvPlaceholders($resolvingBag);
73+
}
74+
6175
$container->merge($tmpContainer);
6276
$container->getParameterBag()->add($parameters);
6377
}
@@ -66,3 +80,58 @@ public function process(ContainerBuilder $container)
6680
$container->addAliases($aliases);
6781
}
6882
}
83+
84+
/**
85+
* @internal
86+
*/
87+
class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
88+
{
89+
private $beforeProcessingEnvPlaceholders;
90+
91+
public function __construct(Extension $extension, parent $resolvingBag)
92+
{
93+
$this->beforeProcessingEnvPlaceholders = $resolvingBag->getEnvPlaceholders();
94+
$config = $this->resolveEnvPlaceholders($extension->getProcessedConfigs());
95+
parent::__construct($this->resolveEnvReferences($config));
96+
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function getEnvPlaceholders()
102+
{
103+
// contains the list of env vars that are still used after configs have been merged
104+
$envPlaceholders = parent::getEnvPlaceholders();
105+
106+
foreach ($envPlaceholders as $env => $placeholders) {
107+
if (isset($this->beforeProcessingEnvPlaceholders[$env])) {
108+
// for still-used env vars, keep track of their before-processing placeholders
109+
$envPlaceholders[$env] += $this->beforeProcessingEnvPlaceholders[$env];
110+
}
111+
}
112+
113+
return $envPlaceholders;
114+
}
115+
116+
/**
117+
* Replaces-back env placeholders to their original "%env(FOO)%" version.
118+
*/
119+
private function resolveEnvPlaceholders($value)
120+
{
121+
if (is_array($value)) {
122+
foreach ($value as $k => $v) {
123+
$value[$this->resolveEnvPlaceholders($k)] = $this->resolveEnvPlaceholders($v);
124+
}
125+
} elseif (is_string($value)) {
126+
foreach ($this->beforeProcessingEnvPlaceholders as $env => $placeholders) {
127+
foreach ($placeholders as $placeholder) {
128+
if (false !== stripos($value, $placeholder)) {
129+
$value = str_ireplace($placeholder, "%env($env)%", $value);
130+
}
131+
}
132+
}
133+
}
134+
135+
return $value;
136+
}
137+
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -682,10 +682,9 @@ public function compile(bool $resolveEnvPlaceholders = false)
682682
$bag = $this->getParameterBag();
683683

684684
if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
685-
$bag->resolveEnvReferences();
686-
$this->parameterBag = new ParameterBag($bag->all());
685+
$this->parameterBag = new ParameterBag($bag->resolveEnvReferences($bag->all()));
687686
$this->envPlaceholders = $bag->getEnvPlaceholders();
688-
$this->parameterBag = $bag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
687+
$this->parameterBag = $bag = new ParameterBag($this->resolveEnvPlaceholders($this->parameterBag->all(), true));
689688
}
690689

691690
$compiler->compile($this);

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,6 +1404,9 @@ private function hasReference($id, array $arguments, $deep = false, array &$visi
14041404
private function dumpValue($value, $interpolate = true)
14051405
{
14061406
if (is_array($value)) {
1407+
if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) {
1408+
return $this->dumpValue("%$param%");
1409+
}
14071410
$code = array();
14081411
foreach ($value as $k => $v) {
14091412
$code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));

src/Symfony/Component/DependencyInjection/Exception/ServiceNotFoundException.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class ServiceNotFoundException extends InvalidArgumentException implements NotFo
2222
{
2323
private $id;
2424
private $sourceId;
25+
private $alternatives;
2526

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

4546
$this->id = $id;
4647
$this->sourceId = $sourceId;
48+
$this->alternatives = $alternatives;
4749
}
4850

4951
public function getId()
@@ -55,4 +57,9 @@ public function getSourceId()
5557
{
5658
return $this->sourceId;
5759
}
60+
61+
public function getAlternatives()
62+
{
63+
return $this->alternatives;
64+
}
5865
}

0 commit comments

Comments
 (0)