【问题标题】:Form post-processing in Symfony2Symfony2 中的表单后处理
【发布时间】:2014-10-11 15:38:39
【问题描述】:

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

该实体的一个字段是 ArrayCollection 类型。它实际上是与另一个类的对象的 OneToMany 关系。 所以,为了更清楚,写一点代码。

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 中的项目,而不仅仅是最后一个。

知道如何摆脱这种情况吗? 提前感谢您的帮助。

【问题讨论】:

  • 你看过表单事件吗? symfony.com/doc/current/cookbook/form/…
  • 是的,我看了收据。他们看起来很有希望,但我不知道这些如何映射到我的情况。欢迎提示!
  • 我不明白你的用例。也许其他人可以提供帮助。

标签: forms symfony doctrine-orm formbuilder


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    我按照 Cerad 和 tomazahlin 的建议提出了解决方案。

    我确信全世界每年至少有 2 人遇到我同样的问题,所以我会花一些时间来发布我的结果。
    欢迎指正、批评或加我,最后我是 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,
              )
          );
    \\ ...
    

    还有子表单。 由于我只需要显示今天的值,而不是全部,因此我还需要添加一个 FormEvent 子句来检查要插入的项目。

    namespace MyBundle\Form\Type;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    
    use Doctrine\ORM\EntityManager;
    
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\Form\FormEvents;
    
    class GoodsdataWithDateType extends AbstractType
    {
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            // Here I add the event listener:
            // Since I want only today's value to be displayed, I implement 
            // a check on this field of each element
    
            $builder->addEventListener(
              FormEvents::PRE_SET_DATA, function (FormEvent $event) {
                $goods = $event->getData();
                $form = $event->getForm();
    
                  $datetime1 = $goods->getInsertedAt();
                  $datetime2 = new \DateTime();
                  $datetime2->setTime(0, 0, 0);
    
                  if ($datetime1 > $datetime2)
                  {
                    $form->add('value', 'number', array(
                        'required' => false,
                        ));
            // I am setting this value with LifecycleCallbacks, and I do not 
            // want the user to change it, I am adding it commented just for
            // completeness
            //         $form->add('insertedAt', 'date', array(
            //             'widget' => 'single_text',
            //             'format' => 'yyyy,MM,dd',
            //         ));
                  }
            });
    
        }
    
        public function setDefaultOptions(OptionsResolverInterface $resolver)
        {
            $resolver->setDefaults(array(
                'data_class' => 'MyBundle\Entity\UserGoods',
            ));
        }
    
        public function getName()
        {
            return 'goodsdatawithdate';
        }
    
    }
    


    这可以正常工作,但在树枝文件中使用 {{ 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) }}
    


    到目前为止这很好,但我也想在我的收藏中加入新元素,特别是如果今天的商品还没有插入的话。
    我可以在我的 FormBuilder 中执行此操作,方法是在调用 $builder 之前在数组中手动​​添加一个新项目。

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

    但我还希望,如果用户删除了插入的值(即,在表单中不插入任何内容),则应删除关联的数组元素。
    允许用户删除元素有点棘手。我不能依赖“allow_delete”属性,因为只使用集合中的最后一个项目,提交表单时所有以前的项目都将被删除。
    我也不能依赖 LifecycleCallbacks,因为对关系所做的更改不会保留在数据库中。
    感谢开源,我找到了一个帖子 here 对我有帮助。 我需要的是一个关于 Doctrine Flush 操作的 EventListener。

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

    并在我的 config.yml 中注册为

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

    【讨论】:

      猜你喜欢
      • 2017-01-26
      • 1970-01-01
      • 2015-02-11
      • 2015-03-26
      • 2014-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-14
      相关资源
      最近更新 更多