Symfony2 注册表 - 带有 jQuery 的额外创建字段不会保留在数据库中,仅来自上次输入的值

Symfony2 registration form - extra created fields with jQuery are not persisted in DB only value from last input

我想在提交表单后将所有动态创建的电子邮件输入值保存到数据库电子邮件字段中。现在的问题是 - 只保存电子邮件字段中的最后一个值。我不知道如何解决它。

注册控制器:

class RegistrationFormType extends BaseType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder->add('first_name')
      ->add('last_name')
      ->add('is_company', CheckboxType::class, array(
        'label'    => 'Is company?',
        'required' => true,
      ))
      ->add('emails', TextType::class)
      ->add('add' , ButtonType::class, array('attr'=>array('class'=>'add-email')))
      ->add('department', EntityType::class, array(
        'class' => 'UserBundle:Department',
        'choice_label' => 'name',
      ));
  }
  public function getParent()
  {
    return 'FOS\UserBundle\Form\Type\RegistrationFormType';
  }

  public function getBlockPrefix()
  {
    return 'custom_user_registration';
  }

  public function getName()
  {
    return $this->getBlockPrefix();
  }
}

jQuery 添加/删除表单域的函数:

$(document).ready(function() {
 var max_fields      = 10; //maximum input boxes allowed
 var wrapper         = $("div"); //Fields wrapper
 var add_button      = $(".add-email"); //Add button ID

 var x = 1; //initlal text box count
 $(add_button).click(function(e){ //on add input button click
  e.preventDefault();

  console.log('Clicking Add Button');
  if(x < max_fields){ //max input box allowed
   x++; //text box increment
   $('.add-email').parent("div").prev().append('<div><input type="text" id="fos_user_registration_form_emails" name="fos_user_registration_form[emails]" required="required"><a href="#" class="remove_field">Remove</a></div>');
  }
 });

 $(wrapper).on("click",".remove_field", function(e){ //user click on remove text
  e.preventDefault(); $(this).parent('div').remove(); x--;
 })
});


User Entity:
/**
 * @ORM\Column(type="text",nullable=true)
 */
  protected $emails;

 /**
   * @return mixed
   */
  public function getEmails()
  {
    return $this->emails;
  }

  /**
   * @param mixed $emails
   */
  public function setEmails($emails)
  {
    $this->emails = $emails;
  }

注册控制器:

class RegistrationController extends BaseController
{
//  public function registerAction(Request $request)
//  {
//
//    $response = parent::registerAction( $request );
//
//    return $response;
//  }

  public function registerAction(Request $request)
  {
    $form = $this->container->get('fos_user.registration.form');
    $formHandler = $this->container->get('fos_user.registration.form.handler');
    $confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
    $process = $formHandler->process($confirmationEnabled);
    if ($process) {
      $user = $form->getData();

      $this->container->get('logger')->info(
        sprintf('New user registration: %s', $user)
      );

      if ($confirmationEnabled) {
        $this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
        $route = 'fos_user_registration_check_email';
      } else {
        $this->authenticateUser($user);
        $route = 'fos_user_registration_confirmed';
      }

      $this->setFlash('fos_user_success', 'registration.flash.user_created');
      $url = $this->container->get('router')->generate($route);


      return new RedirectResponse($url);
    }
    return $this->container->get('templating')->renderResponse('FOSUserBundle:Registration:register.html.'.$this->getEngine(), array(
      'form' => $form->createView(),
    ));
  }
}

register.html.twig(从 FOS 用户包覆盖):

{% extends "FOSUserBundle::layout.html.twig" %}

{% block fos_user_content %}
{% include "FOSUserBundle:Registration:register_content.html.twig" %}
{% endblock fos_user_content %}

如果您有 10 个名称为 fos_user_registration_form[emails] 的输入,最后一个将覆盖前 9 个。但是,如果您更改电子邮件字段的名称,Symfony 将无法识别该字段并发送错误。

适合您的解决方案:CollectionType Field。它允许您为一个属性创建多个字段或删除现有的字段。同样默认情况下,它会生成 prototype - 一个 HTML 模板,用于将新输入插入 DOM.

听起来像 js rendered/cloned 的字段没有更新它们的名称,后来出现的字段覆盖了具有相同名称的早期字段。

例如,如果我有一个呈现如下的表单:

<!-- pseudo markup; yours will likely differ -->
<div class="form_row">
    <input type="email" name="fos_user[email]" />
</div>
<button class="add_email" onclick="javascript:[...];">Add email</button>

然后我单击 .add-email 按钮,得到:

<div class="form_row">
    <input type="email" name="fos_user[email]" />
</div>
<div class="form_row">
    <input type="email" name="fos_user[email]" />
    <button onclick="javascript:[...];">Remove</button>
</div>
<button class="add_email" onclick="javascript:[...];">Add email</button>

如果我要填写两个 <input> 字段并提交表单,我基本上会为 fos_user[email] 输入字段设置两个值。浏览器将最后一个作为规范值,忽略所有其他具有相同名称的值。请求出去时,只发送一个值字段名。

要验证这个假设,您应该在浏览器的开发人员工具中检查来自 "network" 面板的请求数据。 Google Chrome 中的操作方法如下:link.

Pic of google chrome dev tools examining an HTTP request. Where the user has selected remotedebugging.png, you might look for the main POST request to /register, or whatever your FOSUserBundle's register action is.

如果事实证明是这种情况 - 那么错误在于您的 javascript 克隆字段的方式。您需要确定这些字段的每个 name 属性都有尾部 [n]。 (其中 n 是一个数字。)最好的方法是使用集合。

但是坚持... 合集!?。看起来您还没有准备好您的用户模型来保存电子邮件集合。

<?php

namespace AppBundle\Entity\User;

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


class User extends BaseUser
{
    ...

    /**
     * @ORM\Column(type="text",nullable=true)
     */
    protected $emails;

你用复数命名它,但就 Doctrine 或你的 ORM 而言,它看起来就像一个普通字符串。

修复

  1. User$emails 字段更改为 simple_array 类型,该类型在 db 列中存储以逗号分隔的值。下面的示例使用一个原则注释来执行此操作。 (Here's the doctrine reference for simple_array.) Other array-like options: "array" (stored w/ php serialization) "json"(存储为 json 对象)

    不要丢弃 $email 字段(注意单数名称),因为我相信 FOSUserBundle 在扩展他们的 User 模型 class 时需要它。请务必更新您的 getter 和 setter(如下例所示),并确保您的用户的 $emails 属性 在实例化时是一个空的 ArrayCollection 对象。 运行 doctrine:schema:update 执行此操作后来自 symfony 控制台。

    <?php
    // src/AppBundle/Entities/User.php
    
    namespace AppBundle\Entity\User;
    
    use FOS\UserBundle\Entity\User as BaseUser;
    use Doctrine\ORM\Mapping as ORM;
    
    class User extends BaseUser
    {
    
        ...
    
        /**
         * @ORM\Column(type="text", nullable=true)
         */
        protected $email;
    
        /**
         * @ORM\Column(type="simple_array")
         */
        protected $emails;
    
        ...
    
        /**
         * Instantiate the User 
         */
        public function __construct()
        {
            parent::__construct();
            $this->emails = new \Doctrine\Common\Collections\ArrayCollection();
            ...
        }
    
        /**
         * Add an email to the collection of emails
         *
         * @param string $email The email to add.
         *
         * @return User
         */
        public function addEmail($email)
        {
            $this->emails[] = $email;
    
            return $this;
        }
    
        /**
         * Remove an email from the collection of emails
         *
         * @param string $email The email to disassociate from this user.
         *
         * @return User
         */
        public function removeEmail($email)
        {
            $this->email->removeElement($email);
    
            return $this;
        }
    
        /**
         * Get all emails in colletion
         *
         * @return \Doctrine\Common\Collections\Collection
         */
        public function getEmails()
        {
            return $this->emails;
        }
    
  2. 进一步扩展您的注册表,使其包括收集字段。

    <?php
    // src/AppBundle/Form/UserRegistrationType.php
    
    namespace AppBundle\Form;
    
    use Symfony\Component\Form\Extension\Core\Type\CollectionType;
    use Symfony\Component\Form\Extension\Core\Type\EmailType;
    
    class RegistrationFormType extends BaseType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        $builder->add(
    
            ...
    
            'emails', 
            CollectionType::class,
            [
                'entry_type' => EmailType::class,
                'allow_add'  => true,
                'allow_delete'  => true
            ],
    
            ...
    
        )
    
        ...
    
    }
    

    在我的示例中,我将表单扩展到控制器之外。当我扩展 reg.在 FOSUserBundle 中形成,我在 app/config.yml

    中告知捆绑包
    // app/config.yml
    ...
    fos_user:
        registration:
            form:
                type: AppBundle\Form\RegistrationFormType
    
    ...
    

    选项数组上的 allow_add 键告诉 twig 使用 data-prototype 属性呈现 emails 字段的容器,其中将包含 __name__ 占位符字符串。该占位符将帮助您更准确地为表单字段编号,并避免我们在上面陷入的重名陷阱。

  3. 以不同方式呈现表单。从 Symfony 推荐的 javascript 中提取 CollectionType。来自 http://symfony.com/doc/current/reference/forms/types/collection.html:

    {# '/project/base/app/Resources/FOSUserBundle/views/Registration/register_content.html.twig' #}
    
    {{ form_start(form) }}
        {# ... #}
    
        {# store the prototype on the data-prototype attribute #}
        <ul id="email-fields-list"
            data-prototype="{{ form_widget(form.emails.vars.prototype)|e }}">
        {% for emailField in form.emails %}
            <li>
                {{ form_errors(emailField) }}
                {{ form_widget(emailField) }}
            </li>
        {% endfor %}
        </ul>
    
        <a href="#" id="add-another-email">Add another email</a>
    
        {# ... #}
    {{ form_end(form) }}
    
    <script type="text/javascript">
        // keep track of how many email fields have been rendered
        var emailCount = '{{ form.emails|length }}';
    
        jQuery(document).ready(function() {
            jQuery('#add-another-email').click(function(e) {
                e.preventDefault();
    
                var emailList = jQuery('#email-fields-list');
    
                // grab the prototype template
                var newWidget = emailList.attr('data-prototype');
                // replace the "__name__" used in the id and name of the prototype
                // with a number that's unique to your emails
                // end name attribute looks like name="contact[emails][2]"
                newWidget = newWidget.replace(/__name__/g, emailCount);
                emailCount++;
    
                // create a new list element and add it to the list
                var newLi = jQuery('<li></li>').html(newWidget);
                newLi.appendTo(emailList);
            });
        })
    </script>
    

    注意到脚本如何用电子邮件在表单中的位置替换原型的 __name__ 占位符了吗?这将呈现为:

    <div class="form_row">
        <input type="email" name="fos_user[emails][0]" />
    </div>
    <div class="form_row">
        <input type="email" name="fos_user[emails][1]" />
        <button onclick="javascript:[...];">Remove</button>
    </div>
    <button class="add_email" onclick="javascript:[...];">Add email</button>
    

    ...这将强制浏览器单独处理每个字段。