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

Skip to content

Commit d60428c

Browse files
committed
feature #15738 Implement service-based Resource (cache) validation (mpdude)
This PR was squashed before being merged into the 2.8 branch (closes #15738). Discussion ---------- Implement service-based Resource (cache) validation | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #7230, #15692, #7782 | License | MIT | Doc PR | symfony/symfony-docs#5136 ### Overview Currently, any metadata passed to `ConfigCache` (namely implementations of `ResourceInterface`) is serialized to disk. When the `ConfigCache` is validated, the metadata is unserialized and queried through `ResourceInterface::isFresh()` to determine whether the cache is fresh. That way, `ResourceInterface` implementations cannot interact with services, for example a database connection. This PR introduces the new concept of `ResourceCheckers`. Services implementing `ResourceCheckerInterface` can be tagged as `config_cache.resource_checker` with an optional priority. Clients that wish to use `ConfigCache` can then obtain an instance from the `config_cache_factory` service (which implements `ConfigCacheFactoryInterface`). The factory will take care of injecting resource checkers into the `ConfigCache` instance so that they can be used for cache validation. Checking cache metadata is easy for `ResourceCheckers`: * First, the `ResourceCheckerInterface::supports()` implementation is passed the metadata object in question. If the checker cannot handle the type of resource passed, `supports()` should return `false`. * Otherwise, the `ResourceCheckerInterface::isFresh()` method will be called and given the resource as well as the timestamp at which the cache was initialized. If that method returns `false`, the cache is considered stale. If it returns `true`, the resource is considered unchanged and will *not* be passed to any additional checkers. ### BC and migration path This PR does not (intend to) break BC but it comes with deprecations. The main reason is that `ResourceInterface` contains an `isFresh()` method that does not make sense in the general case of resources. Thus, `ResourceInterface::isFresh()` is marked as deprecated and should be removed in Symfony 3.0. Resource implementations that can (or wish to) be validated in that simple manner can implement the `SelfCheckingResourceInterface` sub-interface that still contains (and will keep) the `isFresh()` method. The change should be as simple as changing the `extends` list. Apart from that, `ResourceInterface` will be kept as the base interface for resource implementations. It is used in several `@api` interfaces and thus cannot easily be substituted. For the Symfony 2.x series, a `BCResourceInterfaceChecker` will be kept that performs validation through `ResourceInterface::isFresh()` but will trigger a deprecation warning. The remedy is to either implement a custom ResourceChecker with a priority higher than -1000; or to switch to the aforementioned `SelfCheckingResourceInterface` which is used at a priority of -990 (without deprecation warning). The `ConfigCache` and `ConfigCacheFactory` classes can be used as previously but do not feature checker-based cache validation. ### Outlook and closing remarks: This PR supersedes #7230, #15692 and works at least in parts towards the goal of #7176. The `ResourceCheckerInterface`, `...ConfigCache` and `...ConfigCacheFactory` no longer need to be aware of the `debug` flag. The different validation rules applied previously are now just a matter of `ResourceChecker` configuration (i. e. "no checkers" in `prod`). It might be possible to remove the `debug` flag from Symfony's `Router` and/or `Translator` classes in the future as well because it was only passed on to the `ConfigCache` there. Commits ------- 20d3722 Implement service-based Resource (cache) validation
2 parents 995cf4e + 20d3722 commit d60428c

32 files changed

+766
-206
lines changed

UPGRADE-2.8.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,34 @@ Security
442442
}
443443
}
444444
```
445+
446+
Config
447+
------
448+
449+
* The `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` method has been
450+
deprecated and will be removed in Symfony 3.0 because it assumes that resource
451+
implementations are able to check themselves for freshness.
452+
453+
If you have custom resources that implement this method, change them to implement the
454+
`\Symfony\Component\Config\Resource\SelfCheckingResourceInterface` sub-interface instead
455+
of `\Symfony\Component\Config\Resource\ResourceInterface`.
456+
457+
Before:
458+
459+
```php
460+
use Symfony\Component\Config\Resource\ResourceInterface;
461+
462+
class MyCustomResource implements ResourceInterface { ... }
463+
```
464+
465+
After:
466+
467+
```php
468+
use Symfony\Component\Config\Resource\SelfCheckingResourceInterface;
469+
470+
class MyCustomResource implements SelfCheckingResourceInterface { ... }
471+
```
472+
473+
Additionally, if you have implemented cache validation strategies *using* `isFresh()`
474+
yourself, you should have a look at the new cache validation system based on
475+
`ResourceChecker`s.

UPGRADE-3.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,3 +1201,10 @@ UPGRADE FROM 2.x to 3.0
12011201
* `Process::setStdin()` and `Process::getStdin()` have been removed. Use
12021202
`Process::setInput()` and `Process::getInput()` that works the same way.
12031203
* `Process::setInput()` and `ProcessBuilder::setInput()` do not accept non-scalar types.
1204+
1205+
### Config
1206+
1207+
* `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also,
1208+
cache validation through this method (which was still supported in 2.8 for BC) does no longer
1209+
work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class
1210+
has been removed as well.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
/**
19+
* Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority.
20+
*
21+
* @author Matthias Pigulla <[email protected]>
22+
* @author Benjamin Klotz <[email protected]>
23+
*/
24+
class ConfigCachePass implements CompilerPassInterface
25+
{
26+
public function process(ContainerBuilder $container)
27+
{
28+
$resourceCheckers = array();
29+
30+
foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $tags) {
31+
$priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0;
32+
$resourceCheckers[$priority][] = new Reference($id);
33+
}
34+
35+
if (empty($resourceCheckers)) {
36+
return;
37+
}
38+
39+
// sort by priority and flatten
40+
krsort($resourceCheckers);
41+
$resourceCheckers = call_user_func_array('array_merge', $resourceCheckers);
42+
43+
$container->getDefinition('config_cache_factory')->replaceArgument(0, $resourceCheckers);
44+
}
45+
}

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass;
2929
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass;
3030
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass;
31+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass;
3132
use Symfony\Component\Debug\ErrorHandler;
3233
use Symfony\Component\DependencyInjection\ContainerBuilder;
3334
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@@ -92,6 +93,7 @@ public function build(ContainerBuilder $container)
9293
if ($container->getParameter('kernel.debug')) {
9394
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
9495
$container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING);
96+
$container->addCompilerPass(new ConfigCachePass());
9597
}
9698
}
9799
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
</argument>
7676
<argument type="service" id="router.request_context" on-invalid="ignore" />
7777
<argument type="service" id="logger" on-invalid="ignore" />
78+
<call method="setConfigCacheFactory">
79+
<argument type="service" id="config_cache_factory" />
80+
</call>
7881
</service>
7982

8083
<service id="router" alias="router.default" />

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,21 @@
6363
<service id="uri_signer" class="%uri_signer.class%">
6464
<argument>%kernel.secret%</argument>
6565
</service>
66+
67+
<service id="config_cache_factory" class="Symfony\Component\Config\ResourceCheckerConfigCacheFactory">
68+
<argument type="collection"></argument>
69+
</service>
70+
71+
<service class="Symfony\Component\Config\Resource\SelfCheckingResourceChecker" public="false">
72+
<tag name="config_cache.resource_checker" priority="-990" />
73+
</service>
74+
75+
<!--
76+
This service is deprecated and will be removed in 3.0.
77+
-->
78+
<service class="Symfony\Component\Config\Resource\BCResourceInterfaceChecker" public="false">
79+
<tag name="config_cache.resource_checker" priority="-1000" />
80+
</service>
81+
6682
</services>
6783
</container>

src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
<argument key="debug">%kernel.debug%</argument>
4646
</argument>
4747
<argument type="collection" /> <!-- translation resources -->
48+
<call method="setConfigCacheFactory">
49+
<argument type="service" id="config_cache_factory" />
50+
</call>
4851
</service>
4952

5053
<service id="translator.logging" class="Symfony\Component\Translation\LoggingTranslator" public="false">

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Symfony\Bundle\FrameworkBundle\Console\Application;
66
use Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture\TestAppKernel;
77
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
8-
use Symfony\Component\Config\ConfigCache;
8+
use Symfony\Component\Config\ConfigCacheFactory;
99
use Symfony\Component\Config\Resource\ResourceInterface;
1010
use Symfony\Component\Console\Input\ArrayInput;
1111
use Symfony\Component\Console\Output\NullOutput;
@@ -47,15 +47,13 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup()
4747
$metaFiles = $finder->files()->in($this->kernel->getCacheDir())->name('*.php.meta');
4848
// simply check that cache is warmed up
4949
$this->assertGreaterThanOrEqual(1, count($metaFiles));
50+
$configCacheFactory = new ConfigCacheFactory(true);
51+
$that = $this;
52+
5053
foreach ($metaFiles as $file) {
51-
$configCache = new ConfigCache(substr($file, 0, -5), true);
52-
$this->assertTrue(
53-
$configCache->isFresh(),
54-
sprintf(
55-
'Meta file "%s" is not fresh',
56-
(string) $file
57-
)
58-
);
54+
$configCacheFactory->cache(substr($file, 0, -5), function () use ($that, $file) {
55+
$that->fail(sprintf('Meta file "%s" is not fresh', (string) $file));
56+
});
5957
}
6058

6159
// check that app kernel file present in meta file of container's cache
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Reference;
15+
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass;
16+
17+
class ConfigCachePassTest extends \PHPUnit_Framework_TestCase
18+
{
19+
public function testThatCheckersAreProcessedInPriorityOrder()
20+
{
21+
$services = array(
22+
'checker_2' => array(0 => array('priority' => 100)),
23+
'checker_1' => array(0 => array('priority' => 200)),
24+
'checker_3' => array(),
25+
);
26+
27+
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
28+
$container = $this->getMock(
29+
'Symfony\Component\DependencyInjection\ContainerBuilder',
30+
array('findTaggedServiceIds', 'getDefinition', 'hasDefinition')
31+
);
32+
33+
$container->expects($this->atLeastOnce())
34+
->method('findTaggedServiceIds')
35+
->will($this->returnValue($services));
36+
$container->expects($this->atLeastOnce())
37+
->method('getDefinition')
38+
->with('config_cache_factory')
39+
->will($this->returnValue($definition));
40+
41+
$definition->expects($this->once())
42+
->method('replaceArgument')
43+
->with(0, array(
44+
new Reference('checker_1'),
45+
new Reference('checker_2'),
46+
new Reference('checker_3'),
47+
));
48+
49+
$pass = new ConfigCachePass();
50+
$pass->process($container);
51+
}
52+
53+
public function testThatCheckersCanBeMissing()
54+
{
55+
$definition = $this->getMock('Symfony\Component\DependencyInjection\Definition');
56+
$container = $this->getMock(
57+
'Symfony\Component\DependencyInjection\ContainerBuilder',
58+
array('findTaggedServiceIds')
59+
);
60+
61+
$container->expects($this->atLeastOnce())
62+
->method('findTaggedServiceIds')
63+
->will($this->returnValue(array()));
64+
65+
$pass = new ConfigCachePass();
66+
$pass->process($container);
67+
}
68+
}

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,15 @@ public function testTranslator()
250250
);
251251

252252
$calls = $container->getDefinition('translator.default')->getMethodCalls();
253-
$this->assertEquals(array('fr'), $calls[0][1][0]);
253+
$this->assertEquals(array('fr'), $calls[1][1][0]);
254254
}
255255

256256
public function testTranslatorMultipleFallbacks()
257257
{
258258
$container = $this->createContainerFromFile('translator_fallbacks');
259259

260260
$calls = $container->getDefinition('translator.default')->getMethodCalls();
261-
$this->assertEquals(array('en', 'fr'), $calls[0][1][0]);
261+
$this->assertEquals(array('en', 'fr'), $calls[1][1][0]);
262262
}
263263

264264
/**

src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -104,34 +104,6 @@ public function testTransWithCachingWithInvalidLocale()
104104
$translator->trans('foo');
105105
}
106106

107-
public function testLoadResourcesWithCaching()
108-
{
109-
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
110-
$resourceFiles = array(
111-
'fr' => array(
112-
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
113-
),
114-
);
115-
116-
// prime the cache
117-
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml');
118-
$translator->setLocale('fr');
119-
120-
$this->assertEquals('répertoire', $translator->trans('folder'));
121-
122-
// do it another time as the cache is primed now
123-
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'yml');
124-
$translator->setLocale('fr');
125-
126-
$this->assertEquals('répertoire', $translator->trans('folder'));
127-
128-
// refresh cache when resources is changed in debug mode.
129-
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), 'yml');
130-
$translator->setLocale('fr');
131-
132-
$this->assertEquals('folder', $translator->trans('folder'));
133-
}
134-
135107
public function testLoadResourcesWithoutCaching()
136108
{
137109
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"php": ">=5.3.9",
2020
"symfony/asset": "~2.7|~3.0.0",
2121
"symfony/dependency-injection": "~2.8",
22-
"symfony/config": "~2.4",
22+
"symfony/config": "~2.8",
2323
"symfony/event-dispatcher": "~2.8|~3.0.0",
2424
"symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4|~3.0.0",
2525
"symfony/http-kernel": "~2.8",

src/Symfony/Component/Config/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Before: `InvalidArgumentException` (variable must contain at least two
2020
distinct elements).
2121
After: the code will work as expected and it will restrict the values of the
2222
`variable` option to just `value`.
23+
24+
* deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they
25+
can be validated that way, make them implement the new `SelfCheckingResourceInterface`.
2326

2427
2.7.0
2528
-----

0 commit comments

Comments
 (0)