1

I began to build my first web application with symfony. In this app I got user management with authentification and multilanguage. In the user database tabel is a lang column for each user. I got the changing of the default language for the app by changing it through a _GET parameter and also by the login by the database value running. Now I want to change the value in the database automatically by switching the language through the URL _GET parameter in the EventSubscriber. Unfortunately I got no idea how to get the user entity in my 'onKernelRequest' function to save the selecte language in the database. All the logic of changing the language is done in the following code:

<?php
namespace App\EventSubscriber;

use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class LocaleSubscriber implements EventSubscriberInterface
{
    private $defaultLocale;
    private $allowedLangs;
    private $session;

    public function __construct(SessionInterface $session, $defaultLocale = 'de')
    {
        $this->defaultLocale = $defaultLocale;
        $this->allowedLangs = ['de', 'en'];
        $this->session = $session;
    }

    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $event->getAuthenticationToken()->getUser();

        if (null !== $user->getLocale()) {
            $this->session->set('_locale', $user->getLocale());
        }
    }

    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);
        } elseif(array_key_exists('_locale', $_GET) && array_search($_GET['_locale'], $this->allowedLangs) !== FALSE) {
            $request->setLocale($_GET['_locale']);
            $request->getSession()->set('_locale', $_GET['_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 (i.e. with a higher priority than) the default Locale listener
            SecurityEvents::INTERACTIVE_LOGIN => array(array('onInteractiveLogin', 15)),
            KernelEvents::REQUEST => array(array('onKernelRequest', 20)),
        );
    }
}

Thanks in advance Peter

Peter
  • 21
  • 1
  • 4
  • hi, please check https://stackoverflow.com/questions/36870272/how-to-get-the-current-logged-user-in-a-service – LBA Aug 30 '18 at 08:09

3 Answers3

4

First I would split LocaleSubscriber class and move onKernelRequest and onInteractiveLogin to separate listeners / subscribers - less dependencies for each services.

To get current user in onKernelRequest event you need to inject TokenStorage service:

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class LocaleSubscriber implements EventSubscriberInterface
{
    private $tokenStorage;
    private $em;

    public function __construct(
        TokenStorageInterface $tokenStorage, 
        EntityManagerInterface $em
    ) {
        $this->tokenStorage = $tokenStorage;
        $this->em = $em;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        if (!$event->isMasterRequest()) {
            return;
        }

        if (!$token = $this->tokenStorage->getToken()) {
            return ;
        }

        if (!$token->isAuthenticated()) {
            return ;
        }

        if (!$user = $token->getUser()) {
            return ;
        }

        $request = $event->getRequest();

        // update user
        $user->setLocale($locale);

        $this->em->flush($user);

    }

}

Note this is not tested.

Kamil Adryjanek
  • 3,238
  • 1
  • 20
  • 21
  • you'll have to check `!is_object($user = $token->getUser()` too because e.g. for anonymous users `getUser()` will return a string only – LBA Aug 30 '18 at 10:27
  • `$token->isAuthenticated()` is tested before, so it's ok – Mcsky Aug 30 '18 at 10:39
  • I set up a new EventSubscriber next to my old one. I tried to use the class TokenStorage in the constructor with an error message in the frontend: `Cannot autowire service "App\EventSubscriber\UserLocaleSubscriber": argument "$tokenStorage" of method "__construct()" references class "Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" but no such service exists. Try changing the type-hint to "Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" instead.` I also tried TokenStorageInterface but this is always empty. – Peter Aug 30 '18 at 12:16
  • 1
    Do I have to do something different in the authentication process to get data out of the TokenStorageInterface? – Peter Aug 30 '18 at 13:27
  • if empty please check this too: https://stackoverflow.com/questions/40087840/get-user-in-a-doctrine-eventlistener – LBA Aug 30 '18 at 14:17
  • Updated for `TokenStorageInterface`. – Kamil Adryjanek Aug 30 '18 at 14:20
1

The problem for the empty token is the wrong event. I switched to "KernelEvents::CONTROLLER". Now I got my User Entity. My working code:

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class UserLocaleSubscriber implements EventSubscriberInterface
{
    private $tokenStorage;
    private $em;
    private $allowedLangs;

    public function __construct(TokenStorageInterface $tokenStorage, EntityManagerInterface $em)
    {
        $this->tokenStorage = $tokenStorage;
        $this->em = $em;
        $this->allowedLangs = ['de', 'en'];
    }

    public function onKernelController(FilterControllerEvent $event)
    {
        $request = $event->getRequest();
        if (!$request->hasPreviousSession()) {
            return;
        }

        if(array_key_exists('_locale', $_GET) && array_search($_GET['_locale'], $this->allowedLangs) !== FALSE) {
            if (!$event->isMasterRequest()) {
                return;
            }

            if (!$token = $this->tokenStorage->getToken()) {
                return ;
            }

            if (!$token->isAuthenticated()) {
                return ;
            }

            if (!$user = $token->getUser()) {
                return ;
            }

            $locale = $_GET['_locale'];
            // update user
            $user->setLocale($locale);

            $this->em->flush($user);
            $request->setLocale($locale);
            $request->getSession()->set('_locale', $locale);
        }
    }

    public static function getSubscribedEvents()
    {
        return array(
            // must be registered before (i.e. with a higher priority than) the default Locale listener
            KernelEvents::CONTROLLER => array(array('onKernelController', 1)),
        );
    }
}
Peter
  • 21
  • 1
  • 4
0

Entity is present in LifecycleEventArgs.

public function prePersist(LifecycleEventArgs $args)
{
    $entity = $args->getEntity();