Pregunta

I have run into this problem with a couple of my entities now so I thought to try and get a hang of what really goes on, and I turn to my best source here (will add a bounty to this question as soon as it is eligible).

My user is part of a user group. I have a validator for the userGroup entity to make sure no two userGroups have the same name.

The problem is that when I go to editing a user, and try to select that userGroup for the user, symfony2 is behaving as if I were trying to create another userGroup with that same name, when in reality all I am doing is I am trying to select that userGroup for the user.

A user entity

<?php
// src/BizTV/UserBundle/Entity/User.php

namespace BizTV\UserBundle\Entity;

use BizTV\UserBundle\Validator\Constraints as BizTVAssert;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;

use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;

use BizTV\BackendBundle\Entity\company as company;

/**
 * @ORM\Entity
 * @ORM\Table(name="fos_user")
 */
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;
    }
}

The UserGroup entity that the User is linked to:

<?php

namespace BizTV\UserBundle\Entity;

use BizTV\UserBundle\Validator\Constraints as BizTVAssert;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * BizTV\UserBundle\Entity\UserGroup
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class UserGroup
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     * @BizTVAssert\NameExists
     * @ORM\Column(name="name", type="string", length=255)
     * @Assert\NotBlank(message = "Du måste ange ett gruppnamn")
     */
    private $name;

    /**
    * @var object BizTV\BackendBundle\Entity\company
    *  
    * @ORM\ManyToOne(targetEntity="BizTV\BackendBundle\Entity\company")
    * @ORM\JoinColumn(name="company", referencedColumnName="id", nullable=false)
    */
    protected $company; 

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 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;
    }
}

The NameExistsValidator

<?php 

namespace BizTV\UserBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManager as EntityManager;

class NameExistsValidator extends ConstraintValidator
{

    private $container;
    private $em;

    public function __construct(Container $container, EntityManager $em) {
        $this->container = $container;
        $this->em = $em;
    }   

    public function isValid($value, Constraint $constraint)
    {

        $em = $this->em;
        $container = $this->container;

        $company = $this->container->get('security.context')->getToken()->getUser()->getCompany();

        //Fetch entities with same name
        $repository = $em->getRepository('BizTVUserBundle:UserGroup');
        //$repository = $this->getDoctrine()->getRepository('BizTVContainerManagementBundle:Container');
        $query = $repository->createQueryBuilder('c')
            ->where('c.company = :company')
            ->setParameter('company', $company)
            ->orderBy('c.name', 'ASC')
            ->getQuery();
        $groups = $query->getResult();      

        foreach ($groups as $g) {
            if ($g->getName() == $value) {
                $this->setMessage('Namnet '.$value.' är upptaget, vänligen välj ett annat', array('%string%' => $value));
                return false;
            }
        }

        return true;
    }
}

User edit form

<?php

namespace BizTV\UserBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormValidatorInterface;
use Symfony\Component\Form\FormError;

use Doctrine\ORM\EntityRepository;

class editUserType extends AbstractType
{

    function __construct($company)
    {
        $this->company = $company;
    }

    public function buildForm(FormBuilder $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 lösenord  ', 'second_name' => 'Upprepa lösenord  ',));

        $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;
                }
            ));         

        $builder-> addValidator(new CallbackValidator(function(FormInterface $form){
          $email = $form->get('email')->getData();      
            if (empty( $email )) {
              $form['email']->addError(new FormError("Du måste ange en epostadress för användaren"));
            }
        }));

        $builder-> addValidator(new CallbackValidator(function(FormInterface $form){
          $username = $form->get('username')->getData();
            if (strpos($username,'#') !== false) {
              $form['username']->addError(new FormError("Användarnamnet får inte innehålla tecknet #"));
            }
        }));        

        $builder-> addValidator(new CallbackValidator(function(FormInterface $form){
          $username = $form->get('username')->getData();
            if (empty($username)) {
              $form['username']->addError(new FormError("Du måste ange ett namn för användaren"));
            }
        }));        

        //TODO check if username exists 

    }

    public function getName()
    {
        return 'biztv_userbundle_newusertype';
    }
}
¿Fue útil?

Solución

Your NameExistsValidator does this:

  • Fail if I find any user-group with the name I'm checking.

But I think you want it to do this:

  • Fail if I find another user-group with the name I'm checking.

In other words: the validator needs the complete UserGroup entity (or at least its id and name) to check for a user-group with the same name but different id.

Symfony 2 already has a UniqueEntity validator, why don't you use it?

Using annotations this would look something like this:

/**
 * @ORM\Entity
 * @AssertUniqueEntity(fields={"name"}, message="This name already exists")
 */
class UserGroup
{

Otros consejos

One possible and simplest solution is to define Validation Groups. For example, when you create a group, you can use the validation group named 'create' or 'groups' and when you create a user does not specify a group. Then validator will not apply to user creation process.

Validation Groups can be assigned dynamically in the form class. An example of this you can see in the documentation.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top