diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index ba8ab75cb2..110e58ed65 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -86,6 +86,7 @@ HttpKernel * Renamed `GetResponseForControllerResultEvent` to `ViewEvent` * Renamed `GetResponseForExceptionEvent` to `ExceptionEvent` * Renamed `PostResponseEvent` to `TerminateEvent` + * Deprecated `TranslatorListener` in favor of `LocaleAwareListener` Messenger --------- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 00a819543d..7c4a38aba2 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -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 --------- diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 3d19141469..d38ddc7e6d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -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 diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 2e433cdc6c..efeafad5f0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -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', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4831af368e..f3491d3569 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -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']); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index c7b37222ae..97b4ef28a6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -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); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml index 7f4f5890fa..7455ae8833 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml @@ -112,5 +112,11 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml index 8853914f65..a9d5a85d20 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -19,6 +19,7 @@ + @@ -140,11 +141,5 @@ - - - - - - diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 310f9818d8..06193a00b3 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -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 diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php new file mode 100644 index 0000000000..0efb164b72 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterLocaleAwareServicesPass.php @@ -0,0 +1,58 @@ + + * + * 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 + */ +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)) + ; + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php new file mode 100644 index 0000000000..325b8cbc0d --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php @@ -0,0 +1,76 @@ + + * + * 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 + */ +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); + } + } + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php index 4d022f750c..d28eee2b1a 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/TranslatorListener.php @@ -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 * - * @final since Symfony 4.3 + * @deprecated since Symfony 4.3, use LocaleAwareListener instead */ class TranslatorListener implements EventSubscriberInterface { diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php new file mode 100644 index 0000000000..aa3c6aa0c4 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterLocaleAwareServicesPassTest.php @@ -0,0 +1,59 @@ +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')); + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php new file mode 100644 index 0000000000..489b02151c --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php @@ -0,0 +1,119 @@ + + * + * 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; + } +} diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php index 3fecf9aab3..1627a2b191 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TranslatorListenerTest.php @@ -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;