Forms Symfony2中的表单后处理

Forms Symfony2中的表单后处理,forms,symfony,doctrine-orm,formbuilder,Forms,Symfony,Doctrine Orm,Formbuilder,我是Symfony的新手,我正在尝试创建一个绑定到实体用户的表单 此实体的一个字段的类型为ArrayCollection。它实际上是与另一个类的对象的单域关系。 所以,为了更清楚,我们需要一点代码 class User { \\... /** * @ORM\OneToMany(targetEntity="UserGoods", mappedBy="users") * @ORM\JoinColumn(name="goods", referencedColumnN

我是Symfony的新手,我正在尝试创建一个绑定到实体用户的表单

此实体的一个字段的类型为ArrayCollection。它实际上是与另一个类的对象的单域关系。 所以,为了更清楚,我们需要一点代码

class User 
{
    \\...

    /**
    * @ORM\OneToMany(targetEntity="UserGoods", mappedBy="users")
    * @ORM\JoinColumn(name="goods", referencedColumnName="id")
    */
    private $goods;


    public function __construct()
    {
        $this->goods = new ArrayCollection();
    }

    \\...

}
以及相关的类

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

    /**
    * @var \DateTime
    *
    * @ORM\Column(name="inserted_at", type="datetime")
    */
    private $insertedAt;

    /**
    * @var float
    *
    * @ORM\Column(name="value", type="float")
    */
    private $value;

    /**
    * @ORM\ManyToOne(targetEntity="User", inversedBy="goods")
    */
    protected $users;

}
现在,我想创建一个FormBuilder,它可以做一些非常简单的事情,但我自己却不知道如何去做。 我只需要一个number类型的字段,如果存在具有当前日期的Goods类型的对象,请修改它,否则将向集合添加一个新对象

这可以在控制器内轻松完成,但我有很多这种表单的实例,这将使我的程序无法维护

是否有办法在表单生成器中添加一些提交数据的后处理? 我已经尝试过使用DataTransformers,但这些还不够,因为它们最多只能将一个数字转换为一个UserGoods对象,而原始的ArrayCollection将不会被保留(关于条令关联呢?)。 此外,如果我将字段类型声明为数字类型的集合,则在呈现表单时将显示ArrayCollection中的所有项,而不仅仅是最后一项

你知道怎么摆脱这一切吗?
提前感谢您的帮助。

按照建议,使用表单事件。在事件中,您将检查具有提交日期的商品是否已经存在(从数据库加载),并使用post数据对其进行修改。如果它们不存在,你将创造新的。您还可以在实体中创建另一个方法getLastItemsInCollection(),在该方法中,您可以使用条件仅从数据库中加载最后一个(推荐),或从原始ArrayCollection中获取最后一个项。如上所述,您可以使字段未映射,并在FormEvent中手动映射商品。我希望这会有所帮助,我希望我理解正确。

我遵循了Cerad和tomazahlin的建议,并提出了解决方案

我确信每年全世界至少有两个人都会遇到我同样的问题,所以我会花一些时间公布我的结果。
请随意纠正、批评或添加我,最终我是Symfony的新手

首先,我最终是如何定义我的两个类的

class User 
{
    //...

    /**
    * @ORM\ManyToMany(targetEntity="UserGoods", inversedBy="users", cascade={"persist", "remove"})
    * @ORM\JoinColumn(name="goods", referencedColumnName="id")
    */
  // Should have been a OneToMany relationship, but Doctrine requires the
  //  owner side to be on the Many side, and I need it on the One side. 
  //  A ManyToMany relationship compensate this.
    private $goods;

    public function __construct()
    {
        $this->goods = new ArrayCollection();
    }

    //...

}
以及连接类

/**
 * @ORM\HasLifecycleCallbacks()
**/
class UserGoods
{
    /**
    * @var integer
    *
    * @ORM\Column(name="id", type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;

    /**
    * @var \DateTime
    *
    * @ORM\Column(name="inserted_at", type="datetime")
    */
    private $insertedAt;

    /**
    * @var float
    *
    * @ORM\Column(name="value", type="float", nullable=true) 
    */
    // I do not want this field to be null, but in this way when 
    // persisting I can look for null elements and remove them
    private $value;

   /**
    * @ORM\ManyToMany(targetEntity="User", inversedBy="goods")
    */
    protected $users;

    /**
    * @ORM\PrePersist()
    * @ORM\PreUpdate()
    */
    // This automatically sets InsertedAt value when inserting or 
    // updating an element.
    public function setInsertedAtValue()
    {
        $date = new \DateTime();
        $this->setInsertedAt( $date );

    }

}
正如我所说,我需要一个FormBuilder来处理我的数组集合。用于此目的的最佳表单类型是。。。集合类型。
这需要将子窗体定义为其类型

<?php

namespace MyBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

use MyBundle\Entity\UserGoods;

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

      $builder->add('goods', 'collection', array(
          'type' => new GoodsdataWithDateType(),
          'required' => false,
          )
      );
\\ ...

这可以很好地工作,但是当在细枝文件中使用类似{form(form)}的东西进行渲染时,显示非常糟糕。
为了使它更方便用户,我定制了表单的显示方式,以便删除一些垃圾并只包含必要的标签。
所以在我的小树枝上:

{{ form_start(form) }}

  {{ form_errors(form) }}
<div>
  {{ form_label(form.goods) }}
  {{ form_errors(form.goods) }} 
  <br>
  {% for field in form.goods %}
      {{ form_widget(field) }}
  {% endfor %}
</div>

{{ form_end(form) }}
并在我的config.yml中将其注册为

mybundle.emptyvalues_listener:
    class: MyBundle\EventListener\EmptyValueListener
    tags:
        - { name: doctrine.event_listener, event: onFlush }

你看过表单事件吗?是的,我看了收据。他们看起来很有希望,但我不知道这些与我的情况有什么关系。欢迎提示!我不理解你的用例。也许其他人可以帮忙。
class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {

      $thisuser = $builder->getData();

      // I added the following function inside the User class.
      // I use a for loop to scroll all the associated Goods to get the
      // latest one.
      $mygoods = $thisuser->getLatestGoods(); 

      if ( $mygoods && null !== $mygoods->getId() ) {

 //          The Array contains already some elements
          $datetime1 = $mygoods->getInsertedAt();
          $datetime2 = new \DateTime();
          $datetime2->setTime(0, 0, 0);

 //          Check when was the last one inserted
          if ($datetime1 < $datetime2)      // Nice way to compare dates
          {
 //          If it is older than today, add a new element to the array
              $newgoods = new UserGoods();
              $thisuser->addGoods($newgoods);
          }
      } else {

 //          The array is empty and I need to create the firs element
              $newgoods = new UserGoods();
              $thisuser->addGoods($newgoods);
      }

      $builder->add('goods', 'collection', array(
          'type' => new GoodsdataWithDateType(),
          'required' => false,
          'allow_add' => true,      // this enables the array to be
                                    // populated with new elements
          )
      );
namespace MyBundle\EventListener;

use Doctrine\ORM\Event\OnFlushEventArgs;
use MyBundle\Entity\UserGoods;

class EmptyValueListener
{
  public function onFlush(OnFlushEventArgs $args)
  {

    $em = $args->getEntityManager();
    $uow = $em->getUnitOfWork();

    $entities = array_merge(
        $uow->getScheduledEntityInsertions(),
        $uow->getScheduledEntityUpdates()
    );

    foreach ($entities as $entity) {
      if ($entity instanceof UserGoods) {
        if ($entity && null !== $entity )
        {
          if ( empty($entity->getValue()) ) 
          {
            $users = $entity->getUsers();
            foreach ($users as $curruser)
            {
              $curruser->removeGoods($entity);

              $em->remove($entity);
              $md = $em->getClassMetadata('MyBundle\Entity\UserGoods');
              $uow->computeChangeSet($md, $entity);

              $em->persist($curruser);
              $md = $em->getClassMetadata('MyBundle\Entity\User');
              $uow->computeChangeSet($md, $curruser);
            }
          }
        }
      }
    }
  }
}
mybundle.emptyvalues_listener:
    class: MyBundle\EventListener\EmptyValueListener
    tags:
        - { name: doctrine.event_listener, event: onFlush }