symfony2 对子实体的验证防止编辑父实体

Posted

技术标签:

【中文标题】symfony2 对子实体的验证防止编辑父实体【英文标题】:symfony2 validation of child entity prevents editing of parent entity 【发布时间】:2013-09-04 19:52:59 【问题描述】:

我的几个实体现在遇到了这个问题,所以我想尝试了解真正发生的事情,我在这里求助于我最好的来源(将尽快为这个问题添加一个赏金符合条件)。

我的用户属于某个用户组。我有一个 userGroup 实体的验证器,以确保没有两个 userGroup 具有相同的名称。

问题是,当我去编辑一个用户并尝试为该用户选择该用户组时,symfony2 的行为就像我试图创建另一个具有相同名称的用户组,而实际上我所做的只是我正在尝试为用户选择该用户组。

用户实体

<?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;
    

用户链接到的用户组实体:

<?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;
    

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;
    

用户编辑表单

<?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';
    

【问题讨论】:

【参考方案1】:

您的NameExistsValidator 这样做:

如果我发现 任何 用户组与我正在检查的名称相同,则会失败。

但我认为你希望它这样做:

如果我发现 另一个 用户组与我正在检查的名称相同,则会失败。

换句话说:验证器需要完整的UserGroup 实体(或至少它的 id 和名称)来检查具有相同名称但不同 id 的用户组。

Symfony 2 已经有一个UniqueEntity 验证器,你为什么不使用它?

使用注解看起来像这样:

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

【讨论】:

【参考方案2】:

一种可能且最简单的解决方案是定义Validation Groups。例如,当您创建一个组时,您可以使用名为“create”或“groups”的验证组,而在创建用户时不指定组。那么验证器将不会应用于用户创建过程。

验证组可以在表单类中动态分配。您可以在documentation 中看到这方面的一个示例。

【讨论】:

所以你是说我应该将验证绑定到表单而不是绑定到实体?

以上是关于symfony2 对子实体的验证防止编辑父实体的主要内容,如果未能解决你的问题,请参考以下文章

Symfony2 使用父级中的一个字段和具有不同注释/映射的扩展实体

Symfony2 防止多个表单提交

Symfony2:如何获取标记有“编辑”ACL 权限的一种类型的所有实体?

Symfony2 实体用户提供者覆盖自定义身份验证提供者

显示父对象时对子对象求和 - 核心数据

跨相关实体的Symfony2验证