Symfony5 handleRequest 更新原始 collectionType 对象

Posted

技术标签:

【中文标题】Symfony5 handleRequest 更新原始 collectionType 对象【英文标题】:Symfony5 handleRequest updating original collectionType objects 【发布时间】:2021-12-17 07:10:03 【问题描述】:

在此处遵循官方 Symfony 文档时,我无法使我的逻辑工作:https://symfony.com/doc/current/form/form_collections.html#allowing-tags-to-be-removed

基于示例,我需要获取 originalTags,然后在处理完表单后将它们与新标签进行比较。

在我的例子中,我有一个 Purchase 实体,它可以有一个 PurchaseProducts(ManyToMany) 的集合。就我而言,当我更改 PurchaseProduct 时,我需要更新已删除的购买库存。但是,无论我如何获得原始 PurchaseProducts,在 $form->handleRequest() 之后,它们都会使用新值进行更新,并且我会丢失有关原始值的任何信息。

片段构成我的控制器的逻辑:

 /** @var Purchase $purchase */
    $purchase = $this->getDoctrine()
        ->getRepository(Purchase::class)
        ->find($id);

    if (!$purchase) 
        $this->addFlash('error', 'Purchase not found');
        return $this->redirect($this->generateUrl('purchase_list'));
    

    $originalProducts = new ArrayCollection();
    foreach ($purchase->getPurchaseProducts() as $purchaseProduct) 
        $originalProducts->add($purchaseProduct);
    

    $form = $this->createForm(PurchaseType::class, $purchase);

    if ($request->isMethod('POST')) 

        dump($originalProducts); // Original entities are here

        $form->handleRequest($request);

        dump($originalProducts);die; // Original entities are updated with the new ones

        ...

        // This will not work since originalProducts already has the new entities
        foreach ($originalProducts as $purchaseProduct) 
            if (false === $purchase->getPurchaseProducts()->contains($purchaseProduct)) 
                // update stock here
            
        

我尝试了很多选项,比如克隆、查询数据库等等,但是在 handleRequest 之后我总是得到相同的更新实体。为什么?

【问题讨论】:

我认为您应该在将 purchaseProduct 添加到您的 ArrayCollection() 之前尝试序列化它 【参考方案1】:

行为说明

正如您所指的表单处理器的"allow_delete" => true"allow_add" => true 概念。提交表单时,出现在副本中的集合内的实体属性更改是预期的行为。但是,$originalProducts 集合不会包含任何新实体 (new PurchaseProduct())。

发生这种情况是因为 php 通过引用传递对象,即 PurchaseProduct 对象到 ArrayCollection。这意味着对嵌入表单对象所做的任何更改都将应用于Purchase:::$purchaseProducts$originalProducts 集合,因为它们是相同的PurchaseProduct 对象(通过引用)。

但是,当它们在$form->handleRequest($request) 之后从Purchase:::$purchaseProducts 集合中删除时,这些对象仍将存在于$originalProducts 集合中。如果您的实体不包含必要的逻辑,这允许您比较两个集合以将它们从实体管理器或您的实体集合中删除。

示例使用 ArrayObject 和 Foo 对象:https://3v4l.org/oLZqO#v7.4.25

Class Foo

    private $id;
    
    public function __construct()
    
        $this->id = uniqid('', false);
    
    
    public function setId($id)
    
        $this->id = $id;
    


//initial state of Purchase::$purchaseProducts
$foo1 = new Foo();
$foo2 = new Foo();
$a = new ArrayObject([$foo1, $foo2]); 

//Create Copy as $originalProducts
$b = new ArrayObject();
foreach ($a as $i => $foo) 
    $b->offsetSet($i, $foo);


//form submission
$foo1->setId('Hello World');
$a->offsetUnset(1);

结果

Initial state of Copy:
ArrayObject::__set_state(array(
   0 => 
  Foo::__set_state(array(
     'id' => '6182c24b00a21',
  )),
   1 => 
  Foo::__set_state(array(
     'id' => '6182c24b00a28',
  )),
))
-----------------------
Form Submitted ID Changed in Copy:
ArrayObject::__set_state(array(
   0 => 
  Foo::__set_state(array(
     'id' => 'Hello World',
  )),
   1 => 
  Foo::__set_state(array(
     'id' => '6182c24b00a28',
  )),
))
-----------------------
Source has foo2 entity removed:
ArrayObject::__set_state(array(
   0 => 
  Foo::__set_state(array(
     'id' => 'Hello World',
  )),
))
-----------------------
Copy still contains both entities:
ArrayObject::__set_state(array(
   0 => 
  Foo::__set_state(array(
     'id' => 'Hello World',
  )),
   1 => 
  Foo::__set_state(array(
     'id' => '6182c24b00a28',
  )),
))

决议

根据您想要的结果,有几种方法可用于检测和处理更改,例如Change Tracking Policies

确定实体发生了哪些变化的另一种方法是使用$em->getUnitOfWork()->getEntityChangeSet($entity) 来检索向教义提议的实体的更改。

代码建议

为确保请求不会被误解,您应始终处理表单请求(无论请求方法如何)并验证表单是否已提交/有效。这是因为handleRequest方法会根据请求方法触发多个事件。

/** @var Purchase $purchase */
    $purchase = $this->getDoctrine()
        ->getRepository(Purchase::class)
        ->find($id);

    if (!$purchase) 
        $this->addFlash('error', 'Purchase not found');
        return $this->redirect($this->generateUrl('purchase_list'));
    

    $originalProducts = new ArrayCollection();
    foreach ($purchase->getPurchaseProducts() as $purchaseProduct) 
        $originalProducts->add($purchaseProduct);
    

    $form = $this->createForm(PurchaseType::class, $purchase);
    dump($originalProducts); // Original entities are here
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) 
        dd($originalProducts); // dump+die as dd() - Original entities are updated with the new ones

        //...

        // This will not work since originalProducts already has the new entities
        foreach ($originalProducts as $purchaseProduct) 
            if (false === $purchase->getPurchaseProducts()->contains($purchaseProduct)) 
               //remove original from the Purchase entity
            
        

【讨论】:

以上是关于Symfony5 handleRequest 更新原始 collectionType 对象的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 5 更新后 Laravel 7 电子邮件异常中断

Symfony5 系列教程1-安装并认识symfony

Symfony2 - 如何阻止 Form->handleRequest 清空帖子数据中不存在的字段

Symfony 5 作曲家更新失败并带有安全检查器

四则运算网页版

升级到 Symfony 5.3 并更新 flex 配方后出错 (symfony:recipes:install --force)