[FrameworkBundle] Make Translator works with any PSR-11 container

This commit is contained in:
Robin Chalas 2017-03-15 16:59:02 +01:00
parent 4149eab349
commit 85177a649e
10 changed files with 222 additions and 26 deletions

View File

@ -180,6 +180,9 @@ FrameworkBundle
Require `symfony/web-server-bundle` in your composer.json and register
`Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them.
* The `Symfony\Bundle\FrameworkBundle\Translation\Translator` constructor now takes the
default locale as 3rd argument. Not passing it will trigger an error in 4.0.
HttpKernel
-----------

View File

@ -274,6 +274,9 @@ FrameworkBundle
class has been removed. Use the
`Symfony\Component\Routing\DependencyInjection\RoutingResolverPass` class instead.
* The `Symfony\Bundle\FrameworkBundle\Translation\Translator` constructor now takes the
default locale as mandatory 3rd argument.
HttpFoundation
---------------

View File

@ -35,6 +35,8 @@ CHANGELOG
`server:status` console commands have been moved to a dedicated bundle.
Require `symfony/web-server-bundle` in your composer.json and register
`Symfony\Bundle\WebServerBundle\WebServerBundle` in your AppKernel to use them.
* Added `$defaultLocale` as 3rd argument of `Translator::__construct()`
making `Translator` works with any PSR-11 container
3.2.0
-----

View File

@ -11,9 +11,11 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
class TranslatorPass implements CompilerPassInterface
{
@ -24,7 +26,9 @@ class TranslatorPass implements CompilerPassInterface
}
$loaders = array();
$loaderRefs = array();
foreach ($container->findTaggedServiceIds('translation.loader') as $id => $attributes) {
$loaderRefs[$id] = new Reference($id);
$loaders[$id][] = $attributes[0]['alias'];
if (isset($attributes[0]['legacy-alias'])) {
$loaders[$id][] = $attributes[0]['legacy-alias'];
@ -35,11 +39,15 @@ class TranslatorPass implements CompilerPassInterface
$definition = $container->getDefinition('translation.loader');
foreach ($loaders as $id => $formats) {
foreach ($formats as $format) {
$definition->addMethodCall('addLoader', array($format, new Reference($id)));
$definition->addMethodCall('addLoader', array($format, $loaderRefs[$id]));
}
}
}
$container->findDefinition('translator.default')->replaceArgument(2, $loaders);
$container
->findDefinition('translator.default')
->replaceArgument(0, (new Definition(ServiceLocator::class, array($loaderRefs)))->addTag('container.service_locator'))
->replaceArgument(3, $loaders)
;
}
}

View File

@ -954,11 +954,11 @@ class FrameworkExtension extends Extension
}
$options = array_merge(
$translator->getArgument(3),
$translator->getArgument(4),
array('resource_files' => $files)
);
$translator->replaceArgument(3, $options);
$translator->replaceArgument(4, $options);
}
}

View File

@ -6,14 +6,14 @@
<services>
<service id="translator.default" class="Symfony\Bundle\FrameworkBundle\Translation\Translator">
<argument type="service" id="service_container" />
<argument /> <!-- translation loaders locator -->
<argument type="service" id="translator.selector" />
<argument type="collection" /> <!-- translation loaders -->
<argument>%kernel.default_locale%</argument>
<argument type="collection" /> <!-- translation loaders ids -->
<argument type="collection">
<argument key="cache_dir">%kernel.cache_dir%/translations</argument>
<argument key="debug">%kernel.debug%</argument>
</argument>
<argument type="collection" /> <!-- translation resources -->
<call method="setConfigCacheFactory">
<argument type="service" id="config_cache_factory" />
</call>

View File

@ -12,8 +12,10 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
use Symfony\Component\DependencyInjection\ServiceLocator;
class TranslatorPassTest extends TestCase
{
@ -39,7 +41,15 @@ class TranslatorPassTest extends TestCase
->will($this->returnValue(array('xliff' => array(array('alias' => 'xliff', 'legacy-alias' => 'xlf')))));
$container->expects($this->once())
->method('findDefinition')
->will($this->returnValue($this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock()));
->will($this->returnValue($translator = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock()));
$translator->expects($this->at(0))
->method('replaceArgument')
->with(0, $this->equalTo((new Definition(ServiceLocator::class, array(array('xliff' => new Reference('xliff')))))->addTag('container.service_locator')))
->willReturn($translator);
$translator->expects($this->at(1))
->method('replaceArgument')
->with(3, array('xliff' => array('xliff', 'xlf')))
->willReturn($translator);
$pass = new TranslatorPass();
$pass->process($container);
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
use Doctrine\Common\Annotations\Annotation;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass;
use Symfony\Bundle\FullStack;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass;
@ -417,7 +418,7 @@ abstract class FrameworkExtensionTest extends TestCase
$container = $this->createContainerFromFile('full');
$this->assertTrue($container->hasDefinition('translator.default'), '->registerTranslatorConfiguration() loads translation.xml');
$this->assertEquals('translator.default', (string) $container->getAlias('translator'), '->registerTranslatorConfiguration() redefines translator service from identity to real translator');
$options = $container->getDefinition('translator.default')->getArgument(3);
$options = $container->getDefinition('translator.default')->getArgument(4);
$files = array_map('realpath', $options['resource_files']['en']);
$ref = new \ReflectionClass('Symfony\Component\Validator\Validation');
@ -922,7 +923,7 @@ abstract class FrameworkExtensionTest extends TestCase
$container->getCompilerPassConfig()->setOptimizationPasses(array());
$container->getCompilerPassConfig()->setRemovingPasses(array());
}
$container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddAnnotationsCachedReaderPass(), new AddConstraintValidatorsPass()));
$container->getCompilerPassConfig()->setBeforeRemovingPasses(array(new AddAnnotationsCachedReaderPass(), new AddConstraintValidatorsPass(), new TranslatorPass()));
$container->compile();
return self::$containerCache[$cacheKey] = $container;

View File

@ -12,6 +12,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Translation;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Filesystem\Filesystem;
@ -42,6 +43,157 @@ class TranslatorTest extends TestCase
$fs->remove($dir);
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testTransWithoutCachingOmittingLocale()
{
$translator = $this->getTranslator($this->getLoader(), array(), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testTransWithCachingOmittingLocale()
{
// prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
// do it another time as the cache is primed now
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$loader->expects($this->never())->method('load');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar'));
$this->assertEquals('foobar (ES)', $translator->trans('foobar'));
$this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0));
$this->assertEquals('no translation', $translator->trans('no translation'));
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
* @expectedException \InvalidArgumentException
*/
public function testTransWithCachingWithInvalidLocaleOmittingLocale()
{
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir), 'loader', '\Symfony\Bundle\FrameworkBundle\Tests\Translation\TranslatorWithInvalidLocale', null);
$translator->trans('foo');
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testLoadResourcesWithoutCachingOmittingLocale()
{
$loader = new \Symfony\Component\Translation\Loader\YamlFileLoader();
$resourceFiles = array(
'fr' => array(
__DIR__.'/../Fixtures/Resources/translations/messages.fr.yml',
),
);
$translator = $this->getTranslator($loader, array('resource_files' => $resourceFiles), 'yml', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$this->assertEquals('répertoire', $translator->trans('folder'));
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testGetDefaultLocaleOmittingLocale()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->once())
->method('getParameter')
->with('kernel.default_locale')
->will($this->returnValue('en'))
;
$translator = new Translator($container, new MessageSelector());
$this->assertSame('en', $translator->getLocale());
}
/**
* @group legacy
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Missing third $defaultLocale argument.
*/
public function testGetDefaultLocaleOmittingLocaleWithPsrContainer()
{
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$translator = new Translator($container, new MessageSelector());
}
/**
* @group legacy
* @expectedDeprecation Method Symfony\Bundle\FrameworkBundle\Translation\Translator::__construct() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.
*/
public function testWarmupOmittingLocale()
{
$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', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setFallbackLocales(array('fr'));
$translator->warmup($this->tmpDir);
$loader = $this->getMockBuilder('Symfony\Component\Translation\Loader\LoaderInterface')->getMock();
$loader
->expects($this->never())
->method('load');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir, 'resource_files' => $resourceFiles), 'yml', '\Symfony\Bundle\FrameworkBundle\Translation\Translator', null);
$translator->setLocale('fr');
$translator->setFallbackLocales(array('fr'));
$this->assertEquals('répertoire', $translator->trans('folder'));
}
public function testTransWithoutCaching()
{
$translator = $this->getTranslator($this->getLoader());
@ -97,6 +249,7 @@ class TranslatorTest extends TestCase
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid "invalid locale" locale.
*/
public function testTransWithCachingWithInvalidLocale()
{
@ -123,15 +276,8 @@ class TranslatorTest extends TestCase
public function testGetDefaultLocale()
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
$container
->expects($this->once())
->method('getParameter')
->with('kernel.default_locale')
->will($this->returnValue('en'))
;
$translator = new Translator($container, new MessageSelector());
$container = $this->getMockBuilder(ContainerInterface::class)->getMock();
$translator = new Translator($container, new MessageSelector(), 'en');
$this->assertSame('en', $translator->getLocale());
}
@ -144,7 +290,7 @@ class TranslatorTest extends TestCase
{
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();
(new Translator($container, new MessageSelector(), array(), array('foo' => 'bar')));
(new Translator($container, new MessageSelector(), 'en', array(), array('foo' => 'bar')));
}
protected function getCatalogue($locale, $messages, $resources = array())
@ -230,9 +376,9 @@ class TranslatorTest extends TestCase
return $container;
}
public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator')
public function getTranslator($loader, $options = array(), $loaderFomat = 'loader', $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $defaultLocale = 'en')
{
$translator = $this->createTranslator($loader, $options, $translatorClass, $loaderFomat);
$translator = $this->createTranslator($loader, $options, $translatorClass, $loaderFomat, $defaultLocale);
if ('loader' === $loaderFomat) {
$translator->addResource('loader', 'foo', 'fr');
@ -272,11 +418,21 @@ class TranslatorTest extends TestCase
$this->assertEquals('répertoire', $translator->trans('folder'));
}
private function createTranslator($loader, $options, $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $loaderFomat = 'loader')
private function createTranslator($loader, $options, $translatorClass = '\Symfony\Bundle\FrameworkBundle\Translation\Translator', $loaderFomat = 'loader', $defaultLocale = 'en')
{
if (null === $defaultLocale) {
return new $translatorClass(
$this->getContainer($loader),
new MessageSelector(),
array($loaderFomat => array($loaderFomat)),
$options
);
}
return new $translatorClass(
$this->getContainer($loader),
new MessageSelector(),
$defaultLocale,
array($loaderFomat => array($loaderFomat)),
$options
);

View File

@ -11,10 +11,11 @@
namespace Symfony\Bundle\FrameworkBundle\Translation;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Translation\Translator as BaseTranslator;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
@ -54,8 +55,20 @@ class Translator extends BaseTranslator implements WarmableInterface
*
* @throws InvalidArgumentException
*/
public function __construct(ContainerInterface $container, MessageSelector $selector, $loaderIds = array(), array $options = array())
public function __construct(ContainerInterface $container, MessageSelector $selector, $defaultLocale = null, array $loaderIds = array(), array $options = array())
{
// BC 3.x, to be removed in 4.0 along with the $defaultLocale default value
if (is_array($defaultLocale) || 3 > func_num_args()) {
if (!$container instanceof SymfonyContainerInterface) {
throw new \InvalidArgumentException('Missing third $defaultLocale argument.');
}
$options = $loaderIds;
$loaderIds = $defaultLocale;
$defaultLocale = $container->getParameter('kernel.default_locale');
@trigger_error(sprintf('Method %s() takes the default locale as 3rd argument since version 3.3. Not passing it is deprecated and will trigger an error in 4.0.', __METHOD__), E_USER_DEPRECATED);
}
$this->container = $container;
$this->loaderIds = $loaderIds;
@ -70,7 +83,7 @@ class Translator extends BaseTranslator implements WarmableInterface
$this->loadResources();
}
parent::__construct($container->getParameter('kernel.default_locale'), $selector, $this->options['cache_dir'], $this->options['debug']);
parent::__construct($defaultLocale, $selector, $this->options['cache_dir'], $this->options['debug']);
}
/**