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],
];
}
}
Related
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
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]],
];
}
I'm using a service with an autowired dispatcher to dispatch an event. In order to test events, I've added an event subscriber just before dispatching the event. However, the registered subscriber isn't logging the method I would expect it to do.
The event:
<?php
namespace App\Event;
use Symfony\Component\EventDispatcher\Event;
class GameStartEvent extends Event {
}
The subscriber:
<?php
namespace App\Event;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class GameStartSubscriber implements EventSubscriberInterface {
use LoggerAwareTrait;
public function __construct(LoggerInterface $logger) {
$this->setLogger($logger);
}
public static function getSubscribedEvents() {
return [
"game.started" => [
[
'logEvent',
],
]
];
}
public function logEvent(GameStartEvent $event): void {
$this->logger->info("the game has started");
}
}
And this is the used service that is supposed to dispatch events the event registration is supposed to happen somewhere else in the future. I just did it here for testing:
<?php
namespace App\Service;
use App\Event\GameStartEvent;
use App\Event\GameStartSubscriber;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class GameCacheService {
private $eventDispatcher;
private $logger;
public function __construct(EventDispatcherInterface $eventDispatcher, LoggerInterface $logger) {
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
}
// some other methods...
function startGame() {
// some code...
$gameStartSubscriber = new GameStartSubscriber($this->logger);
$this->eventDispatcher->addSubscriber($gameStartSubscriber);
$this->eventDispatcher->dispatch("game.started", new GameStartEvent());
}
}
After calling the service method, the specified logger message isn't written down.
Replace
`$this->eventDispatcher->dispatch("game.started", new GameStartEvent());`
with
`$this->eventDispatcher->dispatch("game.started", $event);`
You seem to have at least your Event in App/Entity which is not autowired by default and shouldn't be. Have a look at your services.yml, it should be excluded from Autowiring. Move your stuff to something like App/Event
I would like to define a global variable for twig, which would be accessible from any template.
I can create a global variable in symfony's config/packages/twig.yaml, but I need it to be a value obtained from the database.
In the documentation for twig it says to use this code:
$twig = new Twig_Environment($loader);
$twig->addGlobal('name', 'value');
Where should I use this code so this varible is available for every template?
A relatively clean way of doing would be to add an event subscriber that would inject the variables globally before the controllers are instantiated.
The problem of doing in the controller, as one of the comments suggested, is that your globals wouldn't be global at all, but defined only for that controller.
You could do something like this:
// src/Twig/TwigGlobalSubscriber.php
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
class TwigGlobalSubscriber implements EventSubscriberInterface {
/**
* #var \Twig\Environment
*/
private $twig;
/**
* #var \Doctrine\ORM\EntityManager
*/
private $manager;
public function __construct( Environment $twig, EntityManager $manager ) {
$this->twig = $twig;
$this->manager = $manager;
}
public function injectGlobalVariables( GetResponseEvent $event ) {
$whatYouWantAsGlobal = $this->manager->getRepository( 'SomeClass' )->findBy( [ 'some' => 'criteria' ] );
$this->twig->addGlobal( 'veryGlobal', $whatYouWantAsGlobal[0]->getName() );
}
public static function getSubscribedEvents() {
return [ KernelEvents::CONTROLLER => 'injectGlobalVariables' ];
}
}
The DB interaction I left deliberately fuzzy, since only you know exactly what do you want to retrieve from there. But you are injecting the EntityManager on this subscriber, so it's only a matter of retrieving the appropriate repository and perform the appropriate search.
Once this is done, you could simply do from your twig templates something like:
<p>I injected a rad global variable: <b>{{ veryGlobal }}</b></p>
Symfony 5.4
service.yml:
App\Twig\TwigGlobalSubscriber:
tags:
- { name: kernel.event_listener, event: kernel.request }
SysParams - my service takes data from the database
src\Twig\TwigGlobalSubscriber.php
<?php
namespace App\Twig;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Twig\Environment;
use App\Service\SysParams;
class TwigGlobalSubscriber implements EventSubscriberInterface {
private $twig;
public function __construct( Environment $twig, SysParams $sysParams ) {
$this->twig = $twig;
$this->sysParams = $sysParams;
}
public function injectGlobalVariables() {
$base_params = $this->sysParams->getBaseForTemplate();
foreach ($base_params as $key => $value) {
$this->twig->addGlobal($key, $value);
}
}
public static function getSubscribedEvents() {
return [ KernelEvents::CONTROLLER => 'injectGlobalVariables' ];
}
public function onKernelRequest()
{
}
}
I am developing a application with symfony2. Im facing a problem with localization. I want to set the in the postLoad event in doctrine lifecycle, but can find a way to do that. I am using the route method to set my local for example:
http://example.com/en/content
here is my listener:
namespace MyApiBundle\Listener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
class LocaleListener
{
private $local;
public function __construct($local) {
$this->local = $local;
}
public function postLoad(LifecycleEventArgs $args)
{
$local= 'en'; // I need to get the local from here
$entity = $args->getEntity();
if(method_exists($entity, 'setLocale')) {
$entity->setLocale($local);
}
}
}
Is there any quick way get the local from here? Cant use the new Request() as it always returning the en I also have 3 other language. Thanks for help
Yes, you can. You can inject #request_stack service into your listener, get request from it and read locale.
There is, however, a Doctrine extension that probably does what you want: Translatable
Thanks #Igor Pantovic
here I got it work, here is my local listner:
#/src/MyApiBUndle/Listner/LocalListner.php
namespace MyApiBundle\Listener;
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\ORM\Event\LifecycleEventArgs;
class LocaleListener {
private $requestStack;
/**
* #param RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack) {
$this->requestStack = $requestStack;
}
/**
* #param LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
$local= $this->requestStack->getCurrentRequest()->getLocale();
$entity = $args->getEntity();
if(method_exists($entity, 'setLocale')) {
$entity->setLocale($local);
}
}
}
and my service
services:
my_api.listener.locale_listener:
class: MyApiBundle\Listener\LocaleListener
tags:
- { name: doctrine.event_listener, event: postLoad }
# #request_stack must be quoted "":
arguments: ["#request_stack"]
hope this will help other too