How to assign roles on successful registration? - symfony

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.

Related

Doctrine error with LifecycleEventArgs arguments

I want to use Listener in my project with postLoad method but I got an error
[TypeError] App\Company\Infrastructure\Persistence\ORM\EventListener\LoadLicensesListener::postLoad(): Argument #1 ($args) must be of type Do
ctrine\ORM\Event\LifecycleEventArgs, App\Company\Domain\Entity\Company given, called in D:\OpenServer\domains\project\vendor\doctrine\orm\lib\Doc
trine\ORM\Event\ListenersInvoker.php on line 108
My Listener
use Doctrine\ORM\Event\LifecycleEventArgs;
final class LoadLicensesListener
{
/**
* #param LifecycleEventArgs $args
*/
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
if (!$entity instanceof Copmany) {
// Something to do
$licenses = $entity->relatedLicenses;
$entity->initializeObject($licenses);
}
}
}
And I registered it in Company.orm.xml
<entity-listeners>
<entity-listener class="App\Company\Infrastructure\Persistence\ORM\EventListener\LoadLicensesListener">
<lifecycle-callback type="postLoad" method="postLoad"/>
</entity-listener>
</entity-listeners>
services.yml
App\Company\Infrastructure\Persistence\ORM\EventListener\LoadLicensesListener:
tags:
- { name: doctrine.event_listener, event: postLoad, connection: default }
Where did I go wrong? Maybe I misunderstood the documentation - Symfony Events or Doctrine Events
Or I should do something in services.yml because I've changed a folder with EventListeners?
"doctrine/orm": "2.8.4"
Doctrine provide different type of listeners, "Default" event listener and Entity Listener, here your registered an entity listener in your file Company.orm.xml and also for the same class a "default" event listener.
Choose which type of listener you want and register it according to the documentation.
If you choose a Entity Listener then the first argument will be the Entity itself, that's why you get this error.
I would say it looks like you've configured it wrong.
try to implement postLoad method inside your Campany.php (Note! Without any params) and see what it outputs.
class Company {
// ...
public function postLoad() {
dump(__METHOD__);
}
}
also take a look at this https://symfony.com/doc/4.1/doctrine/event_listeners_subscribers.html and this one https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html
I am unfortunately not familiar with xml-configs, so I can't spot anything suspicious.
As always, there are several ways to get it done:
simple EntityLifeCycles (docs) - useful for basic stuff and if you don't rely on additional services for this particular task. Logic applies only for that specific Entity.
an Doctrine\Common\EventSubscriber with getSubscribedEvents - more advanced and flexible. One logic could be applied for several entities
an EventListener.
So here are examples for symfony 4.4 and doctrine 2.7:
Entity LifeCylcles:
/**
* #ORM\Entity()
* #ORM\Table(name="company")
* #ORM\HasLifecycleCallbacks
*/
class Company {
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
// ... props and methods
/**
* #ORM\PostLoad()
*/
public function doStuffAfterLoading(): void
{
// yor logic
// you can work with $this as usual
// no-return values!
// dump(__METHOD__);
}
}
with these annotations no extra entries in services.yml|xml necessary
Subscriber - to apply same logic for one or several Entities
use App\Entity\Company;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
final class PostLoadSubscriber implements EventSubscriber {
public functuin __construct()
{
// you can inject some additional services if you need to
// e.g. EntityManager
}
public function getSubscribedEvents()
{
return [
Events::postLoad,
];
}
public function postLoad(LifecycleEventArgs $args)
{
// check if it's right entity and do your stuff
$entity = $args->getObject();
if ($entity instanceof Company) {
// magic...
}
}
}
You need to register this PostLoadSubscriber as a service in services.yaml|xml

EasyAdmin - How to show custom Entity property properly which use EntityRepository

I would like to show on EasyAdmin a custom property, here is an example :
class Book
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
public $id;
/**
* #ORM\Column(type="string")
*/
public $name;
/**
* #ORM\Column(type="float")
*/
public $price;
public function getBenefit(): float
{
// Here the method to retrieve the benefits
}
}
In this example, the custom parameter is benefit it's not a parameter of our Entity and if we configure EasyAdmin like that, it works !
easy_admin:
entities:
Book:
class: App\Entity\Book
list:
fields:
- { property: 'title', label: 'Title' }
- { property: 'benefit', label: 'Benefits' }
The problem is if the function is a bit complexe and need for example an EntityRepository, it becomes impossible to respect Controller > Repository > Entities.
Does anyone have a workaround, maybe by using the AdminController to show custom properties properly in EasyAdmin ?
You shouldn't put the logic to retrieve the benefits inside the Book entity, especially if it involves external dependencies like entityManager.
You could probably use the Doctrine events to achieve that. Retrieve the benefits after a Book entity has been loaded from the DB. Save the benefits before or after saving the Book entity in the DB.
You can find out more about it here https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html
class Book
{
...
public $benefits;
}
// src/EventListener/RetrieveBenefitListener.php
namespace App\EventListener;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Entity\Book;
class RetrieveBenefitListener
{
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
// only act on some "Book" entity
if (!$entity instanceof Book) {
return;
}
// Your logic to retrieve the benefits
$entity->benefits = methodToGetTheBenefits();
}
}
// src/EventListener/SaveBenefitListener.php
namespace App\EventListener;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Entity\Book;
class SaveBenefitListener
{
public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();
// only act on some "Book" entity
if (!$entity instanceof Book) {
return;
}
// Your logic to save the benefits
methodToSaveTheBenefits($entity->benefits);
}
}
// services.yml
services:
App\EventListener\RetrieveBenefitListener:
tags:
- { name: doctrine.event_listener, event: postLoad }
App\EventListener\SaveBenefitListener:
tags:
- { name: doctrine.event_listener, event: postUpdate }
This is just an example, I haven't tested the code. You will probably have to add the logic for the postPersist event if you create new Book objects.
Depending on the logic to retrieve the benefits (another DB call? loading from an external API?), you might want to to approach the problem differently (caching, loading them in your DB via a cron job, ...)

Subscriber call twice symfony

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

Symfony2 and FOSUserBundle: performing other operations when updating/creating a user

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.

Call a method after user Login

I was wondering if there is away to call a function after the user login.
Here is the code I want to call:
$point = $this->container->get('process_points');
$point->ProcessPoints(1 , $this->container);
You can find the events FOSUserBundle fires in the FOSUserEvents class. More specifically, this is the one you are looking for:
/**
* The SECURITY_IMPLICIT_LOGIN event occurs when the user is logged in programmatically.
*
* This event allows you to access the response which will be sent.
* The event listener method receives a FOS\UserBundle\Event\UserEvent instance.
*/
const SECURITY_IMPLICIT_LOGIN = 'fos_user.security.implicit_login';
The documentation for hooking into those events can be found on the Hooking into the controllers doc page. In your case, you will need to implement something like this:
namespace Acme\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class LoginListener implements EventSubscriberInterface
{
private $container;
public function __construct($container)
{
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::SECURITY_IMPLICIT_LOGIN => 'onLogin',
SecurityEvents::INTERACTIVE_LOGIN => 'onLogin',
);
}
public function onLogin($event)
{
// FYI
// if ($event instanceof UserEvent) {
// $user = $event->getUser();
// }
// if ($event instanceof InteractiveLoginEvent) {
// $user = $event->getAuthenticationToken()->getUser();
// }
$point = $this->container->get('process_points');
$point->ProcessPoints(1 , $this->container);
}
}
You should then define the listener as a service and inject the container. Alternatively, you could inject just the service you need instead of the whole container.
services:
acme_user.login:
class: Acme\UserBundle\EventListener\LoginListener
arguments: [#container]
tags:
- { name: kernel.event_subscriber }
There is also another method which involves overriding the controller, but as noted in the documentation, you have to duplicate their code so it's not exactly clean and bound to break if (or rather, when) FOSUserBundle is changed.

Resources