From 4794d8b474f5a19671a4b6945d8002d3256e30f0 Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Thu, 26 Mar 2015 13:34:12 +0000 Subject: [PATCH] [Translation] added message cache + doctrine cache. --- .../DoctrineMessageCatalogueTest.php | 45 ++++ .../TranslatorDoctrineCacheTest.php | 42 ++++ .../Translation/DoctrineMessageCache.php | 141 +++++++++++ .../Translation/DoctrineMessageCatalogue.php | 224 ++++++++++++++++++ src/Symfony/Bridge/Doctrine/composer.json | 9 +- .../DependencyInjection/Configuration.php | 1 + .../FrameworkExtension.php | 8 + .../Resources/config/schema/symfony-1.0.xsd | 1 + .../Resources/config/translation.xml | 9 +- .../DependencyInjection/ConfigurationTest.php | 1 + .../FrameworkExtensionTest.php | 1 + .../Translation/Translator.php | 15 +- .../Component/Translation/MessageCache.php | 148 ++++++++++++ .../Translation/MessageCacheInterface.php | 45 ++++ .../Tests/MessageCatalogueTest.php | 45 ++-- .../Translation/Tests/TranslatorCacheTest.php | 44 ++-- .../Component/Translation/Translator.php | 133 +++-------- 17 files changed, 758 insertions(+), 154 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Translation/DoctrineMessageCatalogueTest.php create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Translation/TranslatorDoctrineCacheTest.php create mode 100644 src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCache.php create mode 100644 src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCatalogue.php create mode 100644 src/Symfony/Component/Translation/MessageCache.php create mode 100644 src/Symfony/Component/Translation/MessageCacheInterface.php diff --git a/src/Symfony/Bridge/Doctrine/Tests/Translation/DoctrineMessageCatalogueTest.php b/src/Symfony/Bridge/Doctrine/Tests/Translation/DoctrineMessageCatalogueTest.php new file mode 100644 index 000000000000..17083a67ec4e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Translation/DoctrineMessageCatalogueTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Translation; + +use Symfony\Bridge\Doctrine\Translation\DoctrineMessageCatalogue; +use Doctrine\Common\Cache\ArrayCache; +use Symfony\Component\Translation\Tests\MessageCatalogueTest; + +class DoctrineMessageCatalogueTest extends MessageCatalogueTest +{ + protected function setUp() + { + if (!interface_exists('Doctrine\Common\Cache\Cache')) { + $this->markTestSkipped('The "Doctrine Cache" is not available'); + } + } + + public function testAll() + { + if (!interface_exists('Doctrine\Common\Cache\MultiGetCache')) { + $this->markTestSkipped('The "Doctrine MultiGetCache" is not available'); + } + + parent::testAll(); + } + + protected function getCatalogue($locale, $messages = array()) + { + $catalogue = new DoctrineMessageCatalogue($locale, new ArrayCache()); + foreach ($messages as $domain => $domainMessages) { + $catalogue->add($domainMessages, $domain); + } + + return $catalogue; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Translation/TranslatorDoctrineCacheTest.php b/src/Symfony/Bridge/Doctrine/Tests/Translation/TranslatorDoctrineCacheTest.php new file mode 100644 index 000000000000..996cb70c574e --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Translation/TranslatorDoctrineCacheTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Translation; + +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Bridge\Doctrine\Translation\DoctrineMessageCache; +use Doctrine\Common\Cache\ArrayCache; +use Symfony\Component\Translation\Loader\PhpFileLoader; +use Symfony\Component\Translation\Dumper\PhpFileDumper; +use Symfony\Component\Translation\Tests\TranslatorCacheTest; + +class TranslatorDoctrineCacheTest extends TranslatorCacheTest +{ + private $cache; + + protected function setUp() + { + if (!interface_exists('Doctrine\Common\Cache\Cache')) { + $this->markTestSkipped('The "Doctrine Cache" is not available'); + } + + $this->cache = new ArrayCache(); + } + + protected function getTranslator($locale, $debug) + { + $cache = new DoctrineMessageCache($this->cache, $debug); + + return new Translator($locale, null, $cache); + } +} diff --git a/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCache.php b/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCache.php new file mode 100644 index 000000000000..fbad33e622bd --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCache.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Translation; + +use Doctrine\Common\Cache\Cache; +use Symfony\Component\Translation\MessageCacheInterface; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * @author Abdellatif Ait Boudad + */ +class DoctrineMessageCache implements MessageCacheInterface +{ + const CACHE_CATALOGUE_HASH = 'catalogue_hash'; + const CACHE_DUMP_TIME = 'time'; + const CACHE_META_DATA = 'meta'; + const CATALOGUE_FALLBACK_LOCALE = 'fallback_locale'; + + /** + * @var bool + */ + private $debug; + + /** + * @var Cache + */ + private $cache; + + /** + * @param Cache $cache + * @param bool $debug + */ + public function __construct(Cache $cache, $debug = false) + { + $this->cache = $cache; + $this->debug = $debug; + } + + /** + * {@inheritdoc} + */ + public function isFresh($locale, array $options = array()) + { + $catalogueIdentifier = $this->getCatalogueIdentifier($locale, $options); + if (!$this->cache->contains($this->getCatalogueHashKey($catalogueIdentifier))) { + return false; + } + + if ($this->debug) { + $time = $this->cache->fetch($this->getDumpTimeKey($locale)); + $meta = unserialize($this->cache->fetch($this->getMetaDataKey($locale))); + foreach ($meta as $resource) { + if (!$resource->isFresh($time)) { + return false; + } + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function load($locale, array $options = array()) + { + $messages = new DoctrineMessageCatalogue($locale, $this->cache, $this->getCatalogueIdentifier($locale, $options)); + $catalogue = $messages; + while ($fallbackLocale = $this->cache->fetch($this->getFallbackLocaleKey($catalogue->getLocale()))) { + $fallback = new DoctrineMessageCatalogue($fallbackLocale, $this->cache, $this->getCatalogueIdentifier($locale, $options)); + $catalogue->addFallbackCatalogue($fallback); + $catalogue = $fallback; + } + + return $messages; + } + + /** + * {@inheritdoc} + */ + public function dump(MessageCatalogueInterface $messages, array $options = array()) + { + $resourcesHash = $this->getCatalogueIdentifier($messages->getLocale(), $options); + while ($messages) { + $catalogue = new DoctrineMessageCatalogue($messages->getLocale(), $this->cache, $resourcesHash); + $catalogue->addCatalogue($messages); + + $this->dumpMetaDataCatalogue($messages->getLocale(), $messages->getResources(), $resourcesHash); + if ($fallback = $messages->getFallbackCatalogue()) { + $this->cache->save($this->getFallbackLocaleKey($messages->getLocale()), $fallback->getLocale()); + } + + $messages = $messages->getFallbackCatalogue(); + } + } + + private function getCatalogueIdentifier($locale, $options) + { + return sha1(serialize(array( + 'resources' => $options['resources'], + 'fallback_locales' => $options['fallback_locales'], + ))); + } + + private function dumpMetaDataCatalogue($locale, $metadata, $resourcesHash) + { + // $catalogueIdentifier = $this->getCatalogueIdentifier($locale, $options); + $this->cache->save($this->getMetaDataKey($locale), serialize($metadata)); + $this->cache->save($this->getCatalogueHashKey($resourcesHash), $resourcesHash); + $this->cache->save($this->getDumpTimeKey($locale), time()); + } + + private function getDumpTimeKey($locale) + { + return self::CACHE_DUMP_TIME.'_'.$locale; + } + + private function getMetaDataKey($locale) + { + return self::CACHE_META_DATA.'_'.$locale; + } + + private function getCatalogueHashKey($locale) + { + return self::CACHE_CATALOGUE_HASH.'_'.$locale; + } + + private function getFallbackLocaleKey($locale) + { + return self::CATALOGUE_FALLBACK_LOCALE.'_'.$locale; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCatalogue.php b/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCatalogue.php new file mode 100644 index 000000000000..82c25c24ff08 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Translation/DoctrineMessageCatalogue.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Translation; + +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Cache\MultiGetCache; +use Symfony\Component\Translation\MessageCatalogue; + +/** + * @author Abdellatif Ait boudad + */ +class DoctrineMessageCatalogue extends MessageCatalogue +{ + const PREFIX = 'sf2_translation'; + const CATALOGUE_DOMAINS = 'domains'; + const CATALOGUE_DOMAIN_METADATA = 'domain_meta_'; + + /** + * @var Cache + */ + private $cache; + + /** + * @var string + */ + private $prefix; + + /** + * @var array + */ + private $domains = array(); + + /** + * @param string $locale + * @param Cache $cache + * @param string $prefix + */ + public function __construct($locale, Cache $cache, $prefix = self::PREFIX) + { + parent::__construct($locale); + if (0 === strlen($prefix)) { + throw new \InvalidArgumentException('$prefix cannot be empty.'); + } + + $this->cache = $cache; + $this->prefix = $prefix.'_'.$locale.'_'; + + if ($cache->contains($domainsId = $this->prefix.self::CATALOGUE_DOMAINS)) { + $this->domains = $cache->fetch($domainsId); + } + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + return $this->domains; + } + + /** + * {@inheritdoc} + */ + public function all($domain = null) + { + if (!$this->cache instanceof MultiGetCache) { + return array(); + } + + $domains = $this->domains; + if (null !== $domain) { + $domains = array($domain); + } + + $messages = array(); + foreach ($domains as $domainMeta) { + $domainIdentity = $this->getDomainMetaDataId($domainMeta); + if ($this->cache->contains($domainIdentity)) { + $keys = $this->cache->fetch($domainIdentity); + $values = $this->cache->fetchMultiple(array_keys($keys)); + foreach ($keys as $key => $id) { + if (isset($values[$key])) { + $messages[$domainMeta][$id] = $values[$key]; + } + } + } + } + + if (null === $domain) { + return $messages; + } + + return isset($messages[$domain]) ? $messages[$domain] : array(); + } + + /** + * {@inheritdoc} + */ + public function set($id, $translation, $domain = 'messages') + { + $this->add(array($id => $translation), $domain); + } + + /** + * {@inheritdoc} + */ + public function has($id, $domain = 'messages') + { + if ($this->defines($id, $domain)) { + return true; + } + + $fallbackCatalogue = $this->getFallbackCatalogue(); + if (null !== $fallbackCatalogue) { + return $fallbackCatalogue->has($id, $domain); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function defines($id, $domain = 'messages') + { + $key = $this->getCacheId($domain, $id); + + return $this->cache->contains($key); + } + + /** + * {@inheritdoc} + */ + public function get($id, $domain = 'messages') + { + if ($this->defines($id, $domain)) { + return $this->cache->fetch($this->getCacheId($domain, $id)); + } + + $fallbackCatalogue = $this->getFallbackCatalogue(); + if (null !== $fallbackCatalogue) { + return $fallbackCatalogue->get($id, $domain); + } + + return $id; + } + + /** + * {@inheritdoc} + */ + public function replace($messages, $domain = 'messages') + { + $domainMetaData = array(); + $domainMetaKey = $this->getDomainMetaDataId($domain); + if ($this->cache->contains($domainMetaKey)) { + $domainMetaData = $this->cache->fetch($domainMetaKey); + } + + foreach ($domainMetaData as $key => $id) { + if (!isset($messages[$id])) { + unset($domainMetaData[$key]); + $this->cache->delete($key); + } + } + + $this->cache->save($domainMetaKey, $domainMetaData); + $this->add($messages, $domain); + } + + /** + * {@inheritdoc} + */ + public function add($messages, $domain = 'messages') + { + if (!isset($this->domains[$domain])) { + $this->addDomain($domain); + } + + $domainMetaData = array(); + $domainMetaKey = $this->getDomainMetaDataId($domain); + if ($this->cache->contains($domainMetaKey)) { + $domainMetaData = $this->cache->fetch($domainMetaKey); + } + + foreach ($messages as $id => $translation) { + $key = $this->getCacheId($domain, $id); + $domainMetaData[$key] = $id; + $this->cache->save($key, $translation); + } + + $this->addDomainMetaData($domain, $domainMetaData); + } + + private function addDomain($domain) + { + $this->domains[] = $domain; + $this->cache->save($this->prefix.self::CATALOGUE_DOMAINS, $this->domains); + } + + private function addDomainMetaData($domain, $keys = array()) + { + $domainIdentity = $this->getDomainMetaDataId($domain); + $this->cache->save($domainIdentity, $keys); + } + + private function getCacheId($id, $domain = 'messages') + { + return $this->prefix.$domain.'_'.sha1($id); + } + + private function getDomainMetaDataId($domain) + { + return $this->prefix.self::CATALOGUE_DOMAIN_METADATA.$domain; + } +} diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 8b2ce6b8abbd..5df5191e8c72 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -29,17 +29,20 @@ "symfony/security": "~2.2", "symfony/expression-language": "~2.2", "symfony/validator": "~2.5,>=2.5.5", - "symfony/translation": "~2.0,>=2.0.5", + "symfony/translation": "~2.7", + "symfony/config": "~2.3,>=2.3.12", "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.2", - "doctrine/orm": "~2.2,>=2.2.3" + "doctrine/orm": "~2.2,>=2.2.3", + "doctrine/cache": "~1.0" }, "suggest": { "symfony/form": "", "symfony/validator": "", "doctrine/data-fixtures": "", "doctrine/dbal": "", - "doctrine/orm": "" + "doctrine/orm": "", + "doctrine/cache": "" }, "autoload": { "psr-4": { "Symfony\\Bridge\\Doctrine\\": "" } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 8a73424d9463..5aed33b0e341 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -582,6 +582,7 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ->defaultValue(array('en')) ->end() ->booleanNode('logging')->defaultValue($this->debug)->end() + ->scalarNode('cache')->defaultValue('translation.cache.default')->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a7d8aab22079..fa085e3a8fca 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -665,6 +665,14 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $container->setParameter('translator.logging', $config['logging']); + if (isset($config['cache'])) { + $container->setParameter( + 'translator.cache.prefix', + 'translator_'.hash('sha256', $container->getParameter('kernel.root_dir')) + ); + $container->setAlias('translation.cache', $config['cache']); + } + // Discover translation directories $dirs = array(); if (class_exists('Symfony\Component\Validator\Validation')) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index fa7aa2b2bd80..7f44aade68c8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -187,6 +187,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 0007a360c6e4..5661f8956ad7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -33,6 +33,7 @@ Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader Symfony\Component\Translation\Extractor\ChainExtractor Symfony\Component\Translation\Writer\TranslationWriter + @@ -44,7 +45,7 @@ %kernel.cache_dir%/translations %kernel.debug% - + @@ -157,5 +158,11 @@ + + + + %kernel.cache_dir%/translations + %kernel.debug% + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 617be624fdb9..d9fe145f66d6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -146,6 +146,7 @@ protected static function getBundleDefaultConfig() 'enabled' => false, 'fallbacks' => array('en'), 'logging' => true, + 'cache' => 'translation.cache.default', ), 'validation' => array( 'enabled' => false, diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 918c676afe1a..a07c06079a36 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -247,6 +247,7 @@ public function testTranslator() $calls = $container->getDefinition('translator.default')->getMethodCalls(); $this->assertEquals(array('fr'), $calls[0][1][0]); + $this->assertContains('translator_', $container->getParameter('translator.cache.prefix')); } public function testTranslatorMultipleFallbacks() diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php index 9cc92f9c2d61..67e6105e74c0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -15,6 +15,7 @@ use Symfony\Component\Translation\Translator as BaseTranslator; use Symfony\Component\Translation\MessageSelector; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Translation\MessageCacheInterface; /** * Translator. @@ -46,14 +47,15 @@ class Translator extends BaseTranslator implements WarmableInterface * * debug: Whether to enable debugging or not (false by default) * * resource_files: List of translation resources available grouped by locale. * - * @param ContainerInterface $container A ContainerInterface instance - * @param MessageSelector $selector The message selector for pluralization - * @param array $loaderIds An array of loader Ids - * @param array $options An array of options + * @param ContainerInterface $container A ContainerInterface instance + * @param MessageSelector $selector The message selector for pluralization + * @param array $loaderIds An array of loader Ids + * @param array $options An array of options + * @param MessageCacheInterface $cache The message cache * * @throws \InvalidArgumentException */ - public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array()) + public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array(), MessageCacheInterface $cache = null) { $this->container = $container; $this->loaderIds = $loaderIds; @@ -69,7 +71,8 @@ public function __construct(ContainerInterface $container, MessageSelector $sele $this->loadResources(); } - parent::__construct($container->getParameter('kernel.default_locale'), $selector, $this->options['cache_dir'], $this->options['debug']); + $cache = $cache ?: $this->options['cache_dir']; + parent::__construct($container->getParameter('kernel.default_locale'), $selector, $cache, $this->options['debug']); } /** diff --git a/src/Symfony/Component/Translation/MessageCache.php b/src/Symfony/Component/Translation/MessageCache.php new file mode 100644 index 000000000000..cd36c143d2f2 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCache.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheFactory; + +/** + * @author Abdellatif Ait Boudad + */ +class MessageCache implements MessageCacheInterface +{ + /** + * @var string + */ + private $cacheDir; + + /** + * @var bool + */ + private $debug; + + /** + * @var ConfigCacheFactoryInterface + */ + private $configCacheFactory; + + /** + * @param string $cacheDir + * @param bool $debug + * @param ConfigCacheFactoryInterface $configCacheFactory + */ + public function __construct($cacheDir, $debug = false, ConfigCacheFactoryInterface $configCacheFactory = null) + { + $this->cacheDir = $cacheDir; + $this->debug = $debug; + + if (null === $configCacheFactory) { + $configCacheFactory = new ConfigCacheFactory($debug); + } + + $this->configCacheFactory = $configCacheFactory; + } + + /** + * {@inheritdoc} + */ + public function isFresh($locale, array $options = array()) + { + $cache = $this->configCacheFactory->cache($this->getCatalogueCachePath($locale, $options), function ($cache) {}); + + return $cache->isFresh(); + } + + /** + * {@inheritdoc} + */ + public function load($locale, array $options = array()) + { + $cache = $this->configCacheFactory->cache($this->getCatalogueCachePath($locale, $options), function ($cache) {}); + + return include $cache->getPath(); + } + + /** + * {@inheritdoc} + */ + public function dump(MessageCatalogueInterface $messages, array $options = array()) + { + $self = $this; + $this->configCacheFactory->cache($this->getCatalogueCachePath($messages->getLocale(), $options), + function (ConfigCacheInterface $cache) use ($self, $messages) { + $self->dumpCatalogue($messages, $cache); + } + ); + } + + /** + * 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($catalogue, ConfigCacheInterface $cache) + { + $fallbackContent = $this->getFallbackContent($catalogue); + $content = sprintf(<<getLocale(), + var_export($catalogue->all(), true), + $fallbackContent + ); + $cache->write($content, $catalogue->getResources()); + } + + private function getFallbackContent(MessageCatalogue $catalogue) + { + $fallbackContent = ''; + $current = ''; + $replacementPattern = '/[^a-z0-9_]/i'; + $fallbackCatalogue = $catalogue->getFallbackCatalogue(); + while ($fallbackCatalogue) { + $fallback = $fallbackCatalogue->getLocale(); + $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); + $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); + $fallbackContent .= sprintf(<<addFallbackCatalogue(\$catalogue%s); +EOF + , + $fallbackSuffix, + $fallback, + var_export($fallbackCatalogue->all(), true), + $currentSuffix, + $fallbackSuffix + ); + $current = $fallbackCatalogue->getLocale(); + $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); + } + + return $fallbackContent; + } + + private function getCatalogueCachePath($locale, $options) + { + $catalogueHash = sha1(serialize(array( + 'resources' => $options['resources'], + 'fallback_locales' => $options['fallback_locales'], + ))); + + return sprintf('%s/catalogue.%s.%s.php', $this->cacheDir, $locale, $catalogueHash); + } +} diff --git a/src/Symfony/Component/Translation/MessageCacheInterface.php b/src/Symfony/Component/Translation/MessageCacheInterface.php new file mode 100644 index 000000000000..2981b1abc1d3 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCacheInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * @author Abdellatif Ait Boudad + */ +interface MessageCacheInterface +{ + /** + * Returns true if the cache is still fresh. + * + * @param string $locale + * @param array $options + * + * @return bool + */ + public function isFresh($locale, array $options = array()); + + /** + * Loads a catalogue. + * + * @param string $locale The locale + * + * @return MessageCatalogueInterface A MessageCatalogue instance + */ + public function load($locale); + + /** + * Dumps the message catalogue. + * + * @param MessageCatalogueInterface $messages The message catalogue + * @param array $options Options that are used by the dumper + */ + public function dump(MessageCatalogueInterface $messages, array $options = array()); +} diff --git a/src/Symfony/Component/Translation/Tests/MessageCatalogueTest.php b/src/Symfony/Component/Translation/Tests/MessageCatalogueTest.php index 7d956553d98c..84a4f08b25ad 100644 --- a/src/Symfony/Component/Translation/Tests/MessageCatalogueTest.php +++ b/src/Symfony/Component/Translation/Tests/MessageCatalogueTest.php @@ -17,21 +17,21 @@ class MessageCatalogueTest extends \PHPUnit_Framework_TestCase { public function testGetLocale() { - $catalogue = new MessageCatalogue('en'); + $catalogue = $this->getCatalogue('en'); $this->assertEquals('en', $catalogue->getLocale()); } public function testGetDomains() { - $catalogue = new MessageCatalogue('en', array('domain1' => array(), 'domain2' => array())); + $catalogue = $this->getCatalogue('en', array('domain1' => array(), 'domain2' => array())); $this->assertEquals(array('domain1', 'domain2'), $catalogue->getDomains()); } public function testAll() { - $catalogue = new MessageCatalogue('en', $messages = array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', $messages = array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $this->assertEquals(array('foo' => 'foo'), $catalogue->all('domain1')); $this->assertEquals(array(), $catalogue->all('domain88')); @@ -40,7 +40,7 @@ public function testAll() public function testHas() { - $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $this->assertTrue($catalogue->has('foo', 'domain1')); $this->assertFalse($catalogue->has('bar', 'domain1')); @@ -49,7 +49,7 @@ public function testHas() public function testGetSet() { - $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $catalogue->set('foo1', 'foo1', 'domain1'); $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); @@ -58,7 +58,7 @@ public function testGetSet() public function testAdd() { - $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $catalogue->add(array('foo1' => 'foo1'), 'domain1'); $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); @@ -74,7 +74,7 @@ public function testAdd() public function testReplace() { - $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $catalogue->replace($messages = array('foo1' => 'foo1'), 'domain1'); $this->assertEquals($messages, $catalogue->all('domain1')); @@ -88,10 +88,10 @@ public function testAddCatalogue() $r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); - $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $catalogue->addResource($r); - $catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo1' => 'foo1'))); + $catalogue1 = $this->getCatalogue('en', array('domain1' => array('foo1' => 'foo1'))); $catalogue1->addResource($r1); $catalogue->addCatalogue($catalogue1); @@ -110,10 +110,10 @@ public function testAddFallbackCatalogue() $r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); - $catalogue = new MessageCatalogue('en_US', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue = $this->getCatalogue('en_US', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); $catalogue->addResource($r); - $catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo' => 'bar', 'foo1' => 'foo1'))); + $catalogue1 = $this->getCatalogue('en', array('domain1' => array('foo' => 'bar', 'foo1' => 'foo1'))); $catalogue1->addResource($r1); $catalogue->addFallbackCatalogue($catalogue1); @@ -129,8 +129,8 @@ public function testAddFallbackCatalogue() */ public function testAddFallbackCatalogueWithCircularReference() { - $main = new MessageCatalogue('en_US'); - $fallback = new MessageCatalogue('fr_FR'); + $main = $this->getCatalogue('en_US'); + $fallback = $this->getCatalogue('fr_FR'); $fallback->addFallbackCatalogue($main); $main->addFallbackCatalogue($fallback); @@ -141,13 +141,13 @@ public function testAddFallbackCatalogueWithCircularReference() */ public function testAddCatalogueWhenLocaleIsNotTheSameAsTheCurrentOne() { - $catalogue = new MessageCatalogue('en'); - $catalogue->addCatalogue(new MessageCatalogue('fr', array())); + $catalogue = $this->getCatalogue('en'); + $catalogue->addCatalogue($this->getCatalogue('fr', array())); } public function testGetAddResource() { - $catalogue = new MessageCatalogue('en'); + $catalogue = $this->getCatalogue('en'); $r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); $catalogue->addResource($r); @@ -161,7 +161,7 @@ public function testGetAddResource() public function testMetadataDelete() { - $catalogue = new MessageCatalogue('en'); + $catalogue = $this->getCatalogue('en'); $this->assertEquals(array(), $catalogue->getMetadata('', ''), 'Metadata is empty'); $catalogue->deleteMetadata('key', 'messages'); $catalogue->deleteMetadata('', 'messages'); @@ -170,7 +170,7 @@ public function testMetadataDelete() public function testMetadataSetGetDelete() { - $catalogue = new MessageCatalogue('en'); + $catalogue = $this->getCatalogue('en'); $catalogue->setMetadata('key', 'value'); $this->assertEquals('value', $catalogue->getMetadata('key', 'messages'), "Metadata 'key' = 'value'"); @@ -186,15 +186,20 @@ public function testMetadataSetGetDelete() public function testMetadataMerge() { - $cat1 = new MessageCatalogue('en'); + $cat1 = $this->getCatalogue('en'); $cat1->setMetadata('a', 'b'); $this->assertEquals(array('messages' => array('a' => 'b')), $cat1->getMetadata('', ''), 'Cat1 contains messages metadata.'); - $cat2 = new MessageCatalogue('en'); + $cat2 = $this->getCatalogue('en'); $cat2->setMetadata('b', 'c', 'domain'); $this->assertEquals(array('domain' => array('b' => 'c')), $cat2->getMetadata('', ''), 'Cat2 contains domain metadata.'); $cat1->addCatalogue($cat2); $this->assertEquals(array('messages' => array('a' => 'b'), 'domain' => array('b' => 'c')), $cat1->getMetadata('', ''), 'Cat1 contains merged metadata.'); } + + protected function getCatalogue($locale, $messages = array()) + { + return new MessageCatalogue($locale, $messages); + } } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php index d5d4639984ce..d559817a85b9 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCache; class TranslatorCacheTest extends \PHPUnit_Framework_TestCase { @@ -62,13 +63,13 @@ public function testThatACacheIsUsed($debug) $msgid = 'test'; // Prime the cache - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, new ArrayLoader()); $translator->addResource($format, array($msgid => 'OK'), $locale); $translator->trans($msgid); // Try again and see we get a valid result whilst no loader can be used - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, $this->createFailingLoader()); $translator->addResource($format, array($msgid => 'OK'), $locale); $this->assertEquals('OK', $translator->trans($msgid), '-> caching does not work in '.($debug ? 'debug' : 'production')); @@ -85,7 +86,7 @@ public function testRefreshCacheWhenResourcesChange() )))) ; - $translator = new Translator('fr', null, $this->tmpDir, true); + $translator = $this->getTranslator('fr', true); $translator->setLocale('fr'); $translator->addLoader('loader', $loader); $translator->addResource('loader', 'foo', 'fr'); @@ -101,7 +102,7 @@ public function testRefreshCacheWhenResourcesChange() )))) ; - $translator = new Translator('fr', null, $this->tmpDir, true); + $translator = $this->getTranslator('fr', true); $translator->setLocale('fr'); $translator->addLoader('loader', $loader); $translator->addResource('loader', 'bar', 'fr'); @@ -126,7 +127,7 @@ public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh() $format = 'some_format'; $msgid = 'test'; - $catalogue = new MessageCatalogue($locale, array()); + $catalogue = $this->getCatalogue($locale, array()); $catalogue->addResource(new StaleResource()); // better use a helper class than a mock, because it gets serialized in the cache and re-loaded /** @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader */ @@ -138,13 +139,13 @@ public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh() ; // 1st pass - $translator = new Translator($locale, null, $this->tmpDir, true); + $translator = $this->getTranslator($locale, true); $translator->addLoader($format, $loader); $translator->addResource($format, null, $locale); $translator->trans($msgid); // 2nd pass - $translator = new Translator($locale, null, $this->tmpDir, true); + $translator = $this->getTranslator($locale, true); $translator->addLoader($format, $loader); $translator->addResource($format, null, $locale); $translator->trans($msgid); @@ -160,7 +161,7 @@ public function testDifferentTranslatorsForSameLocaleDoNotInterfere($debug) $msgid = 'test'; // Create a Translator and prime its cache - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, new ArrayLoader()); $translator->addResource($format, array($msgid => 'FAIL'), $locale); $translator->trans($msgid); @@ -169,7 +170,7 @@ public function testDifferentTranslatorsForSameLocaleDoNotInterfere($debug) * Create another Translator with the same locale but a different resource. * It should not use the first translator's cache but return the value from its own resource. */ - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, new ArrayLoader()); $translator->addResource($format, array($msgid => 'OK'), $locale); @@ -191,19 +192,19 @@ public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCac $msgid = 'test'; // Create a Translator and prime its cache - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, new ArrayLoader()); $translator->addResource($format, array($msgid => 'OK'), $locale); $translator->trans($msgid); // Create another Translator with a different catalogue for the same locale - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, new ArrayLoader()); $translator->addResource($format, array($msgid => 'FAIL'), $locale); $translator->trans($msgid); // Now the first translator must still have a useable cache. - $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator = $this->getTranslator($locale, $debug); $translator->addLoader($format, $this->createFailingLoader()); $translator->addResource($format, array($msgid => 'OK'), $locale); $this->assertEquals('OK', $translator->trans($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production')); @@ -216,7 +217,7 @@ public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales( * catalogues, we must take the set of fallback locales into consideration when * loading a catalogue from the cache. */ - $translator = new Translator('a', null, $this->tmpDir); + $translator = $this->getTranslator('a', false); $translator->setFallbackLocales(array('b')); $translator->addLoader('array', new ArrayLoader()); @@ -230,7 +231,7 @@ public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales( $this->assertEquals('bar', $translator->trans('bar')); // Use a fresh translator with no fallback locales, result should be the same - $translator = new Translator('a', null, $this->tmpDir); + $translator = $this->getTranslator('a', false); $translator->addLoader('array', new ArrayLoader()); $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); @@ -254,7 +255,7 @@ public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardless * Create a translator that loads two catalogues for two different locales. * The catalogues contain distinct sets of messages. */ - $translator = new Translator('a', null, $this->tmpDir); + $translator = $this->getTranslator('a', false); $translator->setFallbackLocales(array('b')); $translator->addLoader('array', new ArrayLoader()); @@ -272,7 +273,7 @@ public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardless * Now, repeat the same test. * Behind the scenes, the cache is used. But that should not matter, right? */ - $translator = new Translator('a', null, $this->tmpDir); + $translator = $this->getTranslator('a', false); $translator->setFallbackLocales(array('b')); $translator->addLoader('array', new ArrayLoader()); @@ -298,18 +299,25 @@ public function testRefreshCacheWhenResourcesAreNoLongerFresh() ->will($this->returnValue($this->getCatalogue('fr', array(), array($resource)))); // prime the cache - $translator = new Translator('fr', null, $this->tmpDir, true); + $translator = $this->getTranslator('fr', true); $translator->addLoader('loader', $loader); $translator->addResource('loader', 'foo', 'fr'); $translator->trans('foo'); // prime the cache second time - $translator = new Translator('fr', null, $this->tmpDir, true); + $translator = $this->getTranslator('fr', true); $translator->addLoader('loader', $loader); $translator->addResource('loader', 'foo', 'fr'); $translator->trans('foo'); } + protected function getTranslator($locale, $debug) + { + $cache = new MessageCache($this->tmpDir, $debug); + + return new Translator($locale, null, $cache); + } + protected function getCatalogue($locale, $messages, $resources = array()) { $catalogue = new MessageCatalogue($locale); diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php index 7ed987d41ee8..5157fef30824 100644 --- a/src/Symfony/Component/Translation/Translator.php +++ b/src/Symfony/Component/Translation/Translator.php @@ -13,9 +13,6 @@ use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Exception\NotFoundResourceException; -use Symfony\Component\Config\ConfigCacheInterface; -use Symfony\Component\Config\ConfigCacheFactoryInterface; -use Symfony\Component\Config\ConfigCacheFactory; /** * Translator. @@ -57,37 +54,29 @@ class Translator implements TranslatorInterface, TranslatorBagInterface private $selector; /** - * @var string - */ - private $cacheDir; - - /** - * @var bool - */ - private $debug; - - /** - * @var ConfigCacheFactoryInterface|null + * @var MessageCacheInterface */ - private $configCacheFactory; + private $cache; /** - * Constructor. - * - * @param string $locale The locale - * @param MessageSelector|null $selector The message selector for pluralization - * @param string|null $cacheDir The directory to use for the cache - * @param bool $debug Use cache in debug mode ? + * @param string $locale The locale + * @param MessageSelector|null $selector The message selector for pluralization + * @param string|null|MessageCacheInterface $cache The message cache or a directory to use for the default cache + * @param bool $debug Use cache in debug mode ? * * @throws \InvalidArgumentException If a locale contains invalid characters * * @api */ - public function __construct($locale, MessageSelector $selector = null, $cacheDir = null, $debug = false) + public function __construct($locale, MessageSelector $selector = null, $cache = null, $debug = false) { $this->setLocale($locale); $this->selector = $selector ?: new MessageSelector(); - $this->cacheDir = $cacheDir; + if (null !== $cache && !$cache instanceof MessageCacheInterface) { + $cache = new MessageCache($cache, $debug); + } + + $this->cache = $cache; $this->debug = $debug; } @@ -311,7 +300,11 @@ public function getMessages($locale = null) */ protected function loadCatalogue($locale) { - if (null === $this->cacheDir) { + if (isset($this->catalogues[$locale])) { + return; + } + + if (null === $this->cache) { $this->initializeCatalogue($locale); } else { $this->initializeCacheCatalogue($locale); @@ -345,91 +338,19 @@ private function initializeCacheCatalogue($locale) return; } - $this->assertValidLocale($locale); - $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($this->getCatalogueCachePath($locale), - function (ConfigCacheInterface $cache) use ($self, $locale) { - $self->dumpCatalogue($locale, $cache); - } - ); - - if (isset($this->catalogues[$locale])) { - /* Catalogue has been initialized as it was written out to cache. */ - return; - } - - /* Read catalogue from cache. */ - $this->catalogues[$locale] = include $cache->getPath(); - } - - /** - * 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(<<catalogues[$locale]->all(), true), - $fallbackContent - ); - - $cache->write($content, $this->catalogues[$locale]->getResources()); - } - - private function getFallbackContent(MessageCatalogue $catalogue) - { - $fallbackContent = ''; - $current = ''; - $replacementPattern = '/[^a-z0-9_]/i'; - $fallbackCatalogue = $catalogue->getFallbackCatalogue(); - while ($fallbackCatalogue) { - $fallback = $fallbackCatalogue->getLocale(); - $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback)); - $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current)); - - $fallbackContent .= sprintf(<<addFallbackCatalogue(\$catalogue%s); - -EOF - , - $fallbackSuffix, - $fallback, - var_export($fallbackCatalogue->all(), true), - $currentSuffix, - $fallbackSuffix - ); - $current = $fallbackCatalogue->getLocale(); - $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue(); - } - - return $fallbackContent; - } - - private function getCatalogueCachePath($locale) - { - $catalogueHash = sha1(serialize(array( + $options = array( 'resources' => isset($this->resources[$locale]) ? $this->resources[$locale] : array(), 'fallback_locales' => $this->fallbackLocales, - ))); + ); - return $this->cacheDir.'/catalogue.'.$locale.'.'.$catalogueHash.'.php'; + if (!$this->cache->isFresh($locale, $options)) { + $this->initializeCatalogue($locale); + foreach ($this->catalogues as $locale => $catalogue) { + $this->cache->dump($catalogue, $options); + } + } else { + $this->catalogues[$locale] = $this->cache->load($locale, $options); + } } private function doLoadCatalogue($locale) 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