FOSUserBundle Form Registration Overriding - symfony

I've got a problem when I want to override the FOSUserBundle registration Form.
The deal is, in the User entity, some of the users can have a "Sponsor" (a sponsor is a ManyToOne to the same entity), to be more explicit, this is the User Entity :
<?php
namespace Diz\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="users")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* create FK "sponsor_id" referenced to the id field on the same table
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="sponsor_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $sponsor;
public function __construct()
{
// import FOSUserBundle properities ->
parent::__construct();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set sponsor
*
* #param Dizsurf\UserBundle\Entity\User $sponsor
*/
public function setSponsor(\Dizsurf\UserBundle\Entity\User $sponsor)
{
$this->sponsor = $sponsor;
}
/**
* Get sponsor
*
* #return Dizsurf\UserBundle\Entity\User
*/
public function getSponsor()
{
return $this->sponsor;
}
}
You see ?
Then, to override the RegistrationFormType, I've created one with the official help :
<?php
namespace Diz\UserBundle\Form\Type;
use Symfony\Component\Form\FormBuilder;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilder $builder, array $options)
{
parent::buildForm($builder, $options);
// add your custom field
$builder->add('sponsor', 'fos_user_username');
}
public function getName()
{
return 'diz_user_registration';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Diz\UserBundle\Entity\User', // Ni de modifier la classe ici.
);
}
}
And that's all ! Look like to be pretty simple ! But...
To simply convert the username into a User Entity, FOS advice to use "fos_user_username" in the builder.
Ok for me, but when I test this form :
With a sponsor who does exist, I've got this error "Please enter a password". (of course I've entered the password twice..).
But when I submit a form with an user whose does not exist, the registration form was submitted with success !
Have I done something wrong ?
Thank you for your help ! ;-)
Dizda.

Fixed.
I've just upgraded symfony from 2.0.10 to 2.1 and the problem is not present anymore !

Related

FOSUserBundle - add login extra field

I have a registration form with extra field. Below is my code:
service.yml
services:
app.form.registration:
class: AppBundle\Form\FOSUserBundle\Registration\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
My extended registration form:
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('company', new CompanyForm(), [
'label' => 'form.company.header', 'translation_domain' => 'FOSUserBundle'
]);
}
public function getParent()
{
return 'fos_user_registration';
}
public function getName()
{
return 'app_user_registration';
}
}
Comapny form:
class CompanyForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', [
'label' => 'form.company', 'translation_domain' => 'FOSUserBundle'
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Company',
));
}
public function getName()
{
return 'app_form_company';
}
}
User entity:
class User extends BaseUser
{
public function __construct()
{
parent::__construct();
// your own logic
}
use Bleamable;
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Company",cascade={"persist"})
* #ORM\JoinColumn(name="company", referencedColumnName="id")
*/
protected $company;
Comapny entity
/**
* #ORM\Entity
* #ORM\Table(name="company")
* #UniqueEntity(fields={"identifier"})
*
* #ORM\HasLifecycleCallbacks
*/
class Company
{
use Timestampable;
// use Bleamable;
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", nullable=false)
* #Assert\NotBlank()
* #Assert\NotNull()
*/
private $name;
/**
* #var string
* #ORM\Column(type="string", nullable=true)
*/
private $identifier;
So basicly while registration user must provide company name
I want to add that company name to login form and check if it is valid.
If username, password and company name is OK user should be logged in. I know how to override fos_user_registration but I can't find information about login form... Could you help me?
There is no LoginFormType or equivalent. You must override the login template (easy) and add custom logic for taking the new field value into account, namely, create a custom User Provider (less easy) : http://symfony.com/doc/current/cookbook/security/custom_provider.html.
You can look at FOSUserBundle's one to get an idea :
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Security/UserProvider.php
For the template
As per the docs, the easiest solution is to :
Define a new template of the same name in the app/Resources directory
For the login, the template is called login.html.twig and is in a folder called Security.
You can copy the original here.
After having copied it in app/Resources/FOSUserBundle/views/Security, just add the lines you need, like :
<label for="company">{{ 'your.company'|trans }}</label>
<input type="text" id="company" name="company" value="{{ company }}" required="required" />
If you want to enforce validation on the company field, you should override validation too : see https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_validation.html
You must create a new LogIn form in the child bundle that you have created for FOSUserBundle. This form declare it as a child of the LoginForm of the FOSUserBundle and then add a new field for the Company. Then override the Security/loginAction and render the new form you just created and not the default one of the FOSUserBundle.

How to use the registration procedure of FOSUserBundle from a third controller

I have to persist an entity (let's call it Entity for simplicity) in the database that has to be referenced to a User handled with FOSUserBundle. To make this reference I have a column entity_table.userId.
When the new Entity is created, I have to:
Create the User through the registration procedure of FosUserBundle;
Get the ID of the new created User: [meta code] $userId = $get->newCreatedUserId();
Set this id in Entity: $entity->setUserId($userId);
Persist the Entity to the database.
How can I integrate the registration procedure of FosUserBundle into the controller that persists my Entity?
MORE DETAILS
In the first time I tried to simply copy the code from the method registerAction() of the RegistrationController of FOSUserBundle: a quick and dirty approach that, anyway didn't work as i get an error as the User class i passed was wrong (I passed my custom User entity I use to overwrite the bundle).
This kind of approach has also other drawbacks:
I cannot control the registration procedure (send or decide to not send confirmation e-mails, for example);
I cannot use the builtin checks on passed data;
I cannot be sure that on FOSUserBundles updates my custom method continue to work
Others I cannot imagine at the moment...
So, I'd like to create the user in the cleanest way possible: how can i do this? Which should be a good approach?
A controller forwarding?
Anyway, an "hardcoded" custom method that emulates the registerAction() method?
A custom registration form?
I have read a lot of discussions here at StackOverflow and on Internet, I read the documentation of FOSUserBundle and of Symfony too, but I cannot decide for the good approach, also because I'm not sure I have understood all the pros and cons of each method.
If someone can put me on the right way... :)
SOMETHING MORE ABOUT MY REGISTRATION FLOW
I have a getStarted procedure handled by the controller GetStarteController.
In it I have two methods:
indexAction(), that displays a registration form with only the field "email";
endAction(), that receive the form and creates a Company using the passed e-mail (it gets the domain part only of the email).
HERE IS A WORKING MESSY CODE (inside it for Companies and Stores are called some methods that exists in the source code but are not in the posted classes below, as setBrand() or setUrl(), for example).
// AppBundle/Controller/getStartedController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use MyVendor\UserBundle\Entity\User;
use AppBundle\Entity\Companies;
use AppBundle\Entity\Stores;
class GetStartedController extends Controller
{
/**
* #Route("getstarted")
* #Template()
*/
public function indexAction()
{
$data = array();
$form = $this->createFormBuilder($data, array(
'action' => $this->generateUrl('getStartedEnd'),
))
->add('email', 'email')
->add('submit', 'submit')
->getForm();
return array(
'form' => $form->createView(),
);
}
/**
* #Route("getstarted/end", name="getStartedEnd")
* #Template()
*/
public function endAction(Request $request)
{
$form = $this->createFormBuilder()
->add('email', 'email')
->add('submit', 'submit')
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
} else {
/** #todo here we have to raise some sort of exception or error */
echo 'no data submitted (See the todo in the code)';exit;
}
// Pass the email to the template
$return['email'] = $data['email'];
// Get the domain part of the email and pass it to the template
$domain = explode('#', $data['email']);
$return['domain'] = $domain[1];
// 1) Create the new user
$user = new User();
// Get the token generator
$tokenGenerator = $this->container->get('fos_user.util.token_generator');
$user->setEmail($return['email']);
$userRandomUsername = substr($tokenGenerator->generateToken(), 0, 12);
$user->setUsername('random-' . $userRandomUsername);
$plainPassword = substr($tokenGenerator->generateToken(), 0, 12);
$encoder = $this->container->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $plainPassword);
// Set the password for the user
$user->setPassword($encoded);
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
// Perstist the user in the database
$userManager->updateUser($user);
$userId = $user->getId();
// 2) Create the Company object
$company = new Companies();
$company->setBrand($return['domain'])
->setAdded(new \DateTime())
->setOwnerId($userId);
// 3) Create the Store object
$store = new Stores();
$store->setEmail($return['email'])
->setUrl($return['domain'])
->setAdded(new \DateTime());
// Get the Entity Manager
$em = $this->getDoctrine()->getManager();
// Persist Company and get its ID
$em->persist($company);
$em->flush();
$return['companyId'] = $company->getId();
// Set the property branchOf of the Store object
$store->setBranchOf($return['companyId']);
// Persist the Store object
$em->persist($store);
$em->flush();
$return['storeId'] = $store->getId();
return $return;
}
}
Here the User Entity that ovewrites the one provided by FOSUserBundle
// MyVendor/UserBundle/Entity/User.php
namespace MyVendor\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="prefix_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
Some essential code of Companies.php
// AppBundle/Entity/Companies.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Companies
*
* #ORM\Table(name="companies")
* #ORM\Entity
*/
class Companies
{
/**
* #var integer
*
* #ORM\Column(name="ownerId", type="integer", nullable=false)
*/
private $ownerid;
/**
* Set ownerid
*
* #param integer $ownerid
* #return Companies
*/
public function setOwnerid($ownerid)
{
$this->ownerid = $ownerid;
return $this;
}
/**
* Get ownerid
*
* #return integer
*/
public function getOwnerid()
{
return $this->ownerid;
}
}
Some essential code of Stores.php
// AppBundle/Entity/Stores.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Stores
*
* #ORM\Table(name="stores", uniqueConstraints={#ORM\UniqueConstraint(name="branchOf", columns={"branchOf"})})
* #ORM\Entity
*/
class Stores
{
/**
* #var integer
*
* #ORM\Column(name="branchOf", type="integer", nullable=false)
*/
private $branchof;
/**
* Set branchof
*
* #param integer $branchof
* #return Stores
*/
public function setBranchof($branchof)
{
$this->branchof = $branchof;
return $this;
}
/**
* Get branchof
*
* #return integer
*/
public function getBranchof()
{
return $this->branchof;
}
}
You can use a custom registration form but the best way is clearly to listen to registration event dispatched by FOSUser.
Here is an example :
class RegistrationListener implements EventSubscriberInterface
{
/**
* L'entity manager
*
* #var EntityManager
*/
private $em;
/**
* Constructeur de l'EventListener
*
* #param \Doctrine\ORM\EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* Triggered when FOSUserEvents::REGISTRATION_INITIALIZE is caught.
*
* #param \FOS\UserBundle\Event\UserEvent $userEvent
*/
public function onRegistrationInit(UserEvent $userEvent)
{
$user = $userEvent->getUser();
// Define your own logic there
}
}
Don't forget to make this listener a service:
#services.yml
services:
oe_user.registration:
class: OrienteExpress\UserBundle\EventListener\RegistrationListener
# arguments are optional but you still can need them
# so I let the EM as example which is an often used parameter
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: kernel.event_subscriber }
You'll find the complete list of event dispatched by FOSUser here
Moreover, Symfony entities are a model of objects. That said, you need to understand that you don't work with ids within your model, but object.
You should not use thing such as $var->setUserId() within entites. Doctrine is there to manage your relations, so be carefull about this. You might face unexpected problem by not using Symfony & Doctrine the way it has been designed for.
EDIT:
In your company entity, your relation is beetween a Company and a User objects. That means you dont need a User id in your company but just a instance of User.
I think you might go back to the basics before wanting to do advanced stuff.
Your relation beetween the user and the company should not be designed by an integer attribute but a real doctrine relation.
Ex:
class Company {
/**
* #ORM\ManyToOne(targetEntity="Path\To\User")
* #ORM\JoinColumn(nullable=false)
*/
private $owner;
/**
* #param $user User
*/
public function setUser(User $user)
{
$this->user = $user;
}
}
Then when you'll create a new company. You won't need to know the User's id or even insert it to make the link between them. But if you are not aware yet of this, once again, I think you should go back to the basics of Symfony since this is one of the most (maybe the most) important feature to master.

Symfony2 - Callback validator on collection

In symfony2 I'm tying to use a callback to validate my form, but this callback is never called. The class wherein the callback is, is called in the main form through a collection.
Here is my code...
Main class :
class InscriptionType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('inscriptionReponses','collection',array('label'=>false,
'type'=>new InscriptionReponseType(),
'error_bubbling'=>false,
'by_reference'=>false))
;
}
}
InscriptionReponse class :
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContextInterface;
/**
* InscriptionReponse
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Ptolemee\ColloqueBundle\Entity\InscriptionReponseRepository")
* #Assert\Callback(methods={"formValidation"})
*/
class InscriptionReponse
{
/* ... some code ... */
public function formValidation(ExecutionContextInterface $context)
{
die('not dying ?');
}
}
I don't understand what's wrong... any help would be highly appreciated. tahnks.
Nicolas.
The callback function of the collection is being called when you add #Assert\Valid to the collection in the containing entity.
Let's say Inscription has a collection of InscriptionResponses:
class Inscription
{
/**
* #Assert\Valid()
*/
private $inscriptionResponses;
}
class InscriptionResponse
{
/**
* #Assert\Callback
*/
public function formValidation(ExecutionContextInterface $context)
{
die('dying');
}
}
This works regardless of the value of the error_bubbling option.
base on what is written in the documentation:
http://symfony.com/doc/current/reference/constraints/Callback.html
instead of
/**
* #Assert\Callback(methods={"formValidation"})
*/
class InscriptionReponse
{
you should move the annotation above the function itself
class InscriptionReponse
{
/**
* #Assert\Callback
*/
public function formValidation(ExecutionContextInterface $context)
{
die('not dying ?');
}
The method you used was valid in version 2.3, you use 2.4 probably now

Symfony 2.3.6 generate CRUD panel from FOSuserbundle

I trying to create CRUD panel from FOSUserBundle but i have some troubles. I mean that i created User entity for FOS and made crud panel for this entity. Now when i trying to add new user i have error like below
Neither the property "expiresAt" nor one of the methods "getExpiresAt()", "isExpiresAt()", "hasExpiresAt()", "_get()" or "_call()" exist and have public access in class "Bn\UserBundle\Entity\User".
It's my first project so please understand when i will ask for simple function, some suggestion ? What is wrong ?
<?php
namespace Bn\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="fos_user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get expiresAt
*
* #return \DateTime
*/
public function getExpiresAt()
{
return $this->expiresAt;
}
/**
* Get credentials_expire_at
*
* #return \DateTime
*/
public function getCredentialsExpireAt()
{
return $this->credentialsExpireAt;
}
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Now is working but i don't know why i must declare again function for getter.
I believe this means you need to add public accessors setExpiresAt() and getExpiresAt() to your User entity.
You need only add getExpiresAt to your User.php class. FOSUserBundle\User doesn't have getter for this field, but Sensio generator creates views for all fields.
public function getExpiresAt()
{
return $this->expiresAt;
}

Symfony2 FosUserBundle and SonataUserBundle : overriding entities?

I use FosUserBundle and SonataUserBundle for my Symfony2 project.
I get confused now. I want to add fields for the entity User but it's not working. There is no update for the schema for example.
Here is my config :
AppKernel:
...
new FOS\UserBundle\FOSUserBundle(),
new Sonata\UserBundle\SonataUserBundle(),
new Application\Sonata\UserBundle\ApplicationSonataUserBundle('FOSUserBundle')
config.yml:
...
# FOSUserBundle Configuration
fos_user:
db_driver: orm # BDD type
firewall_name: main # firewall name
user_class: Application\Sonata\UserBundle\Entity\User # entity class defined
And the User entity with added fields, in app/Application/Sonata/userBundle/Entity/
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
/**
* This file has been generated by the Sonata EasyExtends bundle ( http://sonata- project.org/easy-extends )
*
* References :
* working with object : http://www.doctrine-project.org/projects/orm/2.0 /docs/reference/working-with-objects/en
*
* #author <yourname> <youremail>
*/
class User extends BaseUser
{
/**
* #var integer $id
*/
protected $id;
/**
* #var string
*/
protected $institution;
/**
* #var string
*/
protected $department;
/**
* #var string
*/
protected $city;
/**
* #var string
*/
protected $country;
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
/**
* #return string
*/
public function getInstitution()
{
return $this->institution;
}
/**
* #return string
*/
public function getDepartment()
{
return $this->department;
}
/**
* #return string
*/
public function getCity()
{
return $this->city;
}
/**
* #return string
*/
public function getCountry()
{
return $this->country;
}
}
In my database, table fos_user_user seems to be the table with user saved data.
Added fields (country, city...) are not created when calling "php app/console doctrine:schema:update --force". How to add fields to the user entity ? I'm lost in fosuserbundle and sonatauserbundle....
In fact, it appear that the way i use fos and sonata user bundles force me to use XML definition. So for example adding a field called "city" you have to add these lines :
User.php (in /app/Application/Sonata/UserBundle/Entity/) :
protected $city;
User.orm.xml (in /app/Application/Sonata/UserBundle/Ressource/config/doctrine/) :
<field name="city" column="city" type="text"/>
Maybe putting this in the documentation of the bundles would be interesting for newbies ;)
I found a better solution if you want use annotations.
Delete UserBundle/Resorces/config/doctrine folder and you can use annotations in your UserBundle/Entity folder

Resources