feature #28929 [HttpKernel][Framework] Locale aware services (neghmurken)
This PR was merged into the 4.3-dev branch.
Discussion
----------
[HttpKernel][Framework] Locale aware services
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | yes
| Tests pass? | yes
| Fixed tickets |
| License | MIT
| Doc PR |
Added a `LocaleAwareInterface` (and also a new service tag `kernel.locale_aware`) to be implemented on services that require the current locale at request time.
Also, refactored the actual Translator service to implement the overmentioned interface
Todo :
* [ ] Documention PR (will be written after some feedback)
Commits
-------
b9ac645d8b
Locale aware service registration
This commit is contained in:
commit
9ed2f2b71f
@ -86,6 +86,7 @@ HttpKernel
|
||||
* Renamed `GetResponseForControllerResultEvent` to `ViewEvent`
|
||||
* Renamed `GetResponseForExceptionEvent` to `ExceptionEvent`
|
||||
* Renamed `PostResponseEvent` to `TerminateEvent`
|
||||
* Deprecated `TranslatorListener` in favor of `LocaleAwareListener`
|
||||
|
||||
Messenger
|
||||
---------
|
||||
|
@ -232,6 +232,7 @@ HttpKernel
|
||||
* Removed `GetResponseForControllerResultEvent`, use `ViewEvent` instead
|
||||
* Removed `GetResponseForExceptionEvent`, use `ExceptionEvent` instead
|
||||
* Removed `PostResponseEvent`, use `TerminateEvent` instead
|
||||
* Removed `TranslatorListener` in favor of `LocaleAwareListener`
|
||||
|
||||
Messenger
|
||||
---------
|
||||
|
@ -4,13 +4,14 @@ CHANGELOG
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* added `WebTestAssertions` trait (included by default in `WebTestCase`)
|
||||
* renamed `Client` to `KernelBrowser`
|
||||
* Added `WebTestAssertionsTrait` (included by default in `WebTestCase`)
|
||||
* Renamed `Client` to `KernelBrowser`
|
||||
* Not passing the project directory to the constructor of the `AssetsInstallCommand` is deprecated. This argument will
|
||||
be mandatory in 5.0.
|
||||
* Deprecated the "Psr\SimpleCache\CacheInterface" / "cache.app.simple" service, use "Symfony\Contracts\Cache\CacheInterface" / "cache.app" instead
|
||||
* Added the ability to specify a custom `serializer` option for each
|
||||
transport under`framework.messenger.transports`.
|
||||
* Added the `RegisterLocaleAwareServicesPass` and configured the `LocaleAwareListener`
|
||||
* [BC Break] When using Messenger, the default transport changed from
|
||||
using Symfony's serializer service to use `PhpSerializer`, which uses
|
||||
PHP's native `serialize()` and `unserialize()` functions. To use the
|
||||
|
@ -40,6 +40,7 @@ class UnusedTagsPass implements CompilerPassInterface
|
||||
'kernel.event_listener',
|
||||
'kernel.event_subscriber',
|
||||
'kernel.fragment_renderer',
|
||||
'kernel.locale_aware',
|
||||
'messenger.bus',
|
||||
'messenger.receiver',
|
||||
'messenger.message_handler',
|
||||
|
@ -119,6 +119,7 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\Service\ResetInterface;
|
||||
use Symfony\Contracts\Service\ServiceSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
|
||||
/**
|
||||
* FrameworkExtension.
|
||||
@ -370,6 +371,8 @@ class FrameworkExtension extends Extension
|
||||
->addTag('kernel.cache_warmer');
|
||||
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
|
||||
->addTag('kernel.event_subscriber');
|
||||
$container->registerForAutoconfiguration(LocaleAwareInterface::class)
|
||||
->addTag('kernel.locale_aware');
|
||||
$container->registerForAutoconfiguration(ResetInterface::class)
|
||||
->addTag('kernel.reset', ['method' => 'reset']);
|
||||
|
||||
|
@ -41,6 +41,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\ControllerArgumentValueReso
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RegisterControllerArgumentLocatorsPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
@ -121,6 +122,7 @@ class FrameworkBundle extends Bundle
|
||||
$this->addCompilerPassIfExists($container, FormPass::class);
|
||||
$container->addCompilerPass(new WorkflowGuardListenerPass());
|
||||
$container->addCompilerPass(new ResettableServicePass());
|
||||
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
|
||||
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
|
||||
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
|
||||
|
@ -112,5 +112,11 @@
|
||||
<argument type="service_locator" />
|
||||
</service>
|
||||
<service id="Symfony\Component\DependencyInjection\ReverseContainer" alias="reverse_container" />
|
||||
|
||||
<service id="locale_aware_listener" class="Symfony\Component\HttpKernel\EventListener\LocaleAwareListener">
|
||||
<argument type="collection" /> <!-- locale aware services -->
|
||||
<argument type="service" id="request_stack" />
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -19,6 +19,7 @@
|
||||
<call method="setConfigCacheFactory">
|
||||
<argument type="service" id="config_cache_factory" />
|
||||
</call>
|
||||
<tag name="kernel.locale_aware" />
|
||||
</service>
|
||||
<service id="Symfony\Component\Translation\TranslatorInterface" alias="translator" />
|
||||
<service id="Symfony\Contracts\Translation\TranslatorInterface" alias="translator" />
|
||||
@ -140,11 +141,5 @@
|
||||
<tag name="kernel.cache_warmer" />
|
||||
<argument type="service" id="Psr\Container\ContainerInterface" />
|
||||
</service>
|
||||
|
||||
<service id="translator_listener" class="Symfony\Component\HttpKernel\EventListener\TranslatorListener">
|
||||
<argument type="service" id="translator" />
|
||||
<argument type="service" id="request_stack" />
|
||||
<tag name="kernel.event_subscriber" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -8,7 +8,9 @@ CHANGELOG
|
||||
* `KernelInterface` doesn't extend `Serializable` anymore
|
||||
* deprecated the `Kernel::serialize()` and `unserialize()` methods
|
||||
* increased the priority of `Symfony\Component\HttpKernel\EventListener\AddRequestFormatsListener`
|
||||
* made `Symfony\Component\HttpKernel\EventListenerLocaleListener` set the default locale early
|
||||
* made `Symfony\Component\HttpKernel\EventListener\LocaleListener` set the default locale early
|
||||
* deprecated `TranslatorListener` in favor of `LocaleAwareListener`
|
||||
* added the registration of all `LocaleAwareInterface` implementations into the `LocaleAwareListener`
|
||||
* made `FileLinkFormatter` final and not implement `Serializable` anymore
|
||||
* the base `DataCollector` doesn't implement `Serializable` anymore, you should
|
||||
store all the serialized state in the data property instead
|
||||
|
@ -0,0 +1,58 @@
|
||||
<?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\HttpKernel\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Register all services that have the "kernel.locale_aware" tag into the listener.
|
||||
*
|
||||
* @author Pierre Bobiet <pierrebobiet@gmail.com>
|
||||
*/
|
||||
class RegisterLocaleAwareServicesPass implements CompilerPassInterface
|
||||
{
|
||||
private $listenerServiceId;
|
||||
private $localeAwareTag;
|
||||
|
||||
public function __construct(string $listenerServiceId = 'locale_aware_listener', string $localeAwareTag = 'kernel.locale_aware')
|
||||
{
|
||||
$this->listenerServiceId = $listenerServiceId;
|
||||
$this->localeAwareTag = $localeAwareTag;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->listenerServiceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$services = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->localeAwareTag) as $id => $tags) {
|
||||
$services[] = new Reference($id);
|
||||
}
|
||||
|
||||
if (!$services) {
|
||||
$container->removeDefinition($this->listenerServiceId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$container
|
||||
->getDefinition($this->listenerServiceId)
|
||||
->setArgument(0, new IteratorArgument($services))
|
||||
;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?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\HttpKernel\EventListener;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
|
||||
/**
|
||||
* Pass the current locale to the provided services.
|
||||
*
|
||||
* @author Pierre Bobiet <pierrebobiet@gmail.com>
|
||||
*/
|
||||
class LocaleAwareListener implements EventSubscriberInterface
|
||||
{
|
||||
private $localeAwareServices;
|
||||
private $requestStack;
|
||||
|
||||
/**
|
||||
* @param LocaleAwareInterface[] $localeAwareServices
|
||||
*/
|
||||
public function __construct(iterable $localeAwareServices, RequestStack $requestStack)
|
||||
{
|
||||
$this->localeAwareServices = $localeAwareServices;
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function onKernelRequest(RequestEvent $event): void
|
||||
{
|
||||
$this->setLocale($event->getRequest()->getLocale(), $event->getRequest()->getDefaultLocale());
|
||||
}
|
||||
|
||||
public function onKernelFinishRequest(FinishRequestEvent $event): void
|
||||
{
|
||||
if (null === $parentRequest = $this->requestStack->getParentRequest()) {
|
||||
$this->setLocale($event->getRequest()->getDefaultLocale());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setLocale($parentRequest->getLocale(), $parentRequest->getDefaultLocale());
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
// must be registered after the Locale listener
|
||||
KernelEvents::REQUEST => [['onKernelRequest', 15]],
|
||||
KernelEvents::FINISH_REQUEST => [['onKernelFinishRequest', -15]],
|
||||
];
|
||||
}
|
||||
|
||||
private function setLocale(string $locale, ?string $defaultLocale = null): void
|
||||
{
|
||||
foreach ($this->localeAwareServices as $service) {
|
||||
try {
|
||||
$service->setLocale($locale);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$service->setLocale($defaultLocale);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\HttpKernel\EventListener;
|
||||
|
||||
@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3 and will be removed in 5.0, use LocaleAwareListener instead.', TranslatorListener::class), E_USER_DEPRECATED);
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
@ -25,7 +27,7 @@ use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @final since Symfony 4.3
|
||||
* @deprecated since Symfony 4.3, use LocaleAwareListener instead
|
||||
*/
|
||||
class TranslatorListener implements EventSubscriberInterface
|
||||
{
|
||||
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\DependencyInjection;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\RegisterLocaleAwareServicesPass;
|
||||
use Symfony\Component\HttpKernel\EventListener\LocaleAwareListener;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
|
||||
class RegisterLocaleAwareServicesPassTest extends TestCase
|
||||
{
|
||||
public function testCompilerPass()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container->register('locale_aware_listener', LocaleAwareListener::class)
|
||||
->setPublic(true)
|
||||
->setArguments([null, null]);
|
||||
|
||||
$container->register('some_locale_aware_service', LocaleAwareInterface::class)
|
||||
->setPublic(true)
|
||||
->addTag('kernel.locale_aware');
|
||||
|
||||
$container->register('another_locale_aware_service', LocaleAwareInterface::class)
|
||||
->setPublic(true)
|
||||
->addTag('kernel.locale_aware');
|
||||
|
||||
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
|
||||
$container->compile();
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
new IteratorArgument([
|
||||
0 => new Reference('some_locale_aware_service'),
|
||||
1 => new Reference('another_locale_aware_service'),
|
||||
]),
|
||||
null,
|
||||
],
|
||||
$container->getDefinition('locale_aware_listener')->getArguments()
|
||||
);
|
||||
}
|
||||
|
||||
public function testListenerUnregisteredWhenNoLocaleAwareServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$container->register('locale_aware_listener', LocaleAwareListener::class)
|
||||
->setPublic(true)
|
||||
->setArguments([null, null]);
|
||||
|
||||
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
|
||||
$container->compile();
|
||||
|
||||
$this->assertFalse($container->hasDefinition('locale_aware_listener'));
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
<?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\HttpKernel\Tests\EventListener;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\HttpKernel\EventListener\LocaleAwareListener;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
|
||||
class LocaleAwareListenerTest extends TestCase
|
||||
{
|
||||
private $listener;
|
||||
private $localeAwareService;
|
||||
private $requestStack;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->localeAwareService = $this->getMockBuilder(LocaleAwareInterface::class)->getMock();
|
||||
$this->requestStack = new RequestStack();
|
||||
$this->listener = new LocaleAwareListener(new \ArrayIterator([$this->localeAwareService]), $this->requestStack);
|
||||
}
|
||||
|
||||
public function testLocaleIsSetInOnKernelRequest()
|
||||
{
|
||||
$this->localeAwareService
|
||||
->expects($this->once())
|
||||
->method('setLocale')
|
||||
->with($this->equalTo('fr'));
|
||||
|
||||
$event = new RequestEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST);
|
||||
$this->listener->onKernelRequest($event);
|
||||
}
|
||||
|
||||
public function testDefaultLocaleIsUsedOnExceptionsInOnKernelRequest()
|
||||
{
|
||||
$this->localeAwareService
|
||||
->expects($this->at(0))
|
||||
->method('setLocale')
|
||||
->will($this->throwException(new \InvalidArgumentException()));
|
||||
$this->localeAwareService
|
||||
->expects($this->at(1))
|
||||
->method('setLocale')
|
||||
->with($this->equalTo('en'));
|
||||
|
||||
$event = new RequestEvent($this->createHttpKernel(), $this->createRequest('fr'), HttpKernelInterface::MASTER_REQUEST);
|
||||
$this->listener->onKernelRequest($event);
|
||||
}
|
||||
|
||||
public function testLocaleIsSetInOnKernelFinishRequestWhenParentRequestExists()
|
||||
{
|
||||
$this->localeAwareService
|
||||
->expects($this->once())
|
||||
->method('setLocale')
|
||||
->with($this->equalTo('fr'));
|
||||
|
||||
$this->requestStack->push($this->createRequest('fr'));
|
||||
$this->requestStack->push($subRequest = $this->createRequest('de'));
|
||||
|
||||
$event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
|
||||
$this->listener->onKernelFinishRequest($event);
|
||||
}
|
||||
|
||||
public function testLocaleIsSetToDefaultOnKernelFinishRequestWhenParentRequestDoesNotExist()
|
||||
{
|
||||
$this->localeAwareService
|
||||
->expects($this->once())
|
||||
->method('setLocale')
|
||||
->with($this->equalTo('en'));
|
||||
|
||||
$this->requestStack->push($subRequest = $this->createRequest('de'));
|
||||
|
||||
$event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
|
||||
$this->listener->onKernelFinishRequest($event);
|
||||
}
|
||||
|
||||
public function testDefaultLocaleIsUsedOnExceptionsInOnKernelFinishRequest()
|
||||
{
|
||||
$this->localeAwareService
|
||||
->expects($this->at(0))
|
||||
->method('setLocale')
|
||||
->will($this->throwException(new \InvalidArgumentException()));
|
||||
$this->localeAwareService
|
||||
->expects($this->at(1))
|
||||
->method('setLocale')
|
||||
->with($this->equalTo('en'));
|
||||
|
||||
$this->requestStack->push($this->createRequest('fr'));
|
||||
$this->requestStack->push($subRequest = $this->createRequest('de'));
|
||||
|
||||
$event = new FinishRequestEvent($this->createHttpKernel(), $subRequest, HttpKernelInterface::SUB_REQUEST);
|
||||
$this->listener->onKernelFinishRequest($event);
|
||||
}
|
||||
|
||||
private function createHttpKernel()
|
||||
{
|
||||
return $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface')->getMock();
|
||||
}
|
||||
|
||||
private function createRequest($locale)
|
||||
{
|
||||
$request = new Request();
|
||||
$request->setLocale($locale);
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
@ -19,6 +19,9 @@ use Symfony\Component\HttpKernel\EventListener\TranslatorListener;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class TranslatorListenerTest extends TestCase
|
||||
{
|
||||
private $listener;
|
||||
|
Reference in New Issue
Block a user