在Symfony2中进行表单后处理
我是Symfony的新成员,并且我正在尝试创建一个绑定到实体用户的表单。在Symfony2中进行表单后处理
此实体的一个字段的类型是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,做事情非常简单,但我无法弄清楚如何自己做。 我只想要一个类型的字段,如果类型的对象与当前日期存在,修改它,否则添加一个新的对象到集合中。
这可以很容易地在控制器内完成,但我有很多这种形式的实例,这将使我的程序无法维护。
有没有办法在表单构建器中添加一些已提交数据的后处理? 我已经尝试使用DataTransformers,但这些不足以完成,因为它们至多会将一个数字转换为UserGoods对象,并且原始ArrayCollection不会保留(以及关于教义关联的情况如何?)。 此外,如果我将字段类型声明为数字类型集合,则全部为 ArrayCollection中的项目将在呈现表单时显示,而不仅仅是最后一个。
任何想法如何摆脱这个? 非常感谢您的帮助。
建议使用表单事件。在活动内部,您将检查包含提交日期的商品是否已经存在(从数据库中加载它们),并且您将使用发布数据修改它们。如果他们不存在,你会创造新的。您还可以在实体中使用另一种方法getLastItemsInCollection(),您可以在其中使用Criteria,只从数据库加载最后一个(推荐),或从原始ArrayCollection获取最后一个项目。如上所述,您可以使字段未映射,并在FormEvent中手动映射货物。我希望这有助于我希望自己正确理解。
我跟着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_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帮助我。 我需要的是一个EventListener对Doctrine Flush操作。
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 }
你看着形式的活动? http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html – Cerad 2014-10-11 16:03:43
是的,我看了收据。他们看起来很有希望,但我不知道这些如何映射到我的案例。欢迎提示! – Alberto 2014-10-11 16:10:15
我不明白你的用例。也许别人可以帮忙。 – Cerad 2014-10-11 17:40:48