Manage locale with Symfony3 LocaleSubscriber - php

I've some troubles in using locale parameter with Symfony 3. I've tried a lot of things that I've found on Stack, but nothing seems to work.
I've followed the official documentation : https://symfony.com/doc/current/session/locale_sticky_session.html
LocaleSubscriber.php
<?php
namespace AppBundle\EventSubscriber;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\HttpKernel;
class LocaleSubscriber implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernel::MASTER_REQUEST != $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
// try to see if the locale has been set as a _locale routing parameter
if ($locale = $request->get('_locale')) {
$request->set('_locale', $locale);
} else {
$request->set('_locale', $this->defaultLocale);
}
}
public static function getSubscribedEvents()
{
return array(
// must be registered after the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
config.yml
framework:
#esi: ~
translator: { fallbacks: ['%locale%'] }
service.yml
AppBundle\EventSubscriber\LocaleSubscriber:
arguments: ['%kernel.default_locale%']
tags: [kernel.event_subscriber]
AdminController
namespace AdminBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class AdminController extends Controller
{
public function homeAction(Request $request)
{
var_dump($request->getLocale());
var_dump($request->get('_locale'));
return $this->render('AdminBundle:home:home.html.twig');
}
}
HTML Links :
<li>FR</li>
<li>EN</li>
Problem : When I try to switch of language, the parameter _locale is changed but the text don't switch language. "testfr" must be "testen" according to my messages.en.yml.
My var_dump when I click on the "FR" link : string(2) "fr" string(2) "fr"
My var_dump when I click on the "EN" link : string(2) "fr" string(2) "en"
I guess it is not normal..
If someone can help me, that would be nice and can remove a headache.
Thanks
Edit : The translator takes the "locale" parameter, which doesn't change when I click on one of theses links. I'm still investigating.

Finally, found the problem :
if (!$request->hasPreviousSession()) {
return;
}
Just comment this part of the code, and all is fine now.

Related

Symfony6 changing the controller manually using the "kernel.controller" event. How to inject the service container?

The application that I am building is not going to work in a traditional way. All the routes ar going to be stored in the database. And based on the route provided I need to get the correct controller and action to be executed.
As I understand this can be achieved using the "kernel.controller" event listener: https://symfony.com/doc/current/reference/events.html#kernel-controller
I am trying to use the docs provided, but the example here does not exacly show how to set up a new callable controller to be passed. And I have a problem here, because I dont know how to inject the service container to my newly called controller.
At first the setup:
services.yaml
parameters:
db_i18n.entity: App\Entity\Translation
developer: '%env(DEVELOPER)%'
category_directory: '%kernel.project_dir%/public/uploads/category'
temp_directory: '%kernel.project_dir%/public/uploads/temp'
product_directory: '%kernel.project_dir%/public/uploads/product'
app.supported_locales: 'lt|en|ru'
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
App\Translation\DbLoader:
tags:
- { name: translation.loader, alias: db }
App\Extension\TwigExtension:
arguments:
- '#service_container'
tags:
- { name: twig.extension }
App\EventListener\RequestListener:
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onControllerRequest }
The listener:
RequestListener.php
<?php
namespace App\EventListener;
use App\Controller\Shop\HomepageController;
use App\Entity\SeoUrl;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class RequestListener
{
public ManagerRegistry $doctrine;
public RequestStack $requestStack;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack)
{
$this->doctrine = $doctrine;
$this->requestStack = $requestStack;
}
/**
* #throws Exception
*/
public function onControllerRequest(ControllerEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
if(str_contains($this->requestStack->getMainRequest()->getPathInfo(), '/admin')) {
return;
}
$em = $this->doctrine->getManager();
$pathInfo = $this->requestStack->getMainRequest()->getPathInfo();
;
$route = $em->getRepository(SeoUrl::class)->findOneBy(['keyword' => $pathInfo]);
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = new HomepageController();
$event->setController([$controller, $route->getAction()]);
break;
default:
break;
}
} else {
throw new Exception('Route not found');
}
}
}
So this is the most basic example. I get the route from the database, if it a "homepage" route, I create the new HomepageController and set the action. However I am missing the container interface that I dont know how to inject. I get this error:
Call to a member function has() on null
on line: vendor\symfony\framework-bundle\Controller\AbstractController.php:216
which is:
/**
* Returns a rendered view.
*/
protected function renderView(string $view, array $parameters = []): string
{
if (!$this->container->has('twig')) { // here
throw new \LogicException('You cannot use the "renderView" method if the Twig Bundle is not available. Try running "composer require symfony/twig-bundle".');
}
return $this->container->get('twig')->render($view, $parameters);
}
The controller is as basic as it gets:
HomepageController.php
<?php
namespace App\Controller\Shop;
use App\Repository\CategoryRepository;
use App\Repository\Shop\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class HomepageController extends AbstractController
{
#[Route('/', name: 'index', methods: ['GET'])]
public function index(): Response
{
return $this->render('shop/index.html.twig', [
]);
}
}
So basically the container is not set. If I dump the $event->getController() I get this:
RequestListener.php on line 58:
array:2 [▼
0 => App\Controller\Shop\HomepageController {#417 ▼
#container: null
}
1 => "index"
]
I need to set the container by doing $controller->setContainer(), but what do I pass?
Do not inject the container, controllers are services too and manually instanciating them is preventing you from using constructor dependency injection. Use a service locator which contains only the controllers:
Declared in config/services.yaml:
# config/services.yaml
services:
App\EventListener\RequestListener:
arguments:
$serviceLocator: !tagged_locator { tag: 'controller.service_arguments' }
Then in the event listener, add the service locator argument and fetch the fully configured controllers from it:
# ...
use App\Controller\Shop\HomepageController;
use Symfony\Component\DependencyInjection\ServiceLocator;
class RequestListener
{
# ...
private ServiceLocator $serviceLocator;
public function __construct(
# ...
ServiceLocator $serviceLocator
) {
# ...
$this->serviceLocator = $serviceLocator;
}
public function onControllerRequest(ControllerEvent $event)
{
# ...
if($route instanceof SeoUrl) {
switch ($route->getController()) {
case 'homepage':
$controller = $this->serviceLocator->get(HomepageController::class);
# ...
break;
default:
break;
}
}
# ...
}
}
If you dump any controller you will see that the container is set. Same will go for additionnal service that you autowire from the constructor.

Symfony Twig Extension breaks other service - Is templating done before security?

I am working on a Symfony 2.7 WebApp. One of the bundles I created includes a service that offer some user related stuff, e.g. userHasPurchases().
Problem is, that including a Twig Extesion breaks another service:
AppShopService
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $user;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->user = $tokenStorage->getToken() ? $tokenStorage->getToken()->getUser() : null;
...
}
public function userHasPurchases(User $user) {
$user = $user ? $user : $this->user;
$result = $user...
return result;
}
}
AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
So far everything works fine: The AppShopServices is created with the current user and userHasPurchases() work as expected.
Now I have add a Twig Extension to be able to use userHasPurchases() within my templates:
Twig Extension
namespace AppShopBundle\Twig;
use AppShopBundle\Service\AppShopService;
class AppShopExtension extends \Twig_Extension {
private $shopService;
public function __construct(AppShopService $shopService) {
$this->shopService = $shopService;
}
public function getName() {
return 'app_shop_bundle_extension';
}
public function getFunctions() {
$functions = array();
$functions[] = new \Twig_SimpleFunction('userHasPurchases', array(
$this,
'userHasPurchases'
));
return $functions;
}
public function userHasPurchases($user) {
return $this->shopService->userHasPurchases($user);
}
}
Including Extension in AppShopBundle\Resources\config\services.yml
services:
app_shop.service:
class: AppShopBundle\Service\AppShopService
arguments:
- "#security.token_storage"
- ...
app_shop.twig_extension:
class: AppShopBundle\Twig\AppShopExtension
arguments:
- "#app_shop.service"
tags:
- { name: twig.extension }
After icluding the Twig Extension, AppShopService and its method userHasPurchases does not work any more. Problem is, that the constructor of AppShopService does not set user anymore since $tokenStorage->getToken() now returns null.
How is this possible? I have changed nothing except including the Twig Extension. As soon as I remove the Twig Extension from services.yml everything works correctly again.
My only guess is, that the creation fo the Twig Extension is done before any security. But why?
Any idea what might be wrong here?
don't interact with the tokenStorage in the constructor but only in the userHasPurchases method.
namespace AppShopBundle\Service;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
...
class AppShopService {
protected $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage, ...) {
$this->tokenStorage = $tokenStorage;
}
public function userHasPurchases(User $user) {
$user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
$result = $user...
return result;
}
}
Hope this help

Symfony session cannot be retrieve in twig

I'm trying to make a two language app with symfony2.
I'm using Doctrine behavior and A2lixtranslationformbundle.
I have a listener which listen to change the locale:
namespace George\CoreBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
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')) {
if ($locale = $request->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)),
);
}
}
In the twig template i want to retrieve the translation trough the session param to see what locale we have:
{{ entity.translate(app.session.get('_locale')).title }}
But the app.session.get('_locale') does not return nothing. What is the problem why the session in the twig do not get this property i have test it in the listener everything looks fine.
In order to retrive the locale in twig you can use the following snippet
{{ app.request.locale }}
for your case will be
{{ entity.translate(app.request.locale).title }}

Symfony 2, switching language does not work

I wonder why when I click the links on the view to switch the language it doesn't work. If I set default locale to en or km, it will show English language or Khmer language respectively. What wrong with me, I can not click links below to switch the language? Please help me ! Many thanks in advanced for any answer.
In the view
<div class="col-sm-3 language-switcher">
English |
ខ្មែរ
</div>
routing.yml
ngs_locale:
path: locale/{locale}
defaults: { _controller: NGSHomeBundle:Locale:locale }
LocaleController.php
namespace NGS\HomeBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class LocaleController extends Controller
{
public function localeAction(Request $request, $locale)
{
/** ======== dump ========== **/
dump($locale); //"km"
$request->getSession()->set('_locale', $locale);
/** ======== dump ========== **/
dump($request->getLocale()); //"en"
$referer = $request->headers->get('referer');
/** ======== dump ========== **/
dump($referer);die; // null
if (empty($referer)) {
throw $this->createNotFoundException('ngs_not_found');
}
return $this->redirect($referer);
}
}
LocaleListener.php
namespace NGS\HomeBundle\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)),
);
}
}
Service.yml
services:
ngs_home.locale_listener:
class: NGS\HomeBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
Now, I fixed my problem:
First, I changed the link: from locale to _locale
<div class="col-sm-3 language-switcher">
English |
ខ្មែរ
</div>
Second, I changed routing.yml, locale to _locale
ngs_locale:
path: locale/{_locale}
defaults: { _controller: NGSHomeBundle:Locale:locale
Third, I changed LocaleController.php. Removed $locale parameter and then get $locale by $request->getLocale();
public function localeAction(Request $request)
{
$locale = $locale = $request->getLocale();
$request->getSession()->set('_locale', $locale);
$referer = $request->headers->get('referer');
if (empty($referer)) {
throw $this->createNotFoundException('ngs_not_found');
}
return $this->redirect($referer);
}
Fourth, I add DependencyInjection for HomeBundle DependencyInjection \Configuration.php and DependencyInjection \NGSHomeExtension.php
That's it !

change locale symfony 2.3

I just began with symfony
I'm trying to build a multilang website but I have a problem to change the locale
I read some posts and I read the documentation about this but the locale don't change, I try:
public function indexAction()
{
$this->get('session')->set('_locale', 'fr');
$request = $this->getRequest();
$locale = $request->getLocale();
return $this->render('PhoneMainBundle:Default:index.html.twig',array('locale'=>$locale));
}
but the value in $locale is always 'en' (my default locale)
I also try
public function indexAction()
{
$this->get('session')->set('_locale', 'fr');
$request = $this->getRequest();
$request->setLocale('fr');
$locale = $request->getLocale();
return $this->render('PhoneMainBundle:Default:index.html.twig',array('locale'=>$locale));
}
In this case $locale is fr but the translations are always from messages.en.yml
I'd like in a first time to detect the user locale using $_SERVER['HTTP_ACCEPT_LANGUAGE'], maybe using a listner on each page actualisation ?
and after I will create a route to change the locale
But I 'd like to find a way to change the locale.
Thanks for your help
Based on this and this answers.
LanguageListener.php:
<?php
namespace Acme\UserBundle\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 LanguageListener
{
private $session;
public function setSession(Session $session)
{
$this->session = $session;
}
public function setLocale(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
$request->setLocale($request->getPreferredLanguage(array('en', 'de')));
}
}
services.yml:
acme.language.kernel_request_listener:
class: Acme\UserBundle\EventListener\LanguageListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: setLocale }
About wrong locale detection in twig, there could be a lot of different causes. Search through the SO, you'll definitely find the answer. Make sure that your '_local' var is defined right, make sure that you put your languages files in the right place, etc. FInally, read again the last version of the documentation: http://symfony.com/doc/current/book/translation.html
I however added this to make it more dynamic
services.yml
services:
acme.language.kernel_request_listener:
class: Acme\UserBundle\EventListener\LanguageListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: setLocale }
arguments: [ #router, #service_container ]
LanguageListener.php:
<?php
namespace Acme\UserBundle\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 LanguageListener
{
private $session;
private $container;
private $router;
public function __construct($router, $container)
{
// ...
$this->router= $router;
$this->container = $container;
}
public function setSession(Session $session)
{
$this->session = $session;
}
public function setLocale(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
$request->setLocale($request->getPreferredLanguage($this->container->parameters['jms_i18n_routing.locales']));
}
}
Just to be able to get the parameters and values from config.yml.
Regards,
Wick
If for example your default locale is french, except for one controller you want to have the default locale set to english can do that:
routing.yml
desktop_comingsoonpage:
resource: "#RemmelComparabusBundle/Controller/ComingsoonpageController.php"
defaults: { _locale: en }
type: annotation
more info : Symfony doc

Categories