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 <fabien@symfony.com>
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 <mp@webfactory.de>
22+
* @author Benjamin Klotz <bk@webfactory.de>
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 <fabien@symfony.com>
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
/**

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy