具有组合主键和事件setData的EntityType choice_value

时间:2017-09-14 17:10:22

标签: symfony symfony-forms symfony3.x

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

我订阅了formType PRE_SUBMIT事件,以便我可以解析该值并将其转换回实体实例,感谢Doctrine\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组件。
谢谢。

1 个答案:

答案 0 :(得分:0)

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

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

当我第一次面对这样的问题时,我并不清楚这对你有帮助。