1

我的自定义表单类型基于EntityType.
我的实体有一个复合主键,因此我将其设置choice_value为反映 options"value"属性中的两个键字段值,这可以按预期工作。

我订阅了 formTypePRE_SUBMIT事件,以便我可以解析该值并将其转换回实体实例,感谢 toDoctrine\ORM\EntityManager::getReference方法。

我的问题在提交时出现。我希望事件setData($my_retrieved_entity)方法(在事件侦听器内部)成功地将表单提交的值替换为相应的实体(满足表单验证管道),但我得到的似乎是默认的 symfony 错误消息:

«此值无效。»

    // …
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setRequired(['entityManager','country']);
        $resolver->setAllowedTypes('country', Country::class);
        $resolver->setAllowedTypes('entityManager', EntityManager::class);

        $resolver->setDefaults([
            'class' => NetworkTypeModel::class,
            'placeholder' => 'Choose a network type',
            'choice_value' => function ($networkType) {
                // Set specific format for for the value attribute
                // so that it reflects both primary key fiels values
                return $networkType
                    ? "{$networkType->getId()}¤{$networkType->getCountry()->getId()}"
                    : ''
                ;
            }
            ,'query_builder' => function (Options $options) {
                return function (EntityRepository $er) use ($options) {
                    $qb = $er->createQueryBuilder('nt')
                        ->leftJoin('nt.country', 'c')
                        ->addSelect('c');
                    return $qb->where($qb->expr()->eq('nt.country', ':country'))
                        ->setParameter('country', $options['country']->getId())
                        ->orderBy('nt.label', 'ASC');
                };
            },
        ]);
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $entityManager = $options['entityManager'];

        // Listen to post DATA in order to transform Option's value 
        // back to a networkType instance
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($entityManager) {
                if ($data = $event->getData()) {
                    // Special value has been sent through POST field
                    // It needs to be parsed and transformed back to a networkType entity
                    // According to the format set from inside choice_value options.
                    $ids = explode('¤', $data);
                    $networkType = $entityManager->getReference(
                        NetworkTypeModel::class,
                        ['id' => $ids[0], 'country' => $ids[1]]
                    );
                    // I expect $event->setData to populate the form submitted value
                    // to be the selected value
                    $event->setData($networkType);
                    // But I get the following common error message on that field
                    // «This value is not valid.»
                }
            }
        );
    }

    public function getParent()
    {
        return EntityType::class;
    }

然而,在转储一个完全有效的实体实例dump($networkType)之前执行$event->setData($networkType);,实际上是正确的,与提交的项目相关联。

感觉就像我几乎把事情做好了,但我不知道这里出了什么问题。关于如何正确地将提交的数据转换为实体,将其注入表单并满足验证器链的任何想法?

编辑

我完成了工作,但可能不是真正的 symfony 方式。它可能会帮助您了解我的需求。

我使用了一个共享变量:

  • 从内部事件侦听器填充PRE_SUBMIT,具有预期的 networkType 实例,使用$event->getData()解析的字符串重建。
  • POST_SUBMIT然后在事件中使用这个实例引用作为FormEvent::setData参数。

更改/添加的代码:

    $entityManager = $options['entityManager'];
    $selectedNetworkType = null;

    // Transform Option's value back to a networkType instance
    $builder->addEventListener(
        FormEvents::PRE_SUBMIT,
        function (FormEvent $event) use ($entityManager, &$selectedNetworkType) {
            // Transform the custom dropdown "value" attribute coming from POST
            // into a my networkType model instance
            if ($data = $event->getData()) {
                // PRE_SUBMIT event data holds the view data as a string
                // which needs to be parsed accordingly to what have been
                // done when encoding the entity's ids in choice_value callable
                $ids = explode('¤', $data);
                // We don't need to retrieve the entire record from DB anyway
                // so we use getReference
                $networkType = $entityManager->getReference(
                    NetworkTypeModel::class,
                    ['id' => $ids[0], 'country' => $ids[1]]
                );
                // Now store the newly created networkType instance
                // for later
                $selectedNetworkType = $networkType;
            }
        }
    )->addEventListener(
        FormEvents::POST_SUBMIT,
        function (FormEvent $event) use (&$selectedNetworkType) {
            // Use the fresh stored instance to feed the model data
            $selectedNetworkType && $event->setData($selectedNetworkType);
        }
    );

我希望有人能提出正确的方向来解决这个用例,以便更好地理解 Symfony Form 组件。
谢谢你。

4

1 回答 1

0

据我了解您的任务是修改您的表单从表单数据重新组装的实体,然后再将其发送回客户端。您实际上需要使用FormEvents::SUBMIT要做到这一点(这里的 $event->setData($data) 和 $event->getData() 用于管理这里的重组实体),你正在做的是改变原始表单数据数组(你将通过 $event- >getData() 在 FormEvents::PRE_SUBMIT 中调用,您可以通过在代理实体上调用 var_dump() 来检查这一点,在提交表单上尝试从代理重新创建您的实体(而提交的有效数据必须以以下形式提供阵列)。另请注意,您使用实体管理器的其他实例而不是使用表单本身,因此您将从表单获得的实体将不会被管理。因此,更好的方法是在提交事件中通过 $event->getData() 修改实体返回,但我不认为您会因为复合键而得到一个。另请注意,您可以通过以这种方式调用提交事件中的表单数据

$event->getForm()->get('id')->getData();

当我第一次遇到此类问题时,这对我来说并不清楚,因此可能对您有所帮助。

于 2017-09-15T05:10:35.507 回答