Symfony Forms CollectionType,按 ID 索引

Symfony Forms CollectionType, index by ID

我正在为 REST API 开发带有嵌入式类型的 Symfony 4 Form。形式:

class OffertaType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('pulsanti_social_attivi', CheckboxType::class, [ ])
            ->add('immagini', CollectionType::class, [
                'entry_type' => OffertaImmagineType::class,
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false,
                'prototype' => 'immagini'
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'data_class'         => Offerta::class,
                'allow_extra_fields' => true,
                'csrf_protection'    => false,
            ]
        );
    }
}

该集合包含 2 个对象

[
    {
        "id": 1,
        "url": "https://via.placeholder.com/150?text=1"
    },
    {
        "id": 2,
        "url": "https://via.placeholder.com/150?text=2"
    }
]

所有 "works",如果我 post 一些数据,则会创建一个新对象,如果我更新一个对象,则会更新数据库。奇怪的是行的 update/delete 似乎是基于对象在集合中的位置而不是它的 ID。如果我删除第一个对象,结果应该是

[
    {
        "id": 2,
        "url": "https://via.placeholder.com/150?text=2"
    }
]

但是它

[
    {
        "id": 1,
        "url": "https://via.placeholder.com/150?text=2"
    }
]

Symfony 只看到 1 个对象,因此删除第二个并更新第一个 "url" 字段。

如何 "index" 通过对象的 ID 而不是它们的位置来收集?

很遗憾,这实际上是不可能的。只要接受 Symfony Collections 是 index-based 并处理它。您可以采用其中一种可能的解决方案。

例如,您可以为 collection 项创建单独的删除按钮,然后设置

'delete_empty' => false,
'allow_delete' => false,

仅将您的表单用于添加新条目。

或者您可以将 delete_emptyallow_delete 设置为 true,但在这种情况下,您应该为 collection 位置设置空元素。

另一种选择是根据您的具体要求为您的表单创建一个唯一的处理程序,但我不能给您一个具体的例子,因为我现在不知道您需要什么。

Symfony 的存储库中有一个打开的票证,用于向 CollectionType 添加一个功能来索引自定义字段上的项目:https://github.com/symfony/symfony/issues/7828 这将解决您遇到的问题,但该票证仍然存在开着没往前走。

然而,在线程的更深处,人们通过手动更改 ArrayCollection 实体以使用 id 对其进行索引来找到解决方法,如下所示:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $entity = $builder->getData();

    // Reindex collection using id
    $indexedCollection = new ArrayCollection();
    foreach ($entity->getCollectionField() as $collectionItem) {
        $indexedCollection->set($collectionItem->getId(), $collectionItem);
    }

    $builder
        ->add('collectionField', CollectionType::class, [
            'data' => $indexedCollection,
        ])
    ;
}

(从 Seb33300 的回答 https://github.com/symfony/symfony/issues/7828#issuecomment-579608260 复制)。

然后你可以发回这个JSON:

{
    "immagini": {
        "2": {"url": "http://new-url.com"}
    }
}

并且它将映射到集合中 ID 为 2 的实体。