I use FOSUserEvents after submit form but the subscriber call twice.
In this way my captcha is valid the first time and not valid the second
this is my code
<?php
namespace AppBundle\EventListener;
class CaptchaSubscriber implements EventSubscriberInterface
{
private $router;
private $requestStack;
private $templating;
/**
* RedirectAfterRegistrationSubscriber constructor.
*/
public function __construct(RouterInterface $router, RequestStack $requestStack, \Twig_Environment $templating)
{
$this->router = $router;
$this->requestStack = $requestStack;
$this->templating = $templating;
}
public function onRegistrationInit(GetResponseUserEvent $event)
{
if ($this->requestStack->getMasterRequest()->isMethod('post')) {
...handle captcha...
}
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit'
];
}
}
my symfony is 3.3
UPDATE
I added
$event->stopPropagation();
with this snippet the code works, but i don't know if it is the best practice
In my case of symfony 4.2 it depends on the service definition if it occures or not.
My Subscriber gets registered twice if I define the service like this:
# oauth process listener
app.subscriber.oauth:
class: App\EventListenerSubscriber\OauthSubscriber
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
But it gets registerd only once if I chenge the definition to this:
# oauth process listener
App\EventListenerSubscriber\OauthSubscriber:
arguments: ['#session', '#router', '#security.token_storage', '#event_dispatcher', '#app.entity_manager.user', '#app.fos_user.mailer.twig_swift']
tags:
- { name: kernel.event_subscriber }
I posted a bug report on github and got immediately an answer, that in newer symfony versions event listeners and subscribers get registered automatically with their class name as key (under some default conditions - must read on that topic).
So there is no need to register them explicitely as services.
I we do this anyway, but using an arbitrary key instead of class name, there will be two services.
If you are using Autowiring/Autoconfiguration, it's possible that you've added the subscriber service you show above, twice. I've done it myself when I first added the autowiring, but I also had the subscriber listed explicitly in the configuration as well.
You can see what events are registered (and check if any are registered more than once to perform the same service/action) with:
bin/console debug:event-dispatcher
Related
Trying to register a Doctrine EventSubscriber but nothing is ever actually fired.
I have, on the Entity, in question, set the #ORM\HasLifeCycleCallbacks annotation.
Here's the Subscriber:
<?php
namespace App\Subscriber;
use App\Entity\User;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserPasswordChangedSubscriber implements EventSubscriber
{
private $passwordEncoder;
public function __construct(UserPasswordEncoderInterface $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public function getSubscribedEvents()
{
return [Events::prePersist, Events::preUpdate, Events::postLoad];
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return null;
}
$this->updateUserPassword($entity);
}
public function preUpdate(PreUpdateEventArgs $event)
{
$entity = $event->getEntity();
if (!$entity instanceof User) {
return null;
}
$this->updateUserPassword($entity);
}
private function updateUserPassword(User $user)
{
$plainPassword = $user->getPlainPassword();
if (!empty($plainPassword)) {
$encodedPassword = $this->passwordEncoder->encodePassword($user, $plainPassword);
$user->setPassword($encodedPassword);
$user->eraseCredentials();
}
}
}
The part that is making this particuarly frustrating is that this same code and configuration was fine in Symfony 3 whe autowiring was turned off and I manually coded all my services.
However, now, even if I manually code up a service entry for this, in the usual way, still nothing happens.
EDIT:
Here is my services.yaml after trying what suggested Domagoj from the Symfony docs:
App\Subscriber\UserPasswordChangedSubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }
It didn't work. Interestingly, If I un-implement the EventSubscriber interface, Symfony throws an exception (rightly). Yet my break points in the code are completely ignored.
I've considered an EntityListener, but it cannot have a constructor with arguments, doesn't have access to the Container and I shouldn't have to; this ought to work :/
I ended up figuring this out. The field that I was specifically updating was transient, and therefore Doctrine didn't consider this an Entity change (rightly).
To fix this, I put
// Set the updatedAt time to trigger the PreUpdate event
$this->updatedAt = new DateTimeImmutable();
In the Entity field's set method and this forced an update.
I also did need to manually register the Subscriber in the services.yaml using the following code. symfony 4 autowiring wasn't auto enough for a Doctrine Event Subscriber.
App\Subscriber\UserPasswordChangedSubscriber:
tags:
- { name: doctrine.event_subscriber, connection: default }
For your first problem, doctrine event subscribers are not autoconfigured/auto-tagged. For the reasons and solutions, you have some responses here.
Personnaly, I just have one Doctrine ORM mapper, so I put this in my services.yaml file :
services:
_instanceof:
Doctrine\Common\EventSubscriber:
tags: ['doctrine.event_subscriber']
You have to register your Event Listener as a service and tag it as doctrine.event_listener
https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html#configuring-the-listener-subscriber
I'm using FOSUserBundle on Symfony2.
I extended the User class to have additional fields, therefore I also added the new fields in the twigs.
One of those fields is a licence code. When a user fills in that field I want to perform a connection to DB to look if that license is valid. If not returns an error, if yes creates an event in the "licenceEvents" table assigning the current user to that license.
[EDIT] As suggested I created a custom validator (which works like a charm), and I'm now struggling with the persisting something on DB once the user is created or updated.
I created an event listener as follows:
<?php
// src/AppBundle/EventListener/UpdateOrCreateProfileSuccessListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Doctrine\ORM\EntityManager; //added
class UpdateOrCreateProfileSuccessListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router, EntityManager $em)
{
$this->router = $router;
$this->em = $em; //added
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => array('onUserCreatedorUpdated',-10),
FOSUserEvents::PROFILE_EDIT_COMPLETED => array('onUserCreatedorUpdated',-10),
);
}
public function onUserCreatedorUpdated(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$code = $user->getLicense();
$em = $this->em;
$lastEvent = $em->getRepository('AppBundle:LicenseEvent')->getLastEvent($code);
$licenseEvent = new LicenseEvent();
// here I set all the fields accordingly, persist and flush
$url = $this->router->generate('fos_user_profile_show');
$event->setResponse(new RedirectResponse($url));
}
}
My service is like follows:
my_user.UpdateOrCreateProfileSuccess_Listener:
class: AppBundle\EventListener\UpdateOrCreateProfileSuccessListener
arguments: [#router, #doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
The listener is properly triggered, manages to create the connection to DB as expected, but gives me the following error
Catchable Fatal Error: Argument 1 passed to AppBundle\EventListener\UpdateOrCreateProfileSuccessListener::onUserCreatedorUpdated()
must be an instance of AppBundle\EventListener\FilterUserResponseEvent,
instance of FOS\UserBundle\Event\FilterUserResponseEvent given
I must be missing something very stupid...
Another question is: I don't want to change the redirect page, so that if the original page was the "email sent" (after a new user is created) let's go there, otherwise if it's a profile update show the profile page.
I create a subscriber and register a new service, what I do :
https://gist.github.com/Draeli/2c591c16409a5664ae58
<?php
namespace My\BlogBundle\Listener;
use Doctrine\ORM\Events;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
class BlogArticleDetailListener implements EventSubscriberInterface
{
/**
* #var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
static public function getSubscribedEvents()
{
return array(
'doctrine.event_subscriber' => array(
array(Events::prePersist, 0),
array(Events::preUpdate, 0),
),
);
}
public function prePersist(FilterResponseEvent $event)
{
var_dump('prePersist');die;
}
public function preUpdate(FilterResponseEvent $event)
{
var_dump('preUpdate');die;
}
}
services:
my_blog.listener.blog_article_detail:
class: My\BlogBundle\Listener\BlogArticleDetailListener
arguments: ["#service_container"]
tags:
- { name: kernel.event_subscriber }
But in this case methods prePersist and preUpdate in spite of I persist objects, like if Doctrine didn't dispatch.
Someone know what is strong in what I do ?
(For explanation, now I register only doctrine event but after I will register more from same place)
Service
At present the listener is listening to event being dispatched using the Symfony kernel event dispatcher whereas you should be listening to the Doctrine event dispatcher.
Your service...
services:
my_blog.listener.blog_article_detail:
class: My\BlogBundle\Listener\BlogArticleDetailListener
arguments: ["#service_container"]
tags:
- { name: kernel.event_subscriber }
Should have the tag doctrine.event_subscriber rather than the kernel.
Subscriber
As you are creating a Doctrine subscriber rather than a Symfony kernel subscriber you should implementing \Doctrine\Common\EventSubscriber, meaning the getSubscribedEvents method shouldn't be static.
Currently your subscriber is listening for an event called doctrine.event_subscriber rather than listening to the Doctrine events.
You should actually just be listening to the Doctrine events using...
public function getSubscribedEvents()
{
return array(
Events::prePersist, // I'm not sure about setting
Events::preUpdate, // the priorities to be honest
);
}
With the Doctrine events you won't be getting FilterResponseEvent as Doctrine dispatches with specific argument objects (Doctrine\Common\Persistence\Event\LifecycleEventArgs mostly) on events. For more info on the arguments (and the events) you can view the documentation.
I'm using fos user bundle and pugx multi user bundle.
I've read all the documentation and I'm new to Symfony.
In the pugx multi user bundle there's a sample on every point but one: sucessful registration.
Samples of overriding controllers for generating forms => ok
Samples of overriding templates for generating forms => ok
Samples of overriding successful registration sample => nothing.
Here's my code:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
$response = parent::registerAction($request);
return $response;
}
public function registerTeacherAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonTeacher');
}
public function registerStudentAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonStudent');
}
}
The problem is with ->get('pugx_multi_user.registration_manager') which returns a manager. In the fos user overring controllers help, they get either a form or a form.handler. I'm having hard times to "link" those with the pugx_multi_user manager.
What code should I put in the registerTeacherAction() to set roles for teacher, and in registerStudentAction() to set roles for student on a successful registration?
Solution 1 (Doctrine Listener/Subscriber)
You can easily add a doctrine prePersist listener/subscriber that adds the roles/groups to your entities depending on their type before persisting.
The listener
namespace Acme\YourBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\YourBundle\Entity\Student;
class RoleListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
// check for students, teachers, whatever ...
if ($entity instanceof Student) {
$entity->addRole('ROLE_WHATEVER');
// or
$entity->addGroup('students');
// ...
}
// ...
}
}
The service configuration
# app/config/config.yml or load inside a bundle extension
services:
your.role_listener:
class: Acme\YourBundle\EventListener\RoleListener
tags:
- { name: doctrine.event_listener, event: prePersist }
Solution 2 (Doctrine LifeCycle Callbacks):
Using lifecycle callbacks you can integrate the role-/group-operations directly into your entity.
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Student
{
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->addRole('ROLE_WHATEVER');
$this->addGroup('students');
}
Solution 3 (Event Dispatcher):
Register an event listener/subscriber for the "fos_user.registration.success" event.
How to create an event listener / The EventDispatcher component.
I've defined a route in my app routing file:
RouteName:
pattern: /some/route
defaults: { _controller: MyAppBundle:Controller:action }
In a controller I can use:
$this->get('router')->generate('RouteName');
How would I simply access that from a fresh class I create, for example a view class that doesn't extend anything:
namespace My\AppBundle\View;
class ViewClass {
public function uri()
{
return getTheRoute('RouteName');
}
}
You need to inject "router" service into your ViewClass. Eg. in place where your define your ViewClass service:
viewclass.service:
class: Namespace\For\ViewClass
arguments:
router: "#router"
and then in your constructor:
public function __construct(\Symfony\Bundle\FrameworkBundle\Routing\Router $router)
{
$this->router = $router;
}
The clue is in how the $this->generateUrl() method works in Controllers. See:
/**
* Generates a URL from the given parameters.
*
* #param string $route The name of the route
* #param mixed $parameters An array of parameters
* #param Boolean $absolute Whether to generate an absolute URL
*
* #return string The generated URL
*/
public function generateUrl($route, $parameters = array(), $absolute = false)
{
return $this->container->get('router')->generate($route, $parameters, $absolute);
}
So you'll need to define your class as a service and inject the #router service. Either that or have your class implement ContainerAwareInterface, but the first method would definitely be better.
You should register your class as a service and insert the router as a dependency.
See the chapter on the service container in the excellent symfony2 docs.
If you're not familiar with the concepts of the service container and dependency injection, you might feel a bit overwhelmed. However, try your best to understand it because it is a essential part of the symfony2 architecture.
You could pass the entire container from your controller to your view class on instantiation. This is NOT BEST PRACTICE and not recommended.
class View
{
protected $container;
public function __construct(\Symfony\Component\DependencyInjection\Container $container)
{
$this->container = $container;
}
}
Then in your code you could use
$this->container->get('router')->generate($route, $parameters, $absolute);