How to change the user locale with Symfony 4? - php

I'm trying to change the user locale with Symfony from a field "locale" in the database. I read the Symfony manual (how to sticky a session for example), but nothing works in my application. Translator still gets the default locale...
I created listeners, subscribers... to dynamically change the locale, but as they are loaded before the firewall listener, I'm unable to change the current value.
I tried to change the priority subscriber, but I lost the user entity. I tried to set locale request in controllers, but I think it's too late.
I don't want to add locales in URLs.
Here my subscriber - listener - code:
public function onKernelRequest(RequestEvent $event)
{
$user = $this->tokenStorage->getToken()->getUser();
$request = $event->getRequest();
$request->setLocale($user->getLocale());
}
In subscribers, I added:
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 0]],
];
}
Here, my full code:
framework.yml:
default_locale: fr
services.yml:
parameters:
locale: 'fr'
app_locales: fr|en|
translation.yml:
framework:
default_locale: '%locale%'
translator:
paths:
- '%kernel.project_dir%/translations'
fallbacks:
- '%locale%'
LocaleSubscriber.php:
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LocaleSubscriber implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(RequestEvent $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 [
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::REQUEST => [['onKernelRequest', 20]],
];
}
}
UserLocaleSubscriber.php
// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleSubscriber afterwards.
*/
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $session;
public function __construct(SessionInterface $session)
{
$this->session = $session;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->session->set('_locale', $user->getLocale());
}
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
];
}
}
Ex controller annotation:
/**
* #Route("/user/locale", name="user_locale", requirements={"_locale" = "%app.locales%"})
* #Route("/{_locale}/user/locale", name="user_locale_locale", requirements={"_locale" = "%app.locales%"})
*/

Find the priority of the firewall listener using debug:event kernel.request.
Make sure your UserLocaleSubscriber is executed right after the firewall listener.
Autowire the TranslatorInterface and manually set the translator locale.
// src/EventSubscriber/UserLocaleSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleSubscriber afterwards.
*/
class UserLocaleSubscriber implements EventSubscriberInterface
{
private $session;
private $translator;
public function __construct(SessionInterface $session, TranslatorInterface $translator)
{
$this->session = $session;
$this->translator = $translator;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->translator->setLocale($user->getLocale());
}
}
public static function getSubscribedEvents()
{
return [
SecurityEvents::INTERACTIVE_LOGIN => ['onInteractiveLogin', 7]
];
}
}

It's hard to help without seeing the full code you are using.
Symfony has it's own LocaleListener as well. Make sure your's is executed first.
/**
* #return array
*/
public static function getSubscribedEvents()
{
return [
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::REQUEST => [['onKernelRequest', 20]],
];
}

Related

Redirect from event subscriber

I want to redirect user conditionnaly from my event subscriber on kernel.controller event and change it if user can't acces the one asked.
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\RouterInterface;
class WebPageAccessSuscriber implements EventSubscriberInterface
{
/**
* #var SessionInterface
*/
private $session;
/**
* Contient l'ensemble des parametres de config
*
* #var ParameterBagInterface
*/
private $params;
private $router;
public function __construct(SessionInterface $session, ParameterBagInterface $params, RouterInterface $router)
{
// Retrieve the client session
$this->session = $session;
// Retrieve configuration variable
$this->params = $params;
$this->router = $router;
}
public function onKernelController(ControllerEvent $event)
{
error_log(__METHOD__);
$controller = $event->getController();
if(!is_array($controller)) return;
if ($this->params->get('visitor_access') === false && $controller[0] instanceof \App\Controller\restictedController) {
$event->setController(function() use ($event) {
return new RedirectResponse($this->router->generate('login_' . $event->getRequest()->getLocale()));
});
}
}
public static function getSubscribedEvents()
{
return [
// must be registered before (i.e. with a higher priority than) the default Locale listener
KernelEvents::CONTROLLER => [['onKernelController']]
];
}
}
It works but symfony web profiler doesn't shows anymore.
I mean it does but his content is the one from my login template
Why symfony is redirecting webprofiler to login page ? How can prevent this behavior ? Should I use another way to achieve this ?
If you want to protect a whole section of your application use the symfony security component to create a firewall with its own authentication.
If you want to protect specific pages you could use a Security Voter https://symfony.com/doc/current/security/voters.html
Edit: After denying access via a Voter you can redirect the user with a https://symfony.com/doc/current/security/access_denied_handler.html

Symfony - using twig in onKernelRequest kills session

I have a strange issue in symfony 4.3 (also tested it in 4.2 - same behaviour) - I am using an EventListener to process a request - heres the code:
<?php
namespace App\EventListener;
use App\Entity\Company;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
class ShopListener implements EventSubscriberInterface
{
/** #var EntityManagerInterface */
protected $em;
/** #var Environment */
protected $twig;
public function __construct(EntityManagerInterface $entityManager, Environment $twig)
{
$this->em=$entityManager;
$this->twig=$twig;
}
public function onKernelRequest(RequestEvent $event)
{
if($event->isMasterRequest()===false) {
return;
}
/** #var Request $request */
$request=$event->getRequest();
$subDomain=$request->attributes->get('domain');
if($subDomain===null) {
return;
}
$company=$this->em->getRepository(Company::class)->findOneBy([
'subDomain' => $subDomain,
]);
if($company instanceof Company && $company->shopIsOnline()) {
$request->attributes->set('company',$company);
return;
}
$event->setResponse(
new Response($this->twig->render('page/shop_not_found.html.twig'),404)
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest',0],
];
}
}
After registering that listener, $request->getSession() is always null in my controller (toolbar also notices, that there is no session registered). When deregistering it, the session is there, but the logic in the listener is skipped. I have tried to play around with the priority to ensure, there's no other listener which interferes.
It seems, that already registering that event kills the session (even if onKernelRequest is empty), which is hard to believe.
What am I missing?
Session is created by Symfony\Component\FrameworkBundle\EventListener\SessionListener listener, on kernel.request event too (priority of 128).
This event has a specific behavior: if a listener sets a Response, "the process skips directly to the kernel.response event" to quote the documentation. I would suspect it could causes issues.
Try setting your listener a priority < 0 (I'm getting you tried many), and please checks the order the profiler "Events" section (/_profiler/latest?panel=events).
Found the solution - problem is the injection of the twig-environment in the constructor - without twig everything works as expected. I guess, loading the twig-environment at this stage does something to the session (like loading it too early).
I moved the listener to onKernelController and modified it:
<?php
namespace App\EventListener;
use App\Entity\Company;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
class ShopListener implements EventSubscriberInterface
{
/** #var EntityManagerInterface */
protected $em;
/** #var Environment */
protected $twig;
public function __construct(EntityManagerInterface $entityManager, Environment $twig)
{
$this->em=$entityManager;
$this->twig=$twig;
}
public function onKernelController(ControllerEvent $controllerEvent)
{
if($controllerEvent->isMasterRequest()===false) {
return;
}
/** #var Request $request */
$request=$controllerEvent->getRequest();
$subDomain = $request->attributes->get('domain');
if($subDomain===null) {
return;
}
$company=$this->em->getRepository(Company::class)->findOneBy([
'subDomain' => $subDomain,
]);
if($company instanceof Company && $company->shopIsOnline()) {
$request->attributes->set('company',$company);
return;
}
$controllerEvent->setController(
function() {
return new Response($this->twig->render('page/shop_not_found.html.twig'),404);
}
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => ['onKernelController',128],
];
}
}

Symfony EventSubscribe on Entity

trying to make an subscriber for Entity actions (CRUD) and cannot figure it out.
I know there is a way, where I can make listener and send him 3 different events, but that's not what I want to reach, I dont even think is good solution.
Event Subscriber
<?php
namespace App\EventListener;
use App\Entity\Log;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* Part of program created by David Jungman
* #author David Jungman <davidjungman.web#gmail.com>
*/
class EntitySubscriber implements EventSubscriberInterface
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $em)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents()
{
return array(
Events::postPersist,
Events::postUpdate,
Events::postRemove,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$this->logEvent($args, "remove");
}
public function postRemove(LifecycleEventArgs $args)
{
$this->logEvent($args, "remove");
}
public function postPersist(LifecycleEventArgs $args)
{
$this->logEvent($args, "create");
}
private function logEvent(LifecycleEventArgs $args, string $method)
{
$entity = $args->getEntity();
if($entity->getShortName() != "Log")
{
$user = $this->tokenStorage->getToken()->getUser();
$log = new Log();
$log
->setUser($user)
->setAffectedTable($entity->getShortName())
->setAffectedItem($entity->getId())
->setAction($method)
->setCreatedAt();
$this->em->persist($log);
$this->em->flush();
}
}
}
and my Service.yaml part
App\EventListener\EntitySubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }
I have tried:
I've looked into these 2 official tutorials:
-https://symfony.com/doc/current/event_dispatcher.html
-https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html
but neither helped.. when I use shown part of config, my computer freeze.
When I try to debug it, I can see these methods active
( php bin/console debug:event-dispatcher )
but they are listening on "event" event
Doctrine has it's own events handler/subscriber system. However, with the class Symfony\Component\EventDispatcher\EventSubscriberInterface; that you are implementing, that is from the Symfony event system.
<?php
use Doctrine\ORM\Events;
use Doctrine\Common\EventSubscriber; // **the Doctrine Event subscriber interface**
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return array(
Events::postUpdate,
);
}
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
// perhaps you only want to act on some "Product" entity
if ($entity instanceof Product) {
// do something with the Product
}
}
}

Symfony 2.8 FosUserBundle User Locale Internacionalization doesn`t work

I create my app with Symfony 2.8 and FosUserBundle. I wiew a lot of tutorials and I donĀ“t understand why it does not take the value of the user session.
I do it like the example in the web of symfony: https://symfony.com/doc/2.8/session/locale_sticky_session.html
The configuration of the first Event Listener is correct but the second event Listener doesn't set the local value in session
<?php
//AppBundle/Controller/DefaultController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request)
{
// replace this example code with whatever you need
$locale = $request->getLocale();
$user = $event->getAuthenticationToken()->getUser();
var_dump($locale);
return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->container->getParameter('kernel.root_dir') . '/..'),
));
/*return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
));*/
}
}
Event Listeners:
<?php
// src/AppBundle/EventListener/LocaleListener.php
namespace AppBundle\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)),
);
}
}
UserLocaleListener.php
<?php
// src/AppBundle/EventListener/UserLocaleListener.php
namespace AppBundle\EventListener;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Stores the locale of the user in the session after the
* login. This can be used by the LocaleListener afterwards.
*/
class UserLocaleListener
{
/**
* #var Session
*/
private $session;
public function __construct(Session $session)
{
$this->session = $session;
}
/**
* #param InteractiveLoginEvent $event
*/
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (null !== $user->getLocale()) {
$this->session->set('_locale', $user->getLocale());
}
}
}
The configuration of config.yml is:
parameters:
locale: en
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
registration:
confirmation:
enabled: true
resetting:
email:
from_email:
address: manuelterronalvarez#gmail.com
sender_name: App Resetting
jms_i18n_routing:
default_locale: "%locale%"
locales: [es, en]
strategy: prefix_except_default
Any ideas? Thanks in advance

Symfony: Decorating a service when user has x role

I have been looking for an answer to this question, but I can not seem to find it anywhere.
I have currently defined a decorator service that decorates the translator service. I however want to decorate the translator service only when the user has a certain role.
services.yml
services:
app.my_translator_decorator:
class: MyBundle\MyTranslatorDecorator
decorates: translator
arguments: ['#app.my_translator_decorator.inner']
public: false
MyTranslatorDecorator.php
class MyTranslatorDecorator {
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
// more code...
}
The container is "compiled" before the runtime. You can't decorate a service depending of the context, it will always be decorated.
However, in your decorator, you can add a guard clause to not execute your custom code if not necessary.
Service definition:
services:
app.my_translator_decorator:
class: AppBundle\MyTranslatorDecorator
decorates: translator
arguments: ['#app.my_translator_decorator.inner', '#security.authorization_checker']
public: false
Decorator:
<?php
namespace AppBundle;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Translation\TranslatorInterface;
class MyTranslatorDecorator implements TranslatorInterface
{
private $translator;
private $authorizationChecker;
public function __construct(TranslatorInterface $translator, AuthorizationCheckerInterface $authorizationChecker)
{
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
}
public function trans($id, array $parameters = [], $domain = null, $locale = null)
{
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
return $this->translator->trans($id, $parameters, $domain, $locale);
}
// return custom translation here
}
// implement other methods
}

Categories