Symfony2 FOSUserBundle relates to another Entity automatically - symfony

I recently implemented the FOSUserBundle in my website as the login procedure. I want it to expand the Author class. So whenever a new user is registered via the FOSUSerBundle a new entry is created in the Author class. Inside the Author class I set slug, createdAt and other useful parameters. The field I want to pass to Authot entity from FOSUserBundle is the "Name" field. Then I want to cascade the FOSUser entity and if its deleted, delete also the Author entity.
So schematically FOSUserBundle.username => Author.name
I do not know how to implement this code except that it has a #ORM/OneToOne relationship. Any ideas please?

You'll have to insert the Author manually after your user registration is completed. The FOSUserBundle provides a way to hook into events like post registration completion. You can create a listener to the FOSUserEvents::REGISTRATION_COMPLETED event and create your Author entity there.
See documentation here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/controller_events.md
For example:
services.yml:
services:
my_user_registration_service:
class: MyBundle\EventListener\MyUserRegistrationListener
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
MyUserRegistrationListener:
namespace MyBundle\EventListener;
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use MyBundle\Entity\Author;
class EventSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'addAuthor',
);
}
public function addAuthor(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$author = new Author();
$author->setName($user->getUsername();
$this->em->persist($author);
$this->em->flush();
}
}

Related

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.

How to access repository methods for an entity in symfony2?

I am stuck with a problem please help me with it. Here is the scenarario:
I have an entity "User" and corresponding repository "UserRepository", inside my entity there are only getter and setter methods. All custom queries I have written to UserRepository. Now inside my UserController I am trying to access repository methods which I am not able to do so.
e.g.
User entity:
class User
{
...
public function getId()
{
return $this->id;
}
public function setId($id)
{
return $this->id=$id;
}
public function setProperty($property)
{
$this->property = $property;
}
public function getProperty()
{
return $this->property;
}
....
}
?>
UserRepository:
class UserRepository extends EntityRepository
{
public function findUsersListingById($id)
{
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$query = $em->createQuery(
"SELECT U
FROM UserEntityPathGoesHere
WHERE U.id IN (".implode(",", $id).")"
);
$users = $query->getResult();
return $users;
}
public function sayHelloWorld(){
echo ' Hello World';
}
}
?>
UserController
class UserController
{
...
$users=$this->getDoctrine()
->getRepository('MyUserEntityPath')
->findUsersListingById($ids);
//now I have multiple users I want to iterate through each user for associating additional data with each user
foreach($users as $user)
{
$temp = array();
//I am able to access getId method which is defined in User entity
$temp['id'] = $user->getId();
//however I am not able to access method from UserRepository, I tried something like below which gives me error call to undefined function sayHelloWorld
$temp['status'] = $user->sayHelloWorld();
....
}
}
....
How can I access repository methods for an entity? Is it possible ? If not then what are the alternatives for the solution?
Everything is possible however you should not access the entity's repository from the entity itself because of the separation of concerns.
See this Stackoverflow answer for more details.
Basically, the whole idea is that you want to have your application organized the following way.
In short:
Controller > Repository > Entities.
It should not go in the other direction otherwise it creates a mess.
If you want to go a bit further into the separation of concerns you could do the following.
Controller > Service > Repository > Entities
Alternative solutions:
Create a Twig extension that access a service (which access a repository) or a repository.
Create a method in your repository, call the method in your controller, map the data to IDs (keys of array are the IDs), pass the array to the template and then pull the data from the array using the entity IDs
Create a method in your repository, call the method in your controller, inject the data into your entities and access the data through the entity in your template.
There are probably others but you would know better how your application is organized.
If the bundle is Acme/DemoBundle, then one would expect at a minimum
User entity
namespace Acme/DemoBundle/Entity
use Doctrine\ORM\Mapping as ORM;
/**
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Acme/DemoBundle/Entity/UserRepository")
*/
class User
{
...
}
User repository
namespace Acme/DemoBundle/Entity
use Doctrine\ORM\Mapping as ORM;
class UserRepository extends EntityRepository
{
...
}
It is also true that with an array of ids, one can also do the following in a controller:
...
$em = $this->getDoctrine()->getManager();
$users = $em->getRepository("AcmeDemoBundle:User")->findAllById($idArray);
...
To iterate thru users in a controller, one can then use a foreach loop as in:
foreach ($users as $user) {
//each user is an array
...
$id = $user['id'];
...
}
or in a template:
{% for user in users %}
...
{{ user.firstName }}
...
{% endfor %}
You need to declare the UserRepository as an EntityRepository for your user entity. In your User entity add this annotation:
/**
* #ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\UserRepository")
*/
See the docs for a more detailed description.
You can use the postLoad event from doctrine and inject everything you want into the entity. The event listener looks like:
<?php
namespace AppBundle\EventListener;
use AppBundle\Entity\MyEntity;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* Class MyEntityListener
*/
class MyEntityListener
{
public function postLoad(LifecycleEventArgs $eventArgs)
{
/** #var MyEntity $document */
$document = $eventArgs->getEntity();
if(!($document instanceof MyEntity)){
return;
}
$document->setEntityManager($eventArgs->getEntityManager());
}
}
and service.yml:
services:
app.myentity.listener:
class: AppBundle\EventListener\MyEntityListener
tags:
- { name: doctrine.event_listener, event: postLoad }
Of cource your Entity needs the method setEntityManager and your're ready.

How to assign roles on successful registration?

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.

Set the email manually with FOSUserBundle

I am using Symfony 2 + FOSUserBundle to manage my users.
The problem is that I would like to build the email address "manually" from the username, and so, do not require any field for the email.
I will create the email from the username because one requirement to sign up is to have an email from my university which follows a strict format (username#schoolname.fr).
I managed to override the RegistrationFormType to avoid the email field from being added to my sign up page but I still have the error "Please enter an email" when I submit the form.
How can I prevent the validation of the email address and how could I "build" it from the username?
Thanks!
(Sorry for the English, I know it's not perfect...)
There is a simple way around it. It is even mentioned in the official FOSUserBundle documentation. You just need to override the controller.
Create your own Bundle and extend the FOSUserBundle:
class CustomUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
And then override the RegistrationController:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
// here you can implement your own logic. something like this:
$user = new User();
$form = $this->container->get('form.factory')->create(new RegistrationType(), $user);
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$user->setEmail($user->getUsername() . '#' . $user->getSchoolname() . '.fr');
// and what not. Also don't forget to either activate the user or send an activation email
}
}
}
}
You should write event listener for fos_user.registration.initialize. From code docs:
/**
* The REGISTRATION_INITIALIZE event occurs when the registration process is initialized.
*
* This event allows you to modify the default values of the user before binding the form.
* The event listener method receives a FOS\UserBundle\Event\UserEvent instance.
*/
const REGISTRATION_INITIALIZE = 'fos_user.registration.initialize';
More info about event dispatcher: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
And example event listener: http://symfony.com/doc/current/cookbook/service_container/event_listener.html
UPDATE - how to code?
In your config.yml (or services.yml or other extension like xml, php) define service like this:
demo_bundle.listener.user_registration:
class: Acme\DemoBundle\EventListener\Registration
tags:
- { name: kernel.event_listener, event: fos_user.registration.initialize, method: overrideUserEmail }
Next, define listener class:
namespace Acme\DemoBundle\EventListener;
class Registration
{
protected function overrideUserEmail(UserEvent $args)
{
$request = $args->getRequest();
$formFields = $request->get('fos_user_registration_form');
// here you can define specific email, ex:
$email = $formFields['username'] . '#sth.com';
$formFields['email'] = $email;
$request->request->set('fos_user_registration_form', $formFields);
}
}
Notice: Of course you can validate this email via injecting #validator to the listener.
Now you should hide email field in registration form. YOu can do that by overriden register_content.html.twig or (in my oppinion better way) override FOS RegistrationFormType like this:
namespace Acme\DemoBundle\Form\Type;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationFormType extends BaseType
{
// some code like __construct(), getName() etc.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// some code for your form builder
->add('email', 'hidden', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
;
}
}
Now your application is ready for setting email manually.

Symfony 2.0.3 Global template variable

I've got an Entity that I want to associate with the users session.
I created a service so that I could reach this info from where ever.
in the service i save the entities id in an session variable
and in the getEntity() method i get the session variable and with doctrine find the entity and return it.
this way to the template i should be able to call {{ myservice.myentity.myproperty }}
The problem is that myservice is used all over the place, and I don't want to have to get it in every since Action and append it to the view array.
Is there a way to make a service accessible from all views like the session {{ app.session }} ?
The solution
By creating a custom service i can get to that from where ever by using
$this->get('myservice');
this is all done by http://symfony.com/doc/current/book/service_container.html
But I'll give you some demo code.
The Service
This first snippet is the actual service
<?php
namespace MyBundle\AppBundle\Extensions;
use Symfony\Component\HttpFoundation\Session;
use Doctrine\ORM\EntityManager;
use MyBundle\AppBundle\Entity\Patient;
class AppState
{
protected $session;
protected $em;
function __construct(Session $session, EntityManager $em)
{
$this->session = $session;
$this->em = $em;
}
public function getPatient()
{
$id = $this->session->get('patient');
return isset($id) ? $em->getRepository('MyBundleStoreBundle:Patient')->find($id) : null;
}
}
Register it in you config.yml with something like this
services:
appstate:
class: MyBundle\AppBundle\Extensions\AppState
arguments: [#session, #doctrine.orm.entity_manager]
Now we can as I said before, get the service in our controllers with
$this->get('myservice');
But since this is a global service I didn't want to have to do this in every controller and every action
public function myAction()
{
$appstate = $this->get('appstate');
return array(
'appstate' => $appstate
);
}
so now we go create a Twig_Extension
Twig Extension
<?php
namespace MyBundle\AppBundle\Extensions;
use MyBundle\AppBundle\Extensions\AppState;
class AppStateExtension extends \Twig_Extension
{
protected $appState;
function __construct(AppState $appState) {
$this->appState = $appState;
}
public function getGlobals() {
return array(
'appstate' => $this->appState
);
}
public function getName()
{
return 'appstate';
}
}
By using dependency injection we now have the AppState Service that we created in the twig extension named appstate
Now we register that with the symfony (again inside the services section inside the config-file)
twig.extension.appstate:
class: MyBundle\AppBundle\Extensions\AppStateExtension
arguments: [#appstate]
tags:
- { name: twig.extension }
The important part being the "tags", since this is what symfony uses to find all twig extensions
We are now set to use our appstate in our twig templates by the variable name
{{ appstate.patient }}
or
{{ appstate.getPatient() }}
Awesome!
Maybe you can try this in your action ? : $this->container->get('templating')->addGlobal($name, $value)

Resources