Symfony2 中的表单后处理

问题描述 投票:0回答:2

我是 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 中的所有项目,而不仅仅是最后一个。 你知道如何摆脱这个困境吗? 预先感谢您的帮助。

php forms symfony doctrine-orm formbuilder
2个回答
0
投票


0
投票

我确信每年世界上至少有 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'; } }


这工作正常,但是当在 twig 文件中使用类似 {{ 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 }

	
© www.soinside.com 2019 - 2024. All rights reserved.