Since the
@UniqueEntity(fields = "username"...
below does absolutely nothing (maybe because username is inherited and not defined explicitly by me in the user class?) I guess I will have to do something as simple as checking for an existing user with that name manually either in the controller or form validation. So I go with form validation.
What I don't know how to do is how to reach the database from the form class, use the queryBuilder from inside the form class manually.
Here's my form class:
namespace BizTV\UserBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormError;
use Doctrine\ORM\EntityRepository;
class editUserType extends AbstractType
{
function __construct($company)
{
$this->company = $company;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$company = $this->company;
$builder
->add('locked', 'checkbox', array('label' => 'Kontot är låst, användaren kan inte logga in '))
->add('username', 'text', array('label' => 'Användarnamn '))
;
$builder
->add('userGroup', 'entity', array(
'label' => 'Användargrupp',
'empty_value' => 'Ingen grupptillhörighet',
'property' => 'name',
'class' => 'BizTV\UserBundle\Entity\UserGroup',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) use ($company) {
$qb = $er->createQueryBuilder('a');
$qb->where('a.company = :company');
$qb->setParameters( array('company' => $company) );
$qb->orderBy('a.name', 'ASC');
return $qb;
}
));
$builder
->add('email', 'email', array('label' => 'Epost '))
->add('plainPassword', 'repeated', array('type' => 'password', 'first_name' => 'Nytt_losenord', 'second_name' => 'Upprepa_losenord',));
$builder
->add('roles', 'choice', array(
'label' => 'Roller',
'expanded' => true,
'multiple' => true,
'choices' => array(
'ROLE_CONTENT' => 'Innehåll (Användaren kan lägga till, redigera och ta bort innehåll där du nedan beviljar åtkomst)',
'ROLE_LAYOUT' => 'Skärmlayout (Användaren kan skapa ny skärmlayout, redigera befintlig eller ta bort gällande skärmlayout där du nedan beviljar åtkomst)',
'ROLE_VIDEO' => 'Videouppladdning (Användaren har rätt att ladda upp videofiler till företagets mediabibliotek)',
'ROLE_ADMIN' => 'Administratör (Användaren är administratör med fulla rättigheter till allt precis som det konto du nu är inloggad på, var mycket restriktiv med att tilldela denna behörighet).',
),
))
;
$builder
->add('access', 'entity', array(
'label' => 'Behörigheter',
'multiple' => true, // Multiple selection allowed
'expanded' => true, // Render as checkboxes
'property' => 'select_label',
'class' => 'BizTV\ContainerManagementBundle\Entity\Container',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er) use ($company) {
$qb = $er->createQueryBuilder('a');
$qb->innerJoin('a.containerType', 'ct');
$qb->where('a.containerType IN (:containers)', 'a.company = :company');
$qb->setParameters( array('containers' => array(1,2,3,4), 'company' => $company) );
$qb->orderBy('ct.id', 'ASC');
return $qb;
}
));
$validatorEmail = function(FormEvent $event){
$form = $event->getForm();
$myExtraField = $form->get('email')->getData();
if (empty($myExtraField)) {
$form['email']->addError(new FormError("Du måste ange en epostadress för användaren"));
}
};
$validatorUsername = function(FormEvent $event){
$form = $event->getForm();
$myExtraField = $form->get('username')->getData();
if (empty($myExtraField)) {
$form['username']->addError(new FormError("Du måste ange ett namn för användaren"));
}
elseif ( preg_match('/^[a-zA-Z0-9_]+$/',$myExtraField) == false ) {
$form['username']->addError(new FormError("Du får inte använda andra specialtecken än understreck (_)"));
}
};
$validatorUsernameTaken = function(FormEvent $event){
$form = $event->getForm();
$myExtraField = $form->get('username')->getData();
//TODO: CHECK IN DB FOR USER WITH THAT NAME
if ($taken) {
$form['username']->addError(new FormError("Du måste ange ett namn för användaren"));
}
};
// adding the validator to the FormBuilderInterface
$builder->addEventListener(FormEvents::POST_BIND, $validatorEmail);
$builder->addEventListener(FormEvents::POST_BIND, $validatorUsername);
$builder->addEventListener(FormEvents::POST_BIND, $validatorUsernameTaken);
//TODO check if username exists
}
public function getName()
{
return 'biztv_userbundle_newusertype';
}
}
BTW here is my entity:
namespace BizTV\UserBundle\Entity;
use BizTV\UserBundle\Validator\Constraints as BizTVAssert;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use BizTV\BackendBundle\Entity\company as company;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
* @UniqueEntity(fields = "username", message = "En användare med det namnet finns redan, försök igen.")
*/
class User extends BaseUser implements AdvancedUserInterface
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
//TODO: Add constraint on $name * @BizTVAssert\NameExists (and finish coding this constraint)
/**
* @var object BizTV\BackendBundle\Entity\company
*
* @ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
* @ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
*/
protected $company;
/**
* @var object BizTV\UserBundle\Entity\UserGroup
* @ORM\ManyToOne(targetEntity="BizTV\UserBundle\Entity\UserGroup")
* @ORM\JoinColumn(name="userGroup", referencedColumnName="id", nullable=true)
*/
protected $userGroup;
/**
* @ORM\ManyToMany(targetEntity="BizTV\ContainerManagementBundle\Entity\Container", inversedBy="users")
* @ORM\JoinTable(name="access")
*/
private $access;
/**
* @var object BizTV\ContainerManagementBundle\Entity\Container
*
* This only applies to the BizTV server user accounts or "screen display accounts". Others will have null here.
*
* @ORM\ManyToOne(targetEntity="BizTV\ContainerManagementBundle\Entity\Container")
* @ORM\JoinColumn(name="screen", referencedColumnName="id", nullable=true)
*/
protected $screen;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
protected $isServer;
public function __construct()
{
parent::__construct();
$this->access = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set company
*
* @param BizTV\BackendBundle\Entity\company $company
*/
public function setCompany(\BizTV\BackendBundle\Entity\company $company)
{
$this->company = $company;
}
/**
* Get company
*
* @return BizTV\BackendBundle\Entity\company
*/
public function getCompany()
{
return $this->company;
}
/**
* Add access
*
* @param BizTV\ContainerManagementBundle\Entity\Container $access
*/
public function addContainer(\BizTV\ContainerManagementBundle\Entity\Container $access)
{
$this->access[] = $access;
}
/**
* Get access
*
* @return Doctrine\Common\Collections\Collection
*/
public function getAccess()
{
return $this->access;
}
/**
* Set screen
*
* @param BizTV\ContainerManagementBundle\Entity\Container $screen
*/
public function setScreen(\BizTV\ContainerManagementBundle\Entity\Container $screen)
{
$this->screen = $screen;
}
/**
* Get screen
*
* @return BizTV\ContainerManagementBundle\Entity\Container
*/
public function getScreen()
{
return $this->screen;
}
/**
* Set isServer
*
* @param boolean $isServer
*/
public function setIsServer($isServer)
{
$this->isServer = $isServer;
}
/**
* Get isServer
*
* @return boolean
*/
public function getIsServer()
{
return $this->isServer;
}
/**
* Set userGroup
*
* @param BizTV\UserBundle\Entity\UserGroup $userGroup
*/
public function setUserGroup(\BizTV\UserBundle\Entity\UserGroup $userGroup = null)
{
$this->userGroup = $userGroup;
}
/**
* Get userGroup
*
* @return BizTV\UserBundle\Entity\UserGroup
*/
public function getUserGroup()
{
return $this->userGroup;
}
//Below should be part of base user class but doesn't work so I implement it manually.
/**
* Get lock status
*
* @return boolean
*/
public function getLocked()
{
return $this->locked;
}
}
----- UPDATE -----
My controller's update User method:
public function createUserAction()
{
$tempCompany = $this->container->get('security.context')->getToken()->getUser()->getCompany()->getId();
$entity = new User();
$request = $this->getRequest();
$form = $this->createForm(new newUserType($tempCompany), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user); //get encoder for hashing pwd later
//get company
$currentCompany = $this->container->get('security.context')->getToken()->getUser()->getCompany();
//Set company
$entity->setCompany( $currentCompany );
$tempUsername = $entity->getUsername();
$entity->setUsername($currentCompany->getCompanyName() . "-" . $tempUsername);
$entity->setConfirmationToken(null);
$entity->setEnabled(true);
$tempPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($tempPassword);
$em->persist($entity);
$em->flush();
$helper = $this->get('biztv.helper.globalHelper');
$helper->log('success', 'Användare <strong>'.$entity->getUsername().'</strong> har skapats.');
return $this->redirect($this->generateUrl('UserManagement'));
}
return $this->render('BizTVUserBundle:Default:newUser.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
So am I doing this all wrong? I am in fact using the userManager as you suggest, but only for encoding the password. Should I be persisting the $user created with $userManager instead of persisting my own $entity, would that let me take advantage of the unique checking etc.?
------------ UPDATE ---------------
@jmickell (posting answer here since there is no code formatting in the comments, thank you for your time. I do however not have a solution yet...)
I altered the code according to this
$em = $this->getDoctrine()->getManager();
$userManager = $this->container->get('fos_user.user_manager');
$user = $userManager->createUser();
$encoder = $this->container->get('security.encoder_factory')->getEncoder($user); //get encoder for hashing pwd later
//get company
$currentCompany = $this->container->get('security.context')->getToken()->getUser()->getCompany();
//Set company
$entity->setCompany( $currentCompany );
$tempUsername = $entity->getUsername();
$entity->setUsername($currentCompany->getCompanyName() . "-" . $tempUsername);
$entity->setConfirmationToken(null);
$entity->setEnabled(true);
$tempPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($tempPassword);
$userManager->updateUser($entity);
And it saves the user like you said it would without the flush, there is however no validation on the username/password. And I guess that is something that must happen above this, in the form isValid? or inside the form class itself?
If you take a look at my form class (very top of the question) am I missing something mandatory for those validators of usernameCanonical and emailCanonical to be function?
Also, If I replace this
$tempPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($tempPassword);
with this (as I understood you suggested)
$entity->setPlainPassword( $entity->getPassword() );
I end up with a SQL error telling me password field can't be empty...
I have also tried adding the validation groups to the form like this, but no change in behaviour at all
So I have looked here (symfony2 using validation groups in form) and tried that solution by adding this to the controller when building the form, nothing happens (still jumps straight to the INSERT INTO SQL error)
$entity = new User();
$request = $this->getRequest();
$form = $this->createForm(new newUserType($tempCompany), $entity, array('validation_groups'=>'registration'));
$form->bind($request);
I have also tried to set this validatino group "registration group" (that is supposed to give you unique user & email validation "for free") this way, in the form class:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array('registration'),
));
}