2014-10-11 27 views
0

我是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中的項目將在呈現表單時顯示,而不僅僅是最後一個。

任何想法如何擺脫這個? 非常感謝您的幫助。

+0

你看着形式的活動? http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html – Cerad 2014-10-11 16:03:43

+0

是的,我看了收據。他們看起來很有希望,但我不知道這些如何映射到我的案例。歡迎提示! – Alberto 2014-10-11 16:10:15

+0

我不明白你的用例。也許別人可以幫忙。 – Cerad 2014-10-11 17:40:48

回答

0

建議使用表單事件。在活動內部,您將檢查包含提交日期的商品是否已經存在(從數據庫中加載它們),並且您將使用發佈數據修改它們。如果他們不存在,你會創造新的。您還可以在實體中使用另一種方法getLastItemsInCollection(),您可以在其中使用Criteria,只從數據庫加載最後一個(推薦),或從原始ArrayCollection獲取最後一個項目。如上所述,您可以使字段未映射,並在FormEvent中手動映射貨物。我希望這有助於我希望自己正確理解。

0

我跟着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 }