[Config] Delegate creation of ConfigCache instances to a factory.

This commit is contained in:
Matthias Pigulla 2015-04-02 15:33:59 +02:00 committed by Fabien Potencier
parent aa82fb065d
commit 6fbe9b1064
13 changed files with 348 additions and 73 deletions

View File

@ -16,6 +16,12 @@ Router
but in 2.7 you would get an error if `bar` parameter
doesn't exist or unexpected result otherwise.
* The `getMatcherDumperInstance()` and `getGeneratorDumperInstance()` methods in the
`Symfony\Component\Routing\Router` have been changed from `protected` to `public`.
If you override these methods in a subclass, you will need to change your
methods to `public` as well. Note however that this is a temporary change needed for
PHP 5.3 compatibility only. It will be reverted in Symfony 3.0.
Form
----
@ -515,3 +521,10 @@ PropertyAccess
new UnexpectedTypeException($value, $path, $pathIndex);
```
Config
------
* The `__toString()` method of the `\Symfony\Component\Config\ConfigCache` is marked as
deprecated in favor of the new `getPath()` method.

View File

@ -1,6 +1,12 @@
CHANGELOG
=========
2.7.0
-----
* added `ConfigCacheInterface`, `ConfigCacheFactoryInterface` and a basic `ConfigCacheFactory`
implementation to delegate creation of ConfigCache instances
2.2.0
-----

View File

@ -23,14 +23,12 @@ use Symfony\Component\Filesystem\Filesystem;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfigCache
class ConfigCache implements ConfigCacheInterface
{
private $debug;
private $file;
/**
* Constructor.
*
* @param string $file The absolute cache path
* @param bool $debug Whether debugging is enabled or not
*/
@ -44,8 +42,21 @@ class ConfigCache
* Gets the cache file path.
*
* @return string The cache file path
* @deprecated since 2.7, to be removed in 3.0. Use getPath() instead.
*/
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;
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Basic implementation for ConfigCacheFactoryInterface
* that will simply create an instance of ConfigCache.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
class ConfigCacheFactory implements ConfigCacheFactoryInterface
{
/**
* @var bool Debug flag passed to the ConfigCache
*/
private $debug;
/**
* @param bool $debug The debug flag to pass to ConfigCache
*/
public function __construct($debug)
{
$this->debug = $debug;
}
/**
* {@inheritdoc}
*/
public function cache($file, $callback)
{
$cache = new ConfigCache($file, $this->debug);
if (!$cache->isFresh()) {
call_user_func($callback, $cache);
}
return $cache;
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Config;
/**
* Interface for a ConfigCache factory. This factory creates
* an instance of ConfigCacheInterface and initializes the
* cache if necessary.
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheFactoryInterface
{
/**
* Creates a cache instance and (re-)initializes it if necessary.
*
* @param string $file The absolute cache file path
* @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
*
* @return ConfigCacheInterface $configCache The cache instance
*/
public function cache($file, $callable);
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 ConfigCache
*
* @author Matthias Pigulla <mp@webfactory.de>
*/
interface ConfigCacheInterface
{
/**
* Gets the cache file path.
*
* @return string The cache file path
*/
public function getPath();
/**
* Checks if the cache is still fresh.
*
* This check should take the metadata passed to the write() method into consideration.
*
* @return bool Whether the cache is still fresh.
*/
public function isFresh();
/**
* Writes the given content into the cache file. Metadata will be stored
* independently and can be used to check cache freshness at a later time.
*
* @param string $content The content to write into the cache
* @param ResourceInterface[]|null $metadata An array of ResourceInterface instances
*
* @throws \RuntimeException When the cache file cannot be written
*/
public function write($content, array $metadata = null);
}

View File

@ -47,7 +47,7 @@ class ConfigCacheTest extends \PHPUnit_Framework_TestCase
{
$cache = new ConfigCache($this->cacheFile, true);
$this->assertSame($this->cacheFile, (string) $cache);
$this->assertSame($this->cacheFile, $cache->getPath());
}
public function testCacheIsNotFreshIfFileDoesNotExist()

View File

@ -252,7 +252,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
}
/**
* {@inheritDoc}
* {@inheritdoc}
*
* @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle
*/
@ -546,7 +546,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
$fresh = false;
}
require_once $cache;
require_once $cache->getPath();
$this->container = new $class();
$this->container->set('kernel', $this);
@ -695,7 +695,7 @@ abstract class Kernel implements KernelInterface, TerminableInterface
$dumper->setProxyDumper(new ProxyDumper());
}
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => (string) $cache));
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath()));
if (!$this->debug) {
$content = static::stripComments($content);
}

View File

@ -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.0,>=2.0.5|~3.0.0",
"symfony/config": "~2.7",
"symfony/console": "~2.3|~3.0.0",
"symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
"symfony/dependency-injection": "~2.2|~3.0.0",
@ -40,6 +40,9 @@
"symfony/translation": "~2.0,>=2.0.5|~3.0.0",
"symfony/var-dumper": "~2.6|~3.0.0"
},
"conflict": {
"symfony/config": "<2.7"
},
"suggest": {
"symfony/browser-kit": "",
"symfony/class-loader": "",

View File

@ -12,7 +12,9 @@
namespace Symfony\Component\Routing;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@ -71,6 +73,11 @@ class Router implements RouterInterface, RequestMatcherInterface
*/
protected $logger;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
/**
* @var ExpressionFunctionProviderInterface[]
*/
@ -209,6 +216,16 @@ class Router implements RouterInterface, RequestMatcherInterface
return $this->context;
}
/**
* Sets the ConfigCache factory to use.
*
* @param ConfigCacheFactoryInterface $configCacheFactory The factory to use.
*/
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
}
/**
* {@inheritdoc}
*/
@ -262,24 +279,29 @@ class Router implements RouterInterface, RequestMatcherInterface
}
$class = $this->options['matcher_cache_class'];
$cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']);
if (!$cache->isFresh()) {
$dumper = $this->getMatcherDumperInstance();
if (method_exists($dumper, 'addExpressionLanguageProvider')) {
foreach ($this->expressionLanguageProviders as $provider) {
$dumper->addExpressionLanguageProvider($provider);
$baseClass = $this->options['matcher_base_class'];
$expressionLanguageProviders = $this->expressionLanguageProviders;
$that = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
$cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$class.'.php',
function (ConfigCacheInterface $cache) use ($that, $class, $baseClass, $expressionLanguageProviders) {
$dumper = $that->getMatcherDumperInstance();
if (method_exists($dumper, 'addExpressionLanguageProvider')) {
foreach ($expressionLanguageProviders as $provider) {
$dumper->addExpressionLanguageProvider($provider);
}
}
$options = array(
'class' => $class,
'base_class' => $baseClass,
);
$cache->write($dumper->dump($options), $that->getRouteCollection()->getResources());
}
);
$options = array(
'class' => $class,
'base_class' => $this->options['matcher_base_class'],
);
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
}
require_once $cache;
require_once $cache->getPath();
return $this->matcher = new $class($this->context);
}
@ -299,19 +321,22 @@ class Router implements RouterInterface, RequestMatcherInterface
$this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger);
} else {
$class = $this->options['generator_cache_class'];
$cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']);
if (!$cache->isFresh()) {
$dumper = $this->getGeneratorDumperInstance();
$baseClass = $this->options['generator_base_class'];
$that = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
$cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$class.'.php',
function (ConfigCacheInterface $cache) use ($that, $class, $baseClass) {
$dumper = $that->getGeneratorDumperInstance();
$options = array(
'class' => $class,
'base_class' => $this->options['generator_base_class'],
);
$options = array(
'class' => $class,
'base_class' => $baseClass,
);
$cache->write($dumper->dump($options), $this->getRouteCollection()->getResources());
}
$cache->write($dumper->dump($options), $that->getRouteCollection()->getResources());
}
);
require_once $cache;
require_once $cache->getPath();
$this->generator = new $class($this->context, $this->logger);
}
@ -329,18 +354,37 @@ class Router implements RouterInterface, RequestMatcherInterface
}
/**
* This method is public because it needs to be callable from a closure in PHP 5.3. It should be converted back to protected in 3.0.
* @internal
* @return GeneratorDumperInterface
*/
protected function getGeneratorDumperInstance()
public function getGeneratorDumperInstance()
{
return new $this->options['generator_dumper_class']($this->getRouteCollection());
}
/**
* This method is public because it needs to be callable from a closure in PHP 5.3. It should be converted back to protected in 3.0.
* @internal
* @return MatcherDumperInterface
*/
protected function getMatcherDumperInstance()
public function getMatcherDumperInstance()
{
return new $this->options['matcher_dumper_class']($this->getRouteCollection());
}
/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*
* @return ConfigCacheFactoryInterface $configCacheFactory
*/
private function getConfigCacheFactory()
{
if (null === $this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->options['debug']);
}
return $this->configCacheFactory;
}
}

View File

@ -20,7 +20,7 @@
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7|~3.0.0",
"symfony/config": "~2.2|~3.0.0",
"symfony/config": "~2.7|~3.0.0",
"symfony/http-foundation": "~2.3|~3.0.0",
"symfony/yaml": "~2.0,>=2.0.5|~3.0.0",
"symfony/expression-language": "~2.4|~3.0.0",
@ -28,6 +28,9 @@
"doctrine/common": "~2.2",
"psr/log": "~1.0"
},
"conflict": {
"symfony/config": "<2.7"
},
"suggest": {
"symfony/config": "For using the all-in-one router or any loader",
"symfony/yaml": "For using the YAML loader",

View File

@ -13,7 +13,9 @@ namespace Symfony\Component\Translation;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheFactory;
/**
* Translator.
@ -64,6 +66,11 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
*/
private $debug;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
/**
* Constructor.
*
@ -84,6 +91,16 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
$this->debug = $debug;
}
/**
* Sets the ConfigCache factory to use.
*
* @param ConfigCacheFactoryInterface $configCacheFactory
*/
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
}
/**
* Adds a Loader.
*
@ -315,7 +332,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
return $messages;
}
/*
/**
* @param string $locale
*/
protected function loadCatalogue($locale)
@ -346,21 +363,71 @@ class Translator implements TranslatorInterface, TranslatorBagInterface
/**
* @param string $locale
* @param bool $forceRefresh
*/
private function initializeCacheCatalogue($locale, $forceRefresh = false)
private function initializeCacheCatalogue($locale)
{
if (isset($this->catalogues[$locale])) {
/* Catalogue already initialized. */
return;
}
$this->assertValidLocale($locale);
$cache = new ConfigCache($this->cacheDir.'/catalogue.'.$locale.'.php', $this->debug);
if ($forceRefresh || !$cache->isFresh()) {
$this->initializeCatalogue($locale);
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$cacheFile = $this->cacheDir.'/catalogue.'.$locale.'.php';
$self = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0.
$cache = $this->getConfigCacheFactory()->cache($cacheFile,
function (ConfigCacheInterface $cache) use ($self, $locale) {
$self->dumpCatalogue($locale, $cache);
}
);
$content = sprintf(<<<EOF
if (isset($this->catalogues[$locale])) {
/* Catalogue has been initialized as it was written out to cache. */
return;
}
/* Read catalogue from cache. */
$catalogue = include $cache->getPath();
/*
* Gracefully handle the case when the cached catalogue is in an "old" format, without a resourcesHash
*/
$resourcesHash = null;
if (is_array($catalogue)) {
list($catalogue, $resourcesHash) = $catalogue;
}
if ($this->debug && $resourcesHash !== $this->getResourcesHash($locale)) {
/*
* This approach of resource checking has the disadvantage that a second
* type of freshness check happens based on content *inside* the cache, while
* the idea of ConfigCache is to make this check transparent to the client (and keeps
* the resources in a .meta file).
*
* Thus, we might run into the unfortunate situation that we just thought (a few lines above)
* that the cache is fresh -- and now that we look into it, we figure it's not.
*
* For now, just unlink the cache and try again. See
* https://github.com/symfony/symfony/pull/11862#issuecomment-54634631 and/or
* https://github.com/symfony/symfony/issues/7176 for possible better approaches.
*/
unlink($cacheFile);
$this->initializeCacheCatalogue($locale);
} else {
/* Initialize with catalogue from cache. */
$this->catalogues[$locale] = $catalogue;
}
}
/**
* This method is public because it needs to be callable from a closure in PHP 5.3. It should be made protected (or even private, if possible) in 3.0.
* @internal
*/
public function dumpCatalogue($locale, ConfigCacheInterface $cache)
{
$this->initializeCatalogue($locale);
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$content = sprintf(<<<EOF
<?php
use Symfony\Component\Translation\MessageCatalogue;
@ -372,33 +439,14 @@ use Symfony\Component\Translation\MessageCatalogue;
return array(\$catalogue, \$resourcesHash);
EOF
,
$this->getResourcesHash($locale),
$locale,
var_export($this->catalogues[$locale]->all(), true),
$fallbackContent
);
,
$this->getResourcesHash($locale),
$locale,
var_export($this->catalogues[$locale]->all(), true),
$fallbackContent
);
$cache->write($content, $this->catalogues[$locale]->getResources());
return;
}
$catalogue = include $cache;
/*
* Old cache returns only the catalogue, without resourcesHash
*/
$resourcesHash = null;
if (is_array($catalogue)) {
list($catalogue, $resourcesHash) = $catalogue;
}
if ($this->debug && $resourcesHash !== $this->getResourcesHash($locale)) {
return $this->initializeCacheCatalogue($locale, true);
}
$this->catalogues[$locale] = $catalogue;
$cache->write($content, $this->catalogues[$locale]->getResources());
}
private function getFallbackContent(MessageCatalogue $catalogue)
@ -514,4 +562,19 @@ EOF
throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
}
}
/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*
* @return ConfigCacheFactoryInterface $configCacheFactory
*/
private function getConfigCacheFactory()
{
if (!$this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->debug);
}
return $this->configCacheFactory;
}
}

View File

@ -20,11 +20,14 @@
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7|~3.0.0",
"symfony/config": "~2.3,>=2.3.12|~3.0.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.3|~3.0.0",
"symfony/yaml": "~2.2|~3.0.0",
"psr/log": "~1.0"
},
"conflict": {
"symfony/config": "<2.7"
},
"suggest": {
"symfony/config": "",
"symfony/yaml": "",