From 535131db39669c200d5b9ba2b54e4bef2b5f8ad2 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 9 Sep 2015 13:50:25 +0200 Subject: [PATCH 1/6] Implement service-based Resource (cache) validation 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 caceh 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, support() 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 valid 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 shall 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. Note that ResourceInterface 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. --- .../Compiler/ConfigCachePass.php | 40 +++++ .../FrameworkBundle/FrameworkBundle.php | 2 + .../Resources/config/routing.xml | 3 + .../Resources/config/services.xml | 17 +++ .../Resources/config/translation.xml | 3 + .../CacheClearCommandTest.php | 16 +- .../Compiler/ConfigCachePassTest.php | 54 +++++++ .../FrameworkExtensionTest.php | 4 +- .../Tests/Translation/TranslatorTest.php | 28 ---- .../Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/Config/CHANGELOG.md | 3 + src/Symfony/Component/Config/ConfigCache.php | 101 +++---------- .../Component/Config/ConfigCacheFactory.php | 7 +- .../Resource/BCResourceInterfaceChecker.php | 36 +++++ .../Resource/DefaultResourceChecker.php | 36 +++++ .../Config/Resource/DirectoryResource.php | 2 +- .../Config/Resource/FileExistenceResource.php | 2 +- .../Config/Resource/FileResource.php | 2 +- .../Config/Resource/ResourceInterface.php | 11 +- .../SelfCheckingResourceInterface.php | 30 ++++ .../Config/ResourceCheckerConfigCache.php | 140 ++++++++++++++++++ .../ResourceCheckerConfigCacheFactory.php | 51 +++++++ .../Config/ResourceCheckerInterface.php | 49 ++++++ .../Config/Tests/ConfigCacheTest.php | 106 +++++-------- .../Config/Tests/Resource/ResourceStub.php | 29 ++++ .../Tests/ResourceCheckerConfigCacheTest.php | 118 +++++++++++++++ .../Config/EnvParametersResource.php | 4 +- .../Component/HttpKernel/composer.json | 2 +- .../Translation/Tests/TranslatorCacheTest.php | 4 +- 29 files changed, 698 insertions(+), 204 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php create mode 100644 src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php create mode 100644 src/Symfony/Component/Config/Resource/DefaultResourceChecker.php create mode 100644 src/Symfony/Component/Config/Resource/SelfCheckingResourceInterface.php create mode 100644 src/Symfony/Component/Config/ResourceCheckerConfigCache.php create mode 100644 src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php create mode 100644 src/Symfony/Component/Config/ResourceCheckerInterface.php create mode 100644 src/Symfony/Component/Config/Tests/Resource/ResourceStub.php create mode 100644 src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php new file mode 100644 index 0000000000000..ce36b1b2618ca --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds services tagged config_cache.resource_checker to the config_cache_factory service, ordering them by priority. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +class ConfigCachePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $resourceCheckers = array(); + foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $resourceCheckers[$priority][] = new Reference($id); + } + + // sort by priority and flatten + krsort($resourceCheckers); + $resourceCheckers = call_user_func_array('array_merge', $resourceCheckers); + + $container->getDefinition('config_cache_factory')->addMethodCall('setResourceCheckers', array($resourceCheckers)); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 499f39c6c95c0..7b1c77f225e57 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -28,6 +28,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -92,6 +93,7 @@ public function build(ContainerBuilder $container) if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new CompilerDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); + $container->addCompilerPass(new ConfigCachePass()); } } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml index df9e24abab883..a750027406ec1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.xml @@ -75,6 +75,9 @@ + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 2021505726fef..1c2a2fe673961 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -13,6 +13,9 @@ Symfony\Component\HttpKernel\Config\FileLocator Symfony\Component\HttpKernel\UriSigner Symfony\Component\HttpFoundation\RequestStack + Symfony\Component\Config\ResourceCheckerConfigCacheFactory + Symfony\Component\Config\Resource\DefaultResourceChecker + Symfony\Component\Config\Resource\BCResourceInterfaceChecker @@ -63,5 +66,19 @@ %kernel.secret% + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 0007a360c6e46..ff4c18f212f2d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -45,6 +45,9 @@ %kernel.debug% + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php index 745e015d57d68..0cfc92241b82f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/CacheClearCommand/CacheClearCommandTest.php @@ -5,7 +5,7 @@ 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\ConfigCache; +use Symfony\Component\Config\ConfigCacheFactory; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\NullOutput; @@ -47,15 +47,13 @@ public function testCacheIsFreshAfterCacheClearedWithWarmup() $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); + $that = $this; + foreach ($metaFiles as $file) { - $configCache = new ConfigCache(substr($file, 0, -5), true); - $this->assertTrue( - $configCache->isFresh(), - sprintf( - 'Meta file "%s" is not fresh', - (string) $file - ) - ); + $configCacheFactory->cache(substr($file, 0, -5), function () use ($that, $file) { + $that->fail(sprintf('Meta file "%s" is not fresh', (string) $file)); + }); } // check that app kernel file present in meta file of container's cache diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php new file mode 100644 index 0000000000000..9628ced6b9683 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; + +class ConfigCachePassTest extends \PHPUnit_Framework_TestCase +{ + public function testThatCheckersAreProcessedInPriorityOrder() + { + $services = array( + 'checker_2' => array(0 => array('priority' => 100)), + 'checker_1' => array(0 => array('priority' => 200)), + 'checker_3' => array(), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds', 'getDefinition', 'hasDefinition') + ); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + $container->expects($this->atLeastOnce()) + ->method('getDefinition') + ->with('config_cache_factory') + ->will($this->returnValue($definition)); + + $definition->expects($this->once()) + ->method('addMethodCall') + ->with('setResourceCheckers', array( + array( + new Reference('checker_1'), + new Reference('checker_2'), + new Reference('checker_3'), + ), + )); + + $pass = new ConfigCachePass(); + $pass->process($container); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 6e220740a5107..ad0c427100ddf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -250,7 +250,7 @@ public function testTranslator() ); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('fr'), $calls[0][1][0]); + $this->assertEquals(array('fr'), $calls[1][1][0]); } public function testTranslatorMultipleFallbacks() @@ -258,7 +258,7 @@ public function testTranslatorMultipleFallbacks() $container = $this->createContainerFromFile('translator_fallbacks'); $calls = $container->getDefinition('translator.default')->getMethodCalls(); - $this->assertEquals(array('en', 'fr'), $calls[0][1][0]); + $this->assertEquals(array('en', 'fr'), $calls[1][1][0]); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php index c7b61cb88170e..5bc45682cfd2b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Translation/TranslatorTest.php @@ -104,34 +104,6 @@ public function testTransWithCachingWithInvalidLocale() $translator->trans('foo'); } - public function testLoadResourcesWithCaching() - { - $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); - $resourceFiles = array( - 'fr' => array( - __DIR__.'/../Fixtures/Resources/translations/messages.fr.yml', - ), - ); - - // prime the cache - $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml'); - $translator->setLocale('fr'); - - $this->assertEquals('répertoire', $translator->trans('folder')); - - // do it another time as the cache is primed now - $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'yml'); - $translator->setLocale('fr'); - - $this->assertEquals('répertoire', $translator->trans('folder')); - - // refresh cache when resources is changed in debug mode. - $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'debug' => true), 'yml'); - $translator->setLocale('fr'); - - $this->assertEquals('folder', $translator->trans('folder')); - } - public function testLoadResourcesWithoutCaching() { $loader = new \Symfony\Component\Translation\Loader\YamlFileLoader(); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index d191b010e72cd..496351170b83a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=5.3.9", "symfony/asset": "~2.7|~3.0.0", "symfony/dependency-injection": "~2.8", - "symfony/config": "~2.4", + "symfony/config": "~2.8", "symfony/event-dispatcher": "~2.8|~3.0.0", "symfony/http-foundation": "~2.4.9|~2.5,>=2.5.4|~3.0.0", "symfony/http-kernel": "~2.8", diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index 0c71e2cc1eb0a..3bc22a57bda55 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -20,6 +20,9 @@ Before: `InvalidArgumentException` (variable must contain at least two distinct elements). After: the code will work as expected and it will restrict the values of the `variable` option to just `value`. + + * deprecated the `ResourceInterface::isFresh()` method. If you implement custom resource types and they + can be validated that way, make them implement the new `SelfCheckingResourceInterface`. 2.7.0 ----- diff --git a/src/Symfony/Component/Config/ConfigCache.php b/src/Symfony/Component/Config/ConfigCache.php index cc99bc9211cde..fe4b23d30e8bd 100644 --- a/src/Symfony/Component/Config/ConfigCache.php +++ b/src/Symfony/Component/Config/ConfigCache.php @@ -11,22 +11,27 @@ namespace Symfony\Component\Config; -use Symfony\Component\Config\Resource\ResourceInterface; -use Symfony\Component\Filesystem\Exception\IOException; -use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Config\Resource\BCResourceInterfaceChecker; +use Symfony\Component\Config\Resource\DefaultResourceChecker; /** - * ConfigCache manages PHP cache files. + * ConfigCache caches arbitrary content in files on disk. * - * When debug is enabled, it knows when to flush the cache - * thanks to an array of ResourceInterface instances. + * When in debug mode, those metadata resources that implement + * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will + * be used to check cache freshness. + * + * During a transition period, also instances of + * \Symfony\Component\Config\Resource\ResourceInterface will be checked + * by means of the isFresh() method. This behaviour is deprecated since 2.8 + * and will be removed in 3.0. * * @author Fabien Potencier + * @author Matthias Pigulla */ -class ConfigCache implements ConfigCacheInterface +class ConfigCache extends ResourceCheckerConfigCache { private $debug; - private $file; /** * @param string $file The absolute cache path @@ -34,7 +39,10 @@ class ConfigCache implements ConfigCacheInterface */ public function __construct($file, $debug) { - $this->file = $file; + parent::__construct($file, array( + new DefaultResourceChecker(), + new BCResourceInterfaceChecker(), + )); $this->debug = (bool) $debug; } @@ -49,90 +57,23 @@ public function __toString() { @trigger_error('ConfigCache::__toString() is deprecated since version 2.7 and will be removed in 3.0. Use the getPath() method instead.', E_USER_DEPRECATED); - return $this->file; - } - - /** - * Gets the cache file path. - * - * @return string The cache file path - */ - public function getPath() - { - return $this->file; + return $this->getPath(); } /** * Checks if the cache is still fresh. * - * This method always returns true when debug is off and the + * This implementation always returns true when debug is off and the * cache file exists. * * @return bool true if the cache is fresh, false otherwise */ public function isFresh() { - if (!is_file($this->file)) { - return false; - } - - if (!$this->debug) { + if (!$this->debug && is_file($this->getPath())) { return true; } - $metadata = $this->getMetaFile(); - if (!is_file($metadata)) { - return false; - } - - $time = filemtime($this->file); - $meta = unserialize(file_get_contents($metadata)); - foreach ($meta as $resource) { - if (!$resource->isFresh($time)) { - return false; - } - } - - return true; - } - - /** - * Writes cache. - * - * @param string $content The content to write in the cache - * @param ResourceInterface[] $metadata An array of ResourceInterface instances - * - * @throws \RuntimeException When cache file can't be written - */ - public function write($content, array $metadata = null) - { - $mode = 0666; - $umask = umask(); - $filesystem = new Filesystem(); - $filesystem->dumpFile($this->file, $content, null); - try { - $filesystem->chmod($this->file, $mode, $umask); - } catch (IOException $e) { - // discard chmod failure (some filesystem may not support it) - } - - if (null !== $metadata && true === $this->debug) { - $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null); - try { - $filesystem->chmod($this->getMetaFile(), $mode, $umask); - } catch (IOException $e) { - // discard chmod failure (some filesystem may not support it) - } - } - } - - /** - * Gets the meta file path. - * - * @return string The meta file path - */ - private function getMetaFile() - { - return $this->file.'.meta'; + return parent::isFresh(); } } diff --git a/src/Symfony/Component/Config/ConfigCacheFactory.php b/src/Symfony/Component/Config/ConfigCacheFactory.php index 5a8f4562388b0..396536e2d8ed8 100644 --- a/src/Symfony/Component/Config/ConfigCacheFactory.php +++ b/src/Symfony/Component/Config/ConfigCacheFactory.php @@ -12,8 +12,11 @@ namespace Symfony\Component\Config; /** - * Basic implementation for ConfigCacheFactoryInterface - * that will simply create an instance of ConfigCache. + * Basic implementation of ConfigCacheFactoryInterface that + * creates an instance of the default ConfigCache. + * + * This factory and/or cache do not support cache validation + * by means of ResourceChecker instances (that is, service-based). * * @author Matthias Pigulla */ diff --git a/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php new file mode 100644 index 0000000000000..8ed26202a2809 --- /dev/null +++ b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * Resource checker for the ResourceInterface. Exists for BC. + * + * @author Matthias Pigulla + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class BCResourceInterfaceChecker extends DefaultResourceChecker +{ + public function supports(ResourceInterface $metadata) + { + /* As all resources must be instanceof ResourceInterface, + we support them all. */ + return true; + } + + public function isFresh(ResourceInterface $metadata, $timestamp) + { + trigger_error('Resource checking through ResourceInterface::isFresh() is deprecated since 2.8 and will be removed in 3.0', E_USER_DEPRECATED); + + return parent::isFresh($metadata, $timestamp); // For now, $metadata features the isFresh() method, so off we go (quack quack) + } +} diff --git a/src/Symfony/Component/Config/Resource/DefaultResourceChecker.php b/src/Symfony/Component/Config/Resource/DefaultResourceChecker.php new file mode 100644 index 0000000000000..efffacabbf5a4 --- /dev/null +++ b/src/Symfony/Component/Config/Resource/DefaultResourceChecker.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Config\ResourceCheckerInterface; + +/** + * Resource checker for instances of + * SelfCheckingResourceInterface. As these resources can + * perform the check themselves, we can support them in a generic + * way. + * + * @author Matthias Pigulla + */ +class DefaultResourceChecker implements ResourceCheckerInterface +{ + public function supports(ResourceInterface $metadata) + { + return $metadata instanceof SelfCheckingResourceInterface; + } + + public function isFresh(ResourceInterface $metadata, $timestamp) + { + /* @var SelfCheckingResourceInterface $metadata */ + return $metadata->isFresh($timestamp); + } +} diff --git a/src/Symfony/Component/Config/Resource/DirectoryResource.php b/src/Symfony/Component/Config/Resource/DirectoryResource.php index 515fb5c42d6a9..1921e5d377787 100644 --- a/src/Symfony/Component/Config/Resource/DirectoryResource.php +++ b/src/Symfony/Component/Config/Resource/DirectoryResource.php @@ -16,7 +16,7 @@ * * @author Fabien Potencier */ -class DirectoryResource implements ResourceInterface, \Serializable +class DirectoryResource implements SelfCheckingResourceInterface, \Serializable { private $resource; private $pattern; diff --git a/src/Symfony/Component/Config/Resource/FileExistenceResource.php b/src/Symfony/Component/Config/Resource/FileExistenceResource.php index 4e68e8ac105be..ba1584638186b 100644 --- a/src/Symfony/Component/Config/Resource/FileExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/FileExistenceResource.php @@ -19,7 +19,7 @@ * * @author Charles-Henri Bruyand */ -class FileExistenceResource implements ResourceInterface, \Serializable +class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable { private $resource; diff --git a/src/Symfony/Component/Config/Resource/FileResource.php b/src/Symfony/Component/Config/Resource/FileResource.php index 4c00ae4140abc..bd0ce03eafe80 100644 --- a/src/Symfony/Component/Config/Resource/FileResource.php +++ b/src/Symfony/Component/Config/Resource/FileResource.php @@ -18,7 +18,7 @@ * * @author Fabien Potencier */ -class FileResource implements ResourceInterface, \Serializable +class FileResource implements SelfCheckingResourceInterface, \Serializable { /** * @var string|false diff --git a/src/Symfony/Component/Config/Resource/ResourceInterface.php b/src/Symfony/Component/Config/Resource/ResourceInterface.php index db03d127a401e..c0f0e50e0688e 100644 --- a/src/Symfony/Component/Config/Resource/ResourceInterface.php +++ b/src/Symfony/Component/Config/Resource/ResourceInterface.php @@ -21,7 +21,13 @@ interface ResourceInterface /** * Returns a string representation of the Resource. * - * @return string A string representation of the Resource + * This method is necessary to allow for resource de-duplication, for example by means + * of array_unique(). The string returned need not have a particular meaning, but has + * to be identical for different ResourceInterface instances referring to the same + * resource; and it should be unlikely to collide with that of other, unrelated + * resource instances. + * + * @return string A string representation unique to the underlying Resource */ public function __toString(); @@ -31,6 +37,9 @@ public function __toString(); * @param int $timestamp The last time the resource was loaded * * @return bool True if the resource has not been updated, false otherwise + * + * @deprecated since 2.8, to be removed in 3.0. If your resource can check itself for + * freshness implement the SelfCheckingResourceInterface instead. */ public function isFresh($timestamp); diff --git a/src/Symfony/Component/Config/Resource/SelfCheckingResourceInterface.php b/src/Symfony/Component/Config/Resource/SelfCheckingResourceInterface.php new file mode 100644 index 0000000000000..b3260f2be3e58 --- /dev/null +++ b/src/Symfony/Component/Config/Resource/SelfCheckingResourceInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * Interface for Resources that can check for freshness autonomously, + * without special support from external services. + * + * @author Matthias Pigulla + */ +interface SelfCheckingResourceInterface extends ResourceInterface +{ + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + * + * @return bool True if the resource has not been updated, false otherwise + */ + public function isFresh($timestamp); +} diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCache.php b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php new file mode 100644 index 0000000000000..3cef7819071b2 --- /dev/null +++ b/src/Symfony/Component/Config/ResourceCheckerConfigCache.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface + * to check whether cached data is still fresh. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCache implements ConfigCacheInterface +{ + /** + * @var string + */ + private $file; + + /** + * @var ResourceCheckerInterface[] + */ + private $resourceCheckers; + + /** + * @param string $file The absolute cache path + * @param ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check + */ + public function __construct($file, array $resourceCheckers = array()) + { + $this->file = $file; + $this->resourceCheckers = $resourceCheckers; + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->file; + } + + /** + * Checks if the cache is still fresh. + * + * This implementation will make a decision solely based on the ResourceCheckers + * passed in the constructor. + * + * The first ResourceChecker that supports a given resource is considered authoritative. + * Resources with no matching ResourceChecker will silently be ignored and considered fresh. + * + * @return bool true if the cache is fresh, false otherwise + */ + public function isFresh() + { + if (!is_file($this->file)) { + return false; + } + + if (!$this->resourceCheckers) { + return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all + } + + $metadata = $this->getMetaFile(); + if (!is_file($metadata)) { + return true; + } + + $time = filemtime($this->file); + $meta = unserialize(file_get_contents($metadata)); + + foreach ($meta as $resource) { + /* @var ResourceInterface $resource */ + foreach ($this->resourceCheckers as $checker) { + if (!$checker->supports($resource)) { + continue; // next checker + } + if ($checker->isFresh($resource, $time)) { + break; // no need to further check this resource + } + + return false; // cache is stale + } + // no suitable checker found, ignore this resource + } + + return true; + } + + /** + * Writes cache. + * + * @param string $content The content to write in the cache + * @param ResourceInterface[] $metadata An array of metadata + * + * @throws \RuntimeException When cache file can't be written + */ + public function write($content, array $metadata = null) + { + $mode = 0666; + $umask = umask(); + $filesystem = new Filesystem(); + $filesystem->dumpFile($this->file, $content, null); + try { + $filesystem->chmod($this->file, $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + + if (null !== $metadata) { + $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null); + try { + $filesystem->chmod($this->getMetaFile(), $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + } + } + + /** + * Gets the meta file path. + * + * @return string The meta file path + */ + private function getMetaFile() + { + return $this->file.'.meta'; + } +} diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php b/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php new file mode 100644 index 0000000000000..c9fd7c1bec13b --- /dev/null +++ b/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * A ConfigCacheFactory implementation that validates the + * cache with an arbitrary set of ResourceCheckers. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface +{ + /** + * @var ResourceCheckerInterface[] + */ + private $resourceCheckers = array(); + + /** + * @param ResourceCheckerInterface[] $resourceCheckers + */ + public function setResourceCheckers(array $resourceCheckers) + { + $this->resourceCheckers = $resourceCheckers; + } + + /** + * {@inheritdoc} + */ + public function cache($file, $callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', gettype($callback))); + } + + $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers); + if (!$cache->isFresh()) { + call_user_func($callback, $cache); + } + + return $cache; + } +} diff --git a/src/Symfony/Component/Config/ResourceCheckerInterface.php b/src/Symfony/Component/Config/ResourceCheckerInterface.php new file mode 100644 index 0000000000000..5bffc437c92a6 --- /dev/null +++ b/src/Symfony/Component/Config/ResourceCheckerInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ResourceCheckers. + * + * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated + * metadata resources are passed to ResourceCheckers. The ResourceCheckers + * can then inspect the resources and decide whether the cache can be considered + * fresh or not. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +interface ResourceCheckerInterface +{ + /** + * Queries the ResourceChecker whether it can validate a given + * resource or not. + * + * @param ResourceInterface $metadata The resource to be checked for freshness + * + * @return bool True if the ResourceChecker can handle this resource type, false if not + */ + public function supports(ResourceInterface $metadata); + + /** + * Validates the resource. + * + * @param object $metadata The resource to be validated. + * @param int $timestamp The timestamp at which the cache associated with this resource was created. + * + * @return bool True if the resource has not changed since the given timestamp, false otherwise. + */ + public function isFresh(ResourceInterface $metadata, $timestamp); + +} diff --git a/src/Symfony/Component/Config/Tests/ConfigCacheTest.php b/src/Symfony/Component/Config/Tests/ConfigCacheTest.php index f3f2a446a2bf6..faa37f0e9db0d 100644 --- a/src/Symfony/Component/Config/Tests/ConfigCacheTest.php +++ b/src/Symfony/Component/Config/Tests/ConfigCacheTest.php @@ -12,29 +12,20 @@ namespace Symfony\Component\Config\Tests; use Symfony\Component\Config\ConfigCache; -use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Tests\Resource\ResourceStub; class ConfigCacheTest extends \PHPUnit_Framework_TestCase { - private $resourceFile = null; - private $cacheFile = null; - private $metaFile = null; - protected function setUp() { - $this->resourceFile = tempnam(sys_get_temp_dir(), '_resource'); $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); - $this->metaFile = $this->cacheFile.'.meta'; - - $this->makeCacheFresh(); - $this->generateMetaFile(); } protected function tearDown() { - $files = array($this->cacheFile, $this->metaFile, $this->resourceFile); + $files = array($this->cacheFile, $this->cacheFile.'.meta'); foreach ($files as $file) { if (file_exists($file)) { @@ -43,96 +34,65 @@ protected function tearDown() } } - public function testGetPath() + /** + * @dataProvider debugModes + */ + public function testCacheIsNotValidIfNothingHasBeenCached($debug) { - $cache = new ConfigCache($this->cacheFile, true); - - $this->assertSame($this->cacheFile, $cache->getPath()); - } - - public function testCacheIsNotFreshIfFileDoesNotExist() - { - unlink($this->cacheFile); - - $cache = new ConfigCache($this->cacheFile, false); + unlink($this->cacheFile); // remove tempnam() side effect + $cache = new ConfigCache($this->cacheFile, $debug); $this->assertFalse($cache->isFresh()); } - public function testCacheIsAlwaysFreshIfFileExistsWithDebugDisabled() + public function testIsAlwaysFreshInProduction() { - $this->makeCacheStale(); + $staleResource = new ResourceStub(); + $staleResource->setFresh(false); $cache = new ConfigCache($this->cacheFile, false); + $cache->write('', array($staleResource)); $this->assertTrue($cache->isFresh()); } - public function testCacheIsNotFreshWithoutMetaFile() + /** + * @dataProvider debugModes + */ + public function testIsFreshWhenNoResourceProvided($debug) { - unlink($this->metaFile); - - $cache = new ConfigCache($this->cacheFile, true); - - $this->assertFalse($cache->isFresh()); - } - - public function testCacheIsFreshIfResourceIsFresh() - { - $cache = new ConfigCache($this->cacheFile, true); - + $cache = new ConfigCache($this->cacheFile, $debug); + $cache->write('', array()); $this->assertTrue($cache->isFresh()); } - public function testCacheIsNotFreshIfOneOfTheResourcesIsNotFresh() + public function testFreshResourceInDebug() { - $this->makeCacheStale(); + $freshResource = new ResourceStub(); + $freshResource->setFresh(true); $cache = new ConfigCache($this->cacheFile, true); + $cache->write('', array($freshResource)); - $this->assertFalse($cache->isFresh()); - } - - public function testWriteDumpsFile() - { - unlink($this->cacheFile); - unlink($this->metaFile); - - $cache = new ConfigCache($this->cacheFile, false); - $cache->write('FOOBAR'); - - $this->assertFileExists($this->cacheFile, 'Cache file is created'); - $this->assertSame('FOOBAR', file_get_contents($this->cacheFile)); - $this->assertFileNotExists($this->metaFile, 'Meta file is not created'); + $this->assertTrue($cache->isFresh()); } - public function testWriteDumpsMetaFileWithDebugEnabled() + public function testStaleResourceInDebug() { - unlink($this->cacheFile); - unlink($this->metaFile); - - $metadata = array(new FileResource($this->resourceFile)); + $staleResource = new ResourceStub(); + $staleResource->setFresh(false); $cache = new ConfigCache($this->cacheFile, true); - $cache->write('FOOBAR', $metadata); - - $this->assertFileExists($this->cacheFile, 'Cache file is created'); - $this->assertFileExists($this->metaFile, 'Meta file is created'); - $this->assertSame(serialize($metadata), file_get_contents($this->metaFile)); - } - - private function makeCacheFresh() - { - touch($this->resourceFile, filemtime($this->cacheFile) - 3600); - } + $cache->write('', array($staleResource)); - private function makeCacheStale() - { - touch($this->cacheFile, filemtime($this->resourceFile) - 3600); + $this->assertFalse($cache->isFresh()); } - private function generateMetaFile() + public function debugModes() { - file_put_contents($this->metaFile, serialize(array(new FileResource($this->resourceFile)))); + return array( + array(true), + array(false), + ); } } diff --git a/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php b/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php new file mode 100644 index 0000000000000..be790db17124d --- /dev/null +++ b/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php @@ -0,0 +1,29 @@ +fresh = $isFresh; + } + + public function __toString() { + return 'stub'; + } + + public function isFresh($timestamp) + { + return $this->fresh; + } + + public function getResource() + { + return 'stub'; + } +} diff --git a/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php b/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php new file mode 100644 index 0000000000000..6a915ffed724a --- /dev/null +++ b/src/Symfony/Component/Config/Tests/ResourceCheckerConfigCacheTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests; + +use Symfony\Component\Config\Tests\Resource\ResourceStub; +use Symfony\Component\Config\ResourceCheckerConfigCache; + +class ResourceCheckerConfigCacheTest extends \PHPUnit_Framework_TestCase +{ + private $cacheFile = null; + + protected function setUp() + { + $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); + } + + protected function tearDown() + { + $files = array($this->cacheFile, "{$this->cacheFile}.meta"); + + foreach ($files as $file) { + if (file_exists($file)) { + unlink($file); + } + } + } + + public function testGetPath() + { + $cache = new ResourceCheckerConfigCache($this->cacheFile); + + $this->assertSame($this->cacheFile, $cache->getPath()); + } + + public function testCacheIsNotFreshIfEmpty() + { + $checker = $this->getMock('\Symfony\Component\Config\ResourceCheckerInterface') + ->expects($this->never())->method('supports'); + + /* If there is nothing in the cache, it needs to be filled (and thus it's not fresh). + It does not matter if you provide checkers or not. */ + + unlink($this->cacheFile); // remove tempnam() side effect + $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker)); + + $this->assertFalse($cache->isFresh()); + } + + public function testCacheIsFreshIfNocheckerProvided() + { + /* For example in prod mode, you may choose not to run any checkers + at all. In that case, the cache should always be considered fresh. */ + $cache = new ResourceCheckerConfigCache($this->cacheFile); + $this->assertTrue($cache->isFresh()); + } + + public function testResourcesWithoutcheckersAreIgnoredAndConsideredFresh() + { + /* As in the previous test, but this time we have a resource. */ + $cache = new ResourceCheckerConfigCache($this->cacheFile); + $cache->write('', array(new ResourceStub())); + + $this->assertTrue($cache->isFresh()); // no (matching) ResourceChecker passed + } + + public function testIsFreshWithchecker() + { + $checker = $this->getMock('\Symfony\Component\Config\ResourceCheckerInterface'); + + $checker->expects($this->once()) + ->method('supports') + ->willReturn(true); + + $checker->expects($this->once()) + ->method('isFresh') + ->willReturn(true); + + $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker)); + $cache->write('', array(new ResourceStub())); + + $this->assertTrue($cache->isFresh()); + } + + public function testIsNotFreshWithchecker() + { + $checker = $this->getMock('\Symfony\Component\Config\ResourceCheckerInterface'); + + $checker->expects($this->once()) + ->method('supports') + ->willReturn(true); + + $checker->expects($this->once()) + ->method('isFresh') + ->willReturn(false); + + $cache = new ResourceCheckerConfigCache($this->cacheFile, array($checker)); + $cache->write('', array(new ResourceStub())); + + $this->assertFalse($cache->isFresh()); + } + + public function testCacheKeepsContent() + { + $cache = new ResourceCheckerConfigCache($this->cacheFile); + $cache->write('FOOBAR'); + + $this->assertSame('FOOBAR', file_get_contents($cache->getPath())); + } +} diff --git a/src/Symfony/Component/HttpKernel/Config/EnvParametersResource.php b/src/Symfony/Component/HttpKernel/Config/EnvParametersResource.php index 5f54450137a71..b4178a50ee3e2 100644 --- a/src/Symfony/Component/HttpKernel/Config/EnvParametersResource.php +++ b/src/Symfony/Component/HttpKernel/Config/EnvParametersResource.php @@ -11,14 +11,14 @@ namespace Symfony\Component\HttpKernel\Config; -use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; /** * EnvParametersResource represents resources stored in prefixed environment variables. * * @author Chris Wilkinson */ -class EnvParametersResource implements ResourceInterface, \Serializable +class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable { /** * @var string diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index d702fac80863f..27fd41f6a0c8f 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -26,7 +26,7 @@ "symfony/phpunit-bridge": "~2.7|~3.0.0", "symfony/browser-kit": "~2.3|~3.0.0", "symfony/class-loader": "~2.1|~3.0.0", - "symfony/config": "~2.7", + "symfony/config": "~2.8", "symfony/console": "~2.3|~3.0.0", "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0", "symfony/dependency-injection": "~2.8|~3.0.0", diff --git a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php index abe364c7368c7..e931a4c13c1d3 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Translation\Tests; -use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; use Symfony\Component\Translation\Loader\ArrayLoader; use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Translator; @@ -281,7 +281,7 @@ private function createFailingLoader() } } -class StaleResource implements ResourceInterface +class StaleResource implements SelfCheckingResourceInterface { public function isFresh($timestamp) { From 64b922436676a1db304a966819dd3681a8687e5c Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 9 Sep 2015 16:01:57 +0200 Subject: [PATCH 2/6] Fix deprecation notice in test --- src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php index e931a4c13c1d3..75093580b472b 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php @@ -228,7 +228,7 @@ public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardless public function testRefreshCacheWhenResourcesAreNoLongerFresh() { - $resource = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $resource = $this->getMock('Symfony\Component\Config\Resource\SelfCheckingResourceInterface'); $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $resource->method('isFresh')->will($this->returnValue(false)); $loader From 6d81c69ad66878d5cc1f26fd0158fa3c3be01d21 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 9 Sep 2015 17:40:05 +0200 Subject: [PATCH 3/6] Bump require-dev dep because TranslatorCacheTest now needs SelfCheckingResourceInterface --- src/Symfony/Component/Translation/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Translation/composer.json b/src/Symfony/Component/Translation/composer.json index 1c725d43d18a1..ddef54cd9192f 100644 --- a/src/Symfony/Component/Translation/composer.json +++ b/src/Symfony/Component/Translation/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "symfony/phpunit-bridge": "~2.7|~3.0.0", - "symfony/config": "~2.7", + "symfony/config": "~2.8", "symfony/intl": "~2.4|~3.0.0", "symfony/yaml": "~2.2|~3.0.0", "psr/log": "~1.0" From ab13ef4a34840b2ff9c626f9394e172dd9c2bc4e Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 9 Sep 2015 18:31:48 +0200 Subject: [PATCH 4/6] Feedback from GH review --- .../Compiler/ConfigCachePass.php | 11 ++++++--- .../Resources/config/services.xml | 11 ++++----- .../Compiler/ConfigCachePassTest.php | 24 +++++++++++++++---- src/Symfony/Component/Config/ConfigCache.php | 4 ++-- .../Resource/BCResourceInterfaceChecker.php | 8 +++---- ...er.php => SelfCheckingResourceChecker.php} | 16 ++++++------- .../ResourceCheckerConfigCacheFactory.php | 2 +- .../Config/ResourceCheckerInterface.php | 6 ++--- 8 files changed, 50 insertions(+), 32 deletions(-) rename src/Symfony/Component/Config/Resource/{DefaultResourceChecker.php => SelfCheckingResourceChecker.php} (56%) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php index ce36b1b2618ca..a8e1c549a2d6a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ConfigCachePass.php @@ -26,15 +26,20 @@ class ConfigCachePass implements CompilerPassInterface public function process(ContainerBuilder $container) { $resourceCheckers = array(); - foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $attributes) { - $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + + foreach ($container->findTaggedServiceIds('config_cache.resource_checker') as $id => $tags) { + $priority = isset($tags[0]['priority']) ? $tags[0]['priority'] : 0; $resourceCheckers[$priority][] = new Reference($id); } + if (empty($resourceCheckers)) { + return; + } + // sort by priority and flatten krsort($resourceCheckers); $resourceCheckers = call_user_func_array('array_merge', $resourceCheckers); - $container->getDefinition('config_cache_factory')->addMethodCall('setResourceCheckers', array($resourceCheckers)); + $container->getDefinition('config_cache_factory')->replaceArgument(0, $resourceCheckers); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 1c2a2fe673961..f0d54b0d76a43 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -13,9 +13,6 @@ Symfony\Component\HttpKernel\Config\FileLocator Symfony\Component\HttpKernel\UriSigner Symfony\Component\HttpFoundation\RequestStack - Symfony\Component\Config\ResourceCheckerConfigCacheFactory - Symfony\Component\Config\Resource\DefaultResourceChecker - Symfony\Component\Config\Resource\BCResourceInterfaceChecker @@ -67,16 +64,18 @@ %kernel.secret% - + + + - + - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php index 9628ced6b9683..c0eef3d627ce7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ConfigCachePassTest.php @@ -39,14 +39,28 @@ public function testThatCheckersAreProcessedInPriorityOrder() ->will($this->returnValue($definition)); $definition->expects($this->once()) - ->method('addMethodCall') - ->with('setResourceCheckers', array( - array( + ->method('replaceArgument') + ->with(0, array( new Reference('checker_1'), new Reference('checker_2'), new Reference('checker_3'), - ), - )); + )); + + $pass = new ConfigCachePass(); + $pass->process($container); + } + + public function testThatCheckersCanBeMissing() + { + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds') + ); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue(array())); $pass = new ConfigCachePass(); $pass->process($container); diff --git a/src/Symfony/Component/Config/ConfigCache.php b/src/Symfony/Component/Config/ConfigCache.php index fe4b23d30e8bd..5ede07b55230b 100644 --- a/src/Symfony/Component/Config/ConfigCache.php +++ b/src/Symfony/Component/Config/ConfigCache.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Config; use Symfony\Component\Config\Resource\BCResourceInterfaceChecker; -use Symfony\Component\Config\Resource\DefaultResourceChecker; +use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; /** * ConfigCache caches arbitrary content in files on disk. @@ -40,7 +40,7 @@ class ConfigCache extends ResourceCheckerConfigCache public function __construct($file, $debug) { parent::__construct($file, array( - new DefaultResourceChecker(), + new SelfCheckingResourceChecker(), new BCResourceInterfaceChecker(), )); $this->debug = (bool) $debug; diff --git a/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php index 8ed26202a2809..631755e8fe926 100644 --- a/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php +++ b/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php @@ -18,7 +18,7 @@ * * @deprecated since 2.8, to be removed in 3.0. */ -class BCResourceInterfaceChecker extends DefaultResourceChecker +class BCResourceInterfaceChecker extends SelfCheckingResourceChecker { public function supports(ResourceInterface $metadata) { @@ -27,10 +27,10 @@ public function supports(ResourceInterface $metadata) return true; } - public function isFresh(ResourceInterface $metadata, $timestamp) + public function isFresh(ResourceInterface $resource, $timestamp) { - trigger_error('Resource checking through ResourceInterface::isFresh() is deprecated since 2.8 and will be removed in 3.0', E_USER_DEPRECATED); + @trigger_error('Resource checking through ResourceInterface::isFresh() is deprecated since 2.8 and will be removed in 3.0', E_USER_DEPRECATED); - return parent::isFresh($metadata, $timestamp); // For now, $metadata features the isFresh() method, so off we go (quack quack) + return parent::isFresh($resource, $timestamp); // For now, $metadata features the isFresh() method, so off we go (quack quack) } } diff --git a/src/Symfony/Component/Config/Resource/DefaultResourceChecker.php b/src/Symfony/Component/Config/Resource/SelfCheckingResourceChecker.php similarity index 56% rename from src/Symfony/Component/Config/Resource/DefaultResourceChecker.php rename to src/Symfony/Component/Config/Resource/SelfCheckingResourceChecker.php index efffacabbf5a4..d72203bc1a42c 100644 --- a/src/Symfony/Component/Config/Resource/DefaultResourceChecker.php +++ b/src/Symfony/Component/Config/Resource/SelfCheckingResourceChecker.php @@ -14,23 +14,23 @@ use Symfony\Component\Config\ResourceCheckerInterface; /** - * Resource checker for instances of - * SelfCheckingResourceInterface. As these resources can - * perform the check themselves, we can support them in a generic - * way. + * Resource checker for instances of SelfCheckingResourceInterface. + * + * As these resources perform the actual check themselves, we can provide + * this class as a standard way of validating them. * * @author Matthias Pigulla */ -class DefaultResourceChecker implements ResourceCheckerInterface +class SelfCheckingResourceChecker implements ResourceCheckerInterface { public function supports(ResourceInterface $metadata) { return $metadata instanceof SelfCheckingResourceInterface; } - public function isFresh(ResourceInterface $metadata, $timestamp) + public function isFresh(ResourceInterface $resource, $timestamp) { - /* @var SelfCheckingResourceInterface $metadata */ - return $metadata->isFresh($timestamp); + /* @var SelfCheckingResourceInterface $resource */ + return $resource->isFresh($timestamp); } } diff --git a/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php b/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php index c9fd7c1bec13b..61d732cc1c452 100644 --- a/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php +++ b/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php @@ -27,7 +27,7 @@ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface /** * @param ResourceCheckerInterface[] $resourceCheckers */ - public function setResourceCheckers(array $resourceCheckers) + public function __construct(array $resourceCheckers = array()) { $this->resourceCheckers = $resourceCheckers; } diff --git a/src/Symfony/Component/Config/ResourceCheckerInterface.php b/src/Symfony/Component/Config/ResourceCheckerInterface.php index 5bffc437c92a6..aaa5c5065e302 100644 --- a/src/Symfony/Component/Config/ResourceCheckerInterface.php +++ b/src/Symfony/Component/Config/ResourceCheckerInterface.php @@ -39,11 +39,11 @@ public function supports(ResourceInterface $metadata); /** * Validates the resource. * - * @param object $metadata The resource to be validated. - * @param int $timestamp The timestamp at which the cache associated with this resource was created. + * @param ResourceInterface $resource The resource to be validated. + * @param int $timestamp The timestamp at which the cache associated with this resource was created. * * @return bool True if the resource has not changed since the given timestamp, false otherwise. */ - public function isFresh(ResourceInterface $metadata, $timestamp); + public function isFresh(ResourceInterface $resource, $timestamp); } From d1cb1fd592da59fbf76f2627220d86bd0d9a42d6 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 14 Sep 2015 09:54:10 +0200 Subject: [PATCH 5/6] Add UPGRADING notes --- UPGRADE-2.8.md | 32 ++++++++++++++++++++++++++++++++ UPGRADE-3.0.md | 7 +++++++ 2 files changed, 39 insertions(+) diff --git a/UPGRADE-2.8.md b/UPGRADE-2.8.md index 24861929c8ae5..b84f3fbe8b87a 100644 --- a/UPGRADE-2.8.md +++ b/UPGRADE-2.8.md @@ -402,3 +402,35 @@ FrameworkBundle session: cookie_httponly: false ``` + +Config +------ + + * The `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` method has been + deprecated and will be removed in Symfony 3.0 because it assumes that resource + implementations are able to check themselves for freshness. + + If you have custom resources that implement this method, change them to implement the + `\Symfony\Component\Config\Resource\SelfCheckingResourceInterface` sub-interface instead + of `\Symfony\Component\Config\Resource\ResourceInterface`. + + Before: + + ```php + use Symfony\Component\Config\Resource\ResourceInterface; + + class MyCustomResource implements ResourceInterface { ... } + ``` + + After: + + ```php + use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + + class MyCustomResource implements SelfCheckingResourceInterface { ... } + ``` + + Additionally, if you have implemented cache validation strategies *using* `isFresh()` + yourself, you should have a look at the new cache validation system based on + `ResourceChecker`s. + diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index de9a230a55468..4f8028dc873c4 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1201,3 +1201,10 @@ UPGRADE FROM 2.x to 3.0 * `Process::setStdin()` and `Process::getStdin()` have been removed. Use `Process::setInput()` and `Process::getInput()` that works the same way. * `Process::setInput()` and `ProcessBuilder::setInput()` do not accept non-scalar types. + +### Config + + * `\Symfony\Component\Config\Resource\ResourceInterface::isFresh()` has been removed. Also, + cache validation through this method (which was still supported in 2.8 for BC) does no longer + work because the `\Symfony\Component\Config\Resource\BCResourceInterfaceChecker` helper class + has been removed as well. From a845b3cef9a1fbc20957e2a6574f8e502d711ac3 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 14 Sep 2015 16:39:54 +0200 Subject: [PATCH 6/6] CS fix / License added --- .../Component/Config/Tests/Resource/ResourceStub.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php b/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php index be790db17124d..78799d7b91967 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php +++ b/src/Symfony/Component/Config/Tests/Resource/ResourceStub.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Config\Tests\Resource; use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; @@ -13,7 +22,8 @@ public function setFresh($isFresh) $this->fresh = $isFresh; } - public function __toString() { + public function __toString() + { return 'stub'; } 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