Symfony2.7 字段验证两次,使用 validation-groups 和 UniqueEntity

Symfony2.7 field validates twice, using validation-groups and UniqueEntity

字段被验证两次,导致 2 次数据库调用和 2 次相同的错误消息。

我有 Subscriber class 主要用户、Registration class 和 RegistrationType,它按照教程 How to Implement a simple Registration Form 的建议嵌入了 SubscriberType。主用户名字段具有 UniqueEntity 约束,任何时候输入非唯一用户名时,"username already exists" 错误消息都会弹出两次,检查日志数据库调用以检查现有用户名被调用两次。我用谷歌搜索了这个,没有找到与我相同的案例,但发现了类似的案例。我不知道在这里做什么。

数据库是遗留的,所以我不能只切换到 FOSUserBundle,这也是我不使用 @ 注释而是使用 *.orm.ymlvalidation.yml

class RegistrationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        if(!$options['service']){
            $builder->add('user', new SubscriberType());
            $builder->add('Register', 'submit');    
        } else {
            $subscriber = new Subscriber();                
            $subscriber->setEmail($options['user_information']->getEmail());
            $subscriber->setFirstname($options['user_information']->getFirstname());
            $subscriber->setLastname($options['user_information']->getLastname());
            $subscriber->setUsername($options['user_information']->getEmail());

            switch($options['service']){
                case 'facebook':
                    $subscriber->setFbEmail($options['user_information']->getEmail());
                break;
            }

            $builder->add('user', new SubscriberType($subscriber), array('service' => $options['service'],
                'user_information' => $options['user_information'], 'data'=> $subscriber));
        }

        $builder->add(
            'terms',
            'checkbox',
            array('property_path' => 'termsAccepted')
        );

    }    

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'service' => false,
            'user_information' => false
            ,'validation_groups' => array('Default', 'registration')
        ));
    }

    public function getName()
    {
        return 'registration';
    }
}

自定义 'service' 选项是集成 HWIOAuthBundle,其他的都在那里。

class SubscriberType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if(!$options['service']){
            $builder->add('username', 'email');
            $builder->add('plainPassword', 'repeated', array(
               'first_name'  => 'password',
               'second_name' => 'confirm',
               'type'        => 'password',
            ));
        } else {


            $builder->add('email', 'text', array('read_only' => true, ));
            $builder->add('firstname', 'text');
            $builder->add('lastname', 'text');
            $builder->add('username', 'hidden');
            $builder->add('fbEmail', 'hidden');
            $builder->add('gplusEmail', 'hidden');
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Subscriber',
            'service' => false,
            'user_information' => false
            ,'validation_groups' => array('Default', 'registration')
        ));
    }

    /* DEPRECATED since 2.7
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Subscriber',
            'service' => false,
            'user_information' => false
        ));
    }
    */

    public function getName()
    {
        return 'subscriber';
    }
}

Registration.php:

<?php
// src/AppBundle/Form/Model/Registration

namespace AppBundle\Form\Model;

use Symfony\Component\Validator\Constraints as Assert;
use AppBundle\Entity\Subscriber;

class Registration
{
    /**
     * @Assert\Type(type="AppBundle\Entity\Subscriber")
     * @Assert\Valid()
     */
    protected $user;

    /**
     * @Assert\NotBlank()
     * @Assert\True()
     */
    protected $termsAccepted;

    public function setUser(Subscriber $user)
    {
        $this->user = $user;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function getTermsAccepted()
    {
        return $this->termsAccepted;
    }

    public function setTermsAccepted($termsAccepted)
    {
        $this->termsAccepted = (bool) $termsAccepted;
    }
}

Subscriber.php //实体:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
//use FOS\UserBundle\Entity\User as BaseUser;
/**
 * Subscriber
 */
class Subscriber implements UserInterface, \Serializable
{
    /**
     * @var integer
     */
    private $id;

    /**
     * @var string
     */
    private $username;

    /**
     * @var string
     */
    private $email;

    /**
     * @var string
     */
    private $gplusEmail;

    /**
     * @var string
     */
    private $fbEmail;

    /**
     * @var string
     */
    private $sha1Password;

    /**
     * @var string
     */
    private $salt;

    /**
     * @var float
     */
    private $unit;

    /**
     * @var string
     */
    private $validate;

    /**
     * @var string
     */
    private $rememberKey;

    /**
     * @var string
     */
    private $firstname = '';

    /**
     * @var string
     */
    private $lastname = '';

    /**
     * @var string
     */
    private $avatar = '';

    /**
     * @var string
     */
    private $countryId = '';

    /**
     * @var string
     */
    private $address;

    /**
     * @var string
     */
    private $phone;

    /**
     * @var string
     */
    private $gender = '';

    /**
     * @var \DateTime
     */
    private $birthdate;

    /**
     * @var \DateTime
     */
    private $lastAttemp;

    /**
     * @var integer
     */
    private $attempCount;

    /**
     * @var \DateTime
     */
    private $lastloginDate;

    /**
     * @var \DateTime
     */
    private $createdAt;

    /**
     * @var \DateTime
     */
    private $updatedAt;

    /**
     * @var \Doctrine\Common\Collections\Collection
     */
    private $groups;


    /**
     * @ORM\Column(type="string", length=255)
     */
    protected $plainPassword;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->groups = new \Doctrine\Common\Collections\ArrayCollection();
    }

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

    /**
     * Set username
     *
     * @param string $username
     * @return Subscriber
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

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

    /**
     * Set email
     *
     * @param string $email
     * @return Subscriber
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

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

    /**
     * Set gplusEmail
     *
     * @param string $gplusEmail
     * @return Subscriber
     */
    public function setGplusEmail($gplusEmail)
    {
        $this->gplusEmail = $gplusEmail;

        return $this;
    }

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

    /**
     * Set fbEmail
     *
     * @param string $fbEmail
     * @return Subscriber
     */
    public function setFbEmail($fbEmail)
    {
        $this->fbEmail = $fbEmail;

        return $this;
    }

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

    /**
     * Set sha1Password
     *
     * @param string $sha1Password
     * @return Subscriber
     */
    public function setSha1Password($sha1Password)
    {
        $this->sha1Password = $sha1Password;

        return $this;
    }

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

    /**
     * Set salt
     *
     * @param string $salt
     * @return Subscriber
     */
    public function setSalt($salt)
    {
        $this->salt = $salt;

        return $this;
    }

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

    /**
     * Set unit
     *
     * @param float $unit
     * @return Subscriber
     */
    public function setUnit($unit)
    {
        $this->unit = $unit;

        return $this;
    }

    /**
     * Get unit
     *
     * @return float 
     */
    public function getUnit()
    {
        return $this->unit;
    }

    /**
     * Set validate
     *
     * @param string $validate
     * @return Subscriber
     */
    public function setValidate($validate)
    {
        $this->validate = $validate;

        return $this;
    }

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

    /**
     * Set rememberKey
     *
     * @param string $rememberKey
     * @return Subscriber
     */
    public function setRememberKey($rememberKey)
    {
        $this->rememberKey = $rememberKey;

        return $this;
    }

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

    /**
     * Set firstname
     *
     * @param string $firstname
     * @return Subscriber
     */
    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;

        return $this;
    }

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

    /**
     * Set lastname
     *
     * @param string $lastname
     * @return Subscriber
     */
    public function setLastname($lastname)
    {
        $this->lastname = $lastname;

        return $this;
    }

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

    /**
     * Set avatar
     *
     * @param string $avatar
     * @return Subscriber
     */
    public function setAvatar($avatar)
    {
        $this->avatar = $avatar;

        return $this;
    }

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

    /**
     * Set countryId
     *
     * @param string $countryId
     * @return Subscriber
     */
    public function setCountryId($countryId)
    {
        $this->countryId = $countryId;

        return $this;
    }

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

    /**
     * Set address
     *
     * @param string $address
     * @return Subscriber
     */
    public function setAddress($address)
    {
        $this->address = $address;

        return $this;
    }

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

    /**
     * Set phone
     *
     * @param string $phone
     * @return Subscriber
     */
    public function setPhone($phone)
    {
        $this->phone = $phone;

        return $this;
    }

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

    /**
     * Set gender
     *
     * @param string $gender
     * @return Subscriber
     */
    public function setGender($gender)
    {
        $this->gender = $gender;

        return $this;
    }

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

    /**
     * Set birthdate
     *
     * @param \DateTime $birthdate
     * @return Subscriber
     */
    public function setBirthdate($birthdate)
    {
        $this->birthdate = $birthdate;

        return $this;
    }

    /**
     * Get birthdate
     *
     * @return \DateTime 
     */
    public function getBirthdate()
    {
        return $this->birthdate;
    }

    /**
     * Set lastAttemp
     *
     * @param \DateTime $lastAttemp
     * @return Subscriber
     */
    public function setLastAttemp($lastAttemp)
    {
        $this->lastAttemp = $lastAttemp;

        return $this;
    }

    /**
     * Get lastAttemp
     *
     * @return \DateTime 
     */
    public function getLastAttemp()
    {
        return $this->lastAttemp;
    }

    /**
     * Set attempCount
     *
     * @param integer $attempCount
     * @return Subscriber
     */
    public function setAttempCount($attempCount)
    {
        $this->attempCount = $attempCount;

        return $this;
    }

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

    /**
     * Set lastloginDate
     *
     * @param \DateTime $lastloginDate
     * @return Subscriber
     */
    public function setLastloginDate($lastloginDate)
    {
        $this->lastloginDate = $lastloginDate;

        return $this;
    }

    /**
     * Get lastloginDate
     *
     * @return \DateTime 
     */
    public function getLastloginDate()
    {
        return $this->lastloginDate;
    }

    /**
     * Set createdAt
     *
     * @param \DateTime $createdAt
     * @return Subscriber
     */
    public function setCreatedAt($createdAt)
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    /**
     * Get createdAt
     *
     * @return \DateTime 
     */
    public function getCreatedAt()
    {
        return $this->createdAt;
    }

    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     * @return Subscriber
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

    /**
     * Get updatedAt
     *
     * @return \DateTime 
     */
    public function getUpdatedAt()
    {
        return $this->updatedAt;
    }

    /**
     * Add groups
     *
     * @param \AppBundle\Entity\SubscriberGroup $groups
     * @return Subscriber
     */
    public function addGroup(\AppBundle\Entity\SubscriberGroup $groups)
    {
        $this->groups[] = $groups;

        return $this;
    }

    /**
     * Remove groups
     *
     * @param \AppBundle\Entity\SubscriberGroup $groups
     */
    public function removeGroup(\AppBundle\Entity\SubscriberGroup $groups)
    {
        $this->groups->removeElement($groups);
    }

    /**
     * Get groups
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getGroups()
    {
        return $this->groups;
    }



    /**
     * Get setPassword
     *
     * @param string 
     */
    public function setPassword($hashed_password)
    {
        return $this->setSha1Password($hashed_password);
    }

    /**
     * Get setPassword
     *
     * @return string 
     */
    public function getPassword()
    {
        return $this->sha1Password;
    }


    ///*
    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->sha1Password,
            $this->salt
        ));
    }

    ///*
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->sha1Password,
            $this->salt
        ) = unserialize($serialized);
    }
    //*/

    public function getRoles()
    {
        $roles = array();
        //var_dump($this->getGroups());
        foreach($this->getGroups() as $group){
            $roles[] = $group->getRole();
        }
        $roles[] = "ROLE_SUBSCRIBER";
        return array_unique($roles);
    }

    public function eraseCredentials()
    {
    }

    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function setPlainPassword($password)
    {
        $this->plainPassword = $password;
    }

}

和 Subscriber.orm.yml 如下:

AppBundle\Entity\Subscriber:
    type: entity
    table: w_subscriber
    repositoryClass: AppBundle\Entity\SubscriberRepository
    id:
        id:
            type: bigint
            nullable: false
            unsigned: false
            comment: ''
            id: true
            generator:
                strategy: IDENTITY
    fields:
        username:
            type: string
            nullable: false
            length: 50
            fixed: false
            comment: ''
        email:
            type: string
            nullable: false
            length: 150
            fixed: false
            comment: ''
        gplusEmail:
            type: string
            nullable: true
            length: 150
            fixed: false
            comment: ''
            column: gplus_email
        fbEmail:
            type: string
            nullable: true
            length: 150
            fixed: false
            comment: ''
            column: fb_email
        sha1Password:
            type: string
            nullable: true
            length: 40
            fixed: false
            comment: ''
            column: sha1_password
        salt:
            type: string
            nullable: true
            length: 32
            fixed: false
            comment: ''
        unit:
            type: float
            nullable: true
            precision: 18
            scale: 2
            comment: ''
            default: '0.00'
        validate:
            type: string
            nullable: true
            length: 10
            fixed: false
            comment: ''
        rememberKey:
            type: string
            nullable: true
            length: 50
            fixed: false
            comment: ''
            column: remember_key
        firstname:
            type: string
            nullable: false
            length: 200
            fixed: false
            comment: ''
        lastname:
            type: string
            nullable: true
            length: 200
            fixed: false
            comment: ''
        avatar:
            type: string
            nullable: false
            length: 200
            fixed: false
            comment: ''
        countryId:
            type: string
            nullable: false
            length: 5
            fixed: false
            comment: ''
            default: MN
            column: country_id
        address:
            type: text
            nullable: true
            length: null
            fixed: false
            comment: ''
        phone:
            type: string
            nullable: true
            length: 50
            fixed: false
            comment: ''
        gender:
            type: string
            nullable: false
            length: 1
            fixed: false
            comment: ''
            default: M
        birthdate:
            type: date
            nullable: true
            comment: ''
        lastAttemp:
            type: datetime
            nullable: true
            comment: ''
            column: last_attemp
        attempCount:
            type: bigint
            nullable: true
            unsigned: false
            comment: ''
            default: '0'
            column: attemp_count
        lastloginDate:
            type: datetime
            nullable: true
            comment: ''
            column: lastlogin_date
        createdAt:
            type: datetime
            nullable: false
            comment: ''
            column: created_at
            gedmo:
                timestampable:
                  on: create
        updatedAt:
            type: datetime
            nullable: false
            comment: ''
            column: updated_at
            gedmo:
                timestampable:
                    on: update
    lifecycleCallbacks: {  }
    manyToMany:
        groups:
          targetEntity: SubscriberGroup
          joinTable:
            name: w_subscriber_credential
            joinColumns:
              subscriber_id:
                referencedColumnName: id
            inverseJoinColumns:
              group_id:
                referencedColumnName: id

最后 validation.yml

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Subscriber:
    constraints:
      - \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
          fields: [ username ]
          groups: [ registration ]
          message: 'this email is already registered'
    properties:
        email:
            - Email: ~

控制器:

<?php
// src/AppBundle/Controller/SecurityController.php

namespace AppBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use AppBundle\Form\Type\RegistrationType;
use AppBundle\Form\Model\Registration;

class SecurityController extends Controller
{

    /**
     * @Route("/register", name="register")
     */
    public function registerAction()
    {
        $form = $this->createForm(new RegistrationType(), new Registration(), array(
            'action' => $this->generateUrl('register_create'),
        ));

        return $this->render(
            'security/register.html.twig',
            array('form' => $form->createView())
        );
    }

    /**
     * @Route("/register/create", name="register_create")
     */    
    public function createAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();

        $form = $this->createForm(new RegistrationType(), new Registration());

        $form->handleRequest($request);

        if ($form->isValid()) {
            $registration = $form->getData();
            $user = $registration->getUser();

            $user->setEmail($user->getUsername());
            $this->get('my.oauth_aware.user_provider.service')->setUserPassword($user, $user->getPlainPassword());

            $em->persist($registration->getUser());
            $em->flush();

            //return $this->redirectToRoute('homepage');
        }

        return $this->render(
            'security/register.html.twig',
            array('form' => $form->createView())
        );
    }

    /**
     * @Route("/login_check", name="login_check")
     */
    public function loginCheckAction()
    {
        // this controller will not be executed,
        // as the route is handled by the Security system
    }
}

注意用户名也是email字段,我没有post编辑模板,因为它也比较大,我相信它与验证无关,但如果有人想看的话我很乐意 post 它。

配置中还有 config.yml->framework->validation->enable_annotations->true

很抱歉浪费了大家的时间。错误出现在主 AppBundle 的声明中,或者实际上在 1 个捆绑文件层次结构中声明了 2 个不同的捆绑包。

为了覆盖 HWIOAuthBundle 的模板和控制器,我在 AppBundle 中声明了一个新的包,出于某种原因我认为需要一个新的声明。傻我。

所以 symfony 为每个单独的包声明加载了 validation.yml 两次。

通过将 getParent 函数移动到 AppBundle 声明中,并从 AppKernel 中删除第二个 Bundle class 声明及其初始化解决了这个问题。