I am trying to set a locale in Symfony and everything works fine except the places where I try to translate or generate route in the layout file.
I created a listener so I can set the locale on every request.
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LocaleListener implements EventSubscriberInterface {
private $container;
private $defaultLocale;
public function __construct(ContainerInterface $container, $defaultLocale = 'en') {
$this->container = $container;
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
$loc = $this->defaultLocale;
if ($locale = $request->attributes->get('_locale')) {
//$request->getSession()->set('lang', $locale);
$event->getRequest()->getSession()->set('_locale', $locale);
$event->getRequest()->setLocale($locale);
$loc = $locale;
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($this->defaultLocale);
$event->getRequest()->getSession()->set('_locale', $this->defaultLocale);
$loc = $this->defaultLocale;
}
$this->container->get('translator')->setLoc($loc);
}
public static function getSubscribedEvents() {
return array(
// must be registered after the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
);
}
}
If in the twig template I try to access the session variable with app.session.get('_locale') that stores the locale it is as it should be - I get the desired result. But when I try app.request.locale I get the default locale not the one that is in the url. And because of that all my routes are prefixed with the default locale and everything is translated with the default language. How can I get the locale from the url in my layout and generate routes and translations with it?
You probably should use only one way to handle the locale translation.
I implemented this action
/**
* #Route("/{_locale}")
*/
$request->setLocale('en');
$request->getSession()->set('_locale','fr');
$localesArray = [
'request' => $request->getLocale(),
'request_default' => $request->getDefaultLocale(),
'config_default' => $this->getParameter('kernel.default_locale'),
'session_locale' => $request->getSession()->get('_locale'),
'request_attributes' => $request->attributes->get('_locale')
];
var_dump($localesArray);
die();
An the answer is this one
array(5) {
["request"]=> string(2) "en"
["request_default"]=> string(2) "nl"
["config_default"]=> string(2) "nl"
["session_locale"]=> string(2) "fr"
["request_attributes"]=> string(2) "es"
}
As you can see when I changed the locale by setLocale did not affect the other results. I get the result accessi this url "/es" and my default locale for this test was "nl". Compare results
My advice: work only with request locale as describe this doc
Related
I know how to generate an URL for a route. However now I need to generate an URL for a controller or for a controller with a method. I checked the sourced of UrlGenerator but did not find any relevant information. No information in Symfony docs as well.
The method of the controller has an associate url. This url will be used in controller but I need the generator to be a service.
Basically you need to:
1. Add a route to the controller
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
final class BlogController extends AbstractController
{
/**
* #Route(path="blog", name="blog")
*/
public function __invoke(): Response
{
return $this->render('blog/blog.twig');
}
}
2. Generate url route for a route
See Symfony docs
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
/**
* #var UrlGeneratorInterface
*/
private $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function go()
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generateUrl('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generateUrl('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->urlGenerator->generateUrl('sign_up', ['_locale' => 'nl']);
}
}
So, here is the service. At least an example of how it could implemented. SF4
namespace App\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
class UrlGenerator
{
/**
* #var RouteCollection
*/
private $collection;
public function __construct(RouterInterface $router)
{
$this->collection = $router->getRouteCollection();
}
public function generate(string $controllerClass, string $method): string
{
foreach ($this->collection as $item) {
$defaults = $item->getDefaults();
$controllerPath = $defaults['_controller'];
$parts = explode('::', $controllerPath);
if ($parts[0] !== $controllerClass) {
continue;
}
if ($parts[1] !== $method) {
continue;
}
return $item->getPath();
}
throw new \RuntimeException(
'Route for such combination of controller and method is absent'
);
}
}
Poorly tested but working solution.
Question: How to get the form_login.check_path by given firewall name?
We subscribe to Symfony\Component\Security\Http\SecurityEvent::INTERACTIVE_LOGIN in order to log successful logins inside an Application that has multiple firewalls.
One firewall uses JWT tokens via Guard authentication which has the negative effect that this event is triggered for every request with a valid token.
We have currently solved this by manually checking whether the current route matches the firewall's check-path and stopping the event-propagation together with an early return otherwise.
As we're adding more firewalls (with different tokens) I'd like to solve this more generally. Therefore I want to check whether the current route matches the current firewalls check-path without hardcoding any route or firewall-name.
There is a class to generate Logout URLs for the current firewall used by Twig logout_path() method which gets the logout route/path from the firewall listeners somehow. (Symfony\Component\Security\Http\Logout\LogoutUrlGenerator)
Before I hop into a long debugging session I thought maybe someone has solved this case before ;)
Any ideas?
Example code:
class UserEventSubscriber implements EventSubscriberInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var FirewallMapInterface|FirewallMap */
protected $firewallMap;
public function __construct(LoggerInterface $logger, FirewallMapInterface $firewallMap)
{
$this->logger = $logger;
$this->firewallMap = $firewallMap;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$routeName = $request->get('_route');
if (('firewall_jwt' === $firewallName) && ('firewall_jwt_login_check' !== $routeName)) {
$event->stopPropagation();
return;
}
$this->logger->info(
'A User has logged in interactively.',
array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUuid(),
));
The check_path option is only available from authentication factory/listener, so you could pass this configuration manually to the subscriber class while the container is building.
This solution take account that check_path could be a route name or path, that's why HttpUtils service is injected too:
namespace AppBundle\Subscriber;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;
class UserEventSubscriber implements EventSubscriberInterface
{
private $logger;
private $httpUtils;
private $firewallMap;
private $checkPathsPerFirewall;
public function __construct(LoggerInterface $logger, HttpUtils $httpUtils, FirewallMapInterface $firewallMap, array $checkPathsPerFirewall)
{
$this->logger = $logger;
$this->httpUtils = $httpUtils;
$this->firewallMap = $firewallMap;
$this->checkPathsPerFirewall = $checkPathsPerFirewall;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$checkPath = $this->checkPathsPerFirewall[$firewallName];
if (!$this->httpUtils->checkRequestPath($request, $checkPath)) {
$event->stopPropagation();
return;
}
$this->logger->info('A User has logged in interactively.', array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUsername(),
));
}
public static function getSubscribedEvents()
{
return [SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'];
}
}
After regiter this subscriber as service (AppBundle\Subscriber\UserEventSubscriber) we need implement PrependExtensionInterface in your DI extension to be able to access the security configuration and complete the subscriber definition with the check paths per firewall:
namespace AppBundle\DependencyInjection;
use AppBundle\Subscriber\UserEventSubscriber;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
class AppExtension extends Extension implements PrependExtensionInterface
{
// ...
public function prepend(ContainerBuilder $container)
{
$checkPathsPerFirewall = [];
$securityConfig = $container->getExtensionConfig('security');
foreach ($securityConfig[0]['firewalls'] as $name => $config) {
if (isset($config['security']) && false === $config['security']) {
continue; // skip firewalls without security
}
$checkPathsPerFirewall[$name] = isset($config['form_login']['check_path'])
? $config['form_login']['check_path']
: '/login_check'; // default one in Symfony
}
$subscriber = $container->getDefinition(UserEventSubscriber::class);
$subscriber->setArgument(3, $checkPathsPerFirewall);
}
}
I hope it fits your need.
for PHP8
In __construct :
public function __construct(
private RequestStack $requestStack,
private FirewallMapInterface $firewallMap
)
{
}
use this :
$firewallName = $this->firewallMap->getFirewallConfig($this->requestStack->getCurrentRequest())->getName();
In my config.yml if have the following setting
parameters:
locale: en
framework:
translator: { fallbacks: ["%locale%"] }
In my controller I change the locale to French like this:
$request->setLocale('fr')
When I want to translate something in Twig like this{{ 'Something' | trans }} It doesn't show the text in French, even though in my view this {{ dump(app.request.locale) }} gives me fr.
So there is something wrong.
Only when I change the locale in my config.yml to fr like this:
parameters:
locale: fr
then I see the text in French.
Any suggestions?
I ended up creating a localeListener that sets the locale in the session when setting the locale in the controller like this $request->setLocale('fr'). Now my text gets translated in the corresponding locale, but on only after an extra page refresh.
namespace SiteBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
);
}
}
I want to store the last locale used by a user on every Request the user makes. I already created a Field in the Database and only need the best practice way without big modifications in every Controller.
Thank you all.
You could store your locale in session and use it with an event listener. And on user login set user locale in session.
-LocaleListener : Store the locale in session and user locale in session
-UserLocaleListener : On user login set user locale in session
<?php
namespace YourApp\YourBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest17(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest17', 17)),
);
}
}
Second service:
<?php
namespace YourApp\YourBundle\EventListener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class UserLocaleListener
{
private $session;
public function setSession(Session $session)
{
$this->session = $session;
}
/**
* kernel.request event. If a guest user doesn't have an opened session, locale is equal to
* "undefined" as configured by default in parameters.ini. If so, set as a locale the user's
* preferred language.
*
* #param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
*/
public function setLocaleForUnauthenticatedUser(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if ('undefined' == $request->getLocale()) {
if ($locale = $request->getSession()->get('_locale')) {
$request->setLocale($locale);
} else {
$request->setLocale($request->getPreferredLanguage());
}
}
}
/**
* security.interactive_login event. If a user chose a language in preferences, it would be set,
* if not, a locale that was set by setLocaleForUnauthenticatedUser remains.
*
* #param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event
*/
public function setLocaleForAuthenticatedUser(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($lang = $user->getLocale()) {
$event->getRequest()->setLocale($lang);
$this->session->set('_locale', $lang);
}
}
}
services.yml
services:
yourapp_your.locale_listener:
class: YourApp\YourBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
yourapp_your.locale.interactive_login_listener:
class: YourApp\YourBundle\EventListener\UserLocaleListener
calls:
- [ setSession, [#session] ]
tags:
- { name: kernel.event_listener, event: security.interactive_login, method: setLocaleForAuthenticatedUser }
This is using
https://github.com/symfony/symfony/blob/master/UPGRADE-2.1.md#simulate-old-behavior
Translations in Symfony 2.3 locale in request
Symfony2 locale detection: not considering _locale in session
symfony 2 set locale based on user preferences stored in DB
That is my implementation to save the locale on each request in the database.
It is necessary for me because i need the locale persisted for the newsletter.
LocaleListener:
namespace MyApp\MyBundle\EventListener;
use \Symfony\Component\HttpKernel\Event\GetResponseEvent;
use \Symfony\Component\HttpKernel\KernelEvents;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use \FOS\UserBundle\Model\UserManagerInterface;
use \Symfony\Component\Security\Core\SecurityContext;
use \MyApp\MyBundle\Entity\User;
class LocaleListener implements EventSubscriberInterface
{
private $userManager;
private $securityContext;
private $defaultLocale;
public function __construct(UserManagerInterface $userManager, SecurityContext $securityContext, $defaultLocale = 'en')
{
$this->userManager = $userManager;
$this->securityContext = $securityContext;
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest17(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
$locale = $request->attributes->get('_locale');
if ($locale !== null)
{
$request->getSession()->set('_locale', $locale);
}
else
{
// if no explicit locale has been set on this request, use one from the session
$locale = $request->getSession()->get('_locale', $this->defaultLocale);
$request->setLocale($locale);
}
// save last locale to the user if he is logged in
$user = $this->getUser();
if($user instanceof User)
{
$user->setDefaultLanguage($locale);
$this->userManager->updateUser($user);
}
}
private function getUser()
{
$token = $this->securityContext->getToken();
if($token !== null)
return $this->securityContext->getToken()->getUser();
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest17', 17)),
);
}
}
services.yml:
myApp_myBundle.locale_listener:
class: MyApp\MyBundle\EventListener\LocaleListener
arguments: [#fos_user.user_manager, #security.context, "%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
How can I change locale in Symfony 2.3 ?
I created this controller:
public function changelocaleAction($lang)
{
$request = $this->get('request');
$request->setLocale($lang);
return $this->redirect($request->headers->get('referer'));
}
It doesn't show changes when the page is refreshed. Why?
Based on Symfony2 documentation:
namespace Acme\LocaleBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
// if no explicit locale has been set on this request, use one from the session
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
services.yml
services:
acme_locale.locale_listener:
class: Acme\LocaleBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
Finally, you can use in your controller:
$locale = $this->getRequest()->getLocale();
In this link you have a very similar question.