如何动态添加选项到 Symfony 表单 EntityType 元素

问题描述 投票:0回答:2

我是一名 Symfony 新手,正在开发一个应用程序(使用 Doctrine)来管理心理学实践的日程安排和计费。

实体

Invoice
与实体
Session
具有一对多关系。所以我的表单类有一个
EntityType
元素,默认情况下,Symfony 会获取数据库中的 all 会话来填充多选元素。这是我不需要的大量数据,此外还有一个令人讨厌的 N+1 问题(我还没有弄清楚如何优化)。我想要做的是渲染这个EntityType元素并且(显然)不使用任何选项填充它。相反,用户从下拉列表中选择
Patient
实体,然后通过 xhr 调用,我们获取属于该患者且尚未与任何发票关联的会话(数据量要少得多)并进行渲染选项作为复选框。所以,在形式类中:

        public function buildForm(FormBuilderInterface $builder, array $options): void
        {
            $builder
                ->add('invoice_date', DateType::class, ['widget'=>'single_text',])
                ->add('payer',HiddenType::class)
                ->add('sessions',null, [
                    // this does not solve N+1
                    /*'query_builder'=>function(SessionRepository $repository)
                    {return $repository->createQueryBuilder('s')->leftJoin('s.invoice','invoice')
                        ->join('s.patients','patients')
                        ->leftJoin('patients.payer','payer')
                        ->andWhere('s.invoice is null')
                        ->addSelect('patients','payer');},
                    */
                    // ...but with this, it does not assign our input to the form
                    'choices'=>[],
                ])
                ->add('patient',EntityType::class,[
                    'placeholder'=>'',
                    'class'=>Patient::class,
                    'mapped'=>false,
                    'constraints' =>[
                        new Assert\NotBlank(['message'=>'patient is required']),
                    ],
                    'query_builder'=>$this->accountService->getActivePatientQueryBuilder(),
                    'choice_attr' => function($patient){
                        $payer = $patient->getPayer();
                        return $payer === null ? [] :[
                            'data-payer_id'=>$payer->getId(),
                           
                        ];
                    },
                ])
                // other elements etc omitted for brevity

            ;
        }
    }

当我让 Symfony 用选项填充 Sessions/EntityType 元素时,一切正常,但过多的查询问题仍然存在。当我按照我的方式做时,即将选择键定义为空数组并动态添加选择到表单中,看起来应该可以工作,但是 Symfony 抱怨验证错误“所选选择无效”并且不分配提交给元素的值。查看分析器中的异常消息,我们看到

选项“4714”、“4743”、“4772”、“4801”、“4830”、“4859”、“4888”、 选择列表中不存在“4917”、“4946”。

很公平。所以问题是:提交表单后如何将这些选择添加到我的 EntityType 元素中?

我已经在文档中阅读了有关 EventListeners 的内容,并且

FormEvents::PRE_SUBMIT
看起来是一个很好的挂钩事件。但经过一番努力后,没有任何效果,我在文档中找不到任何有关此内容的信息。

另一个可行的可能解决方案是分两步进行,首先他们选择患者,然后我动态呈现一个表单,其中候选会话被过滤到属于该患者且与发票无关的会话。但是我 我很固执,想让 Symfony 让这个元素接受我的数据。

建议?

php doctrine-orm symfony6
2个回答
0
投票

我不知道这是答案,但它是一个答案。我想有某种方法可以直接更改 EntityType 元素的“选择”选项,但如果有的话,我无法弄清楚。因此,我设置了一个事件侦听器,用一个新元素(同名)覆盖“sessions”EntityType 元素,该新元素的选项(选择)与用户提交的数据相同。这感觉有点像通过保持灯泡静止并旋转房子来更换灯泡。好消息是它可以工作——创建一个新的发票实体。更新是一个不同的故事,我还没有讲到。

class InvoiceFormType extends AbstractType { public function __construct(private AccountService $accountService) { // AccountService is a class that does some heavy lifting and it // has an EntityManager } public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('invoice_date', DateType::class, ['widget'=>'single_text',]) ->add('payer',HiddenType::class) ->add('sessions',null, [ 'label'=>'Sessions', 'choices'=>[], ] ) ->add('patient',EntityType::class,[ 'placeholder'=>'', 'class'=>Patient::class, 'mapped'=>false, 'constraints' =>[ new Assert\NotBlank(['message'=>'patient is required']), ], 'query_builder'=>$this->accountService->getActivePatientQueryBuilder(), 'choice_attr' => function($patient){ $payer = $patient->getPayer(); return $payer === null ? [] :[ 'data-payer_id'=>$payer->getId(), ]; }, ]) // more elements omitted for brevity, then... ->addEventListener(FormEvents::PRE_SUBMIT,[$this,'onPreSubmit']) ; $builder->get('payer')->addModelTransformer(new CallbackTransformer( // outgoing function($entity){ return $entity ? $entity->getId() : null; }, // incoming function($id){ return $id ? $this->accountService->getEntityManager()->find(Person::class,$id) : null; }, )); } // here's the interesting bit that handles our issue public function onPreSubmit(FormEvent $event) : void { $form_data = $event->getData(); $ids = $form_data['sessions']; $payer_id = $form_data['payer']; $sessions = $this->accountService->getEntityManager()->createQuery( "SELECT s FROM App\Entity\Session s -- might as well sanity-check the sessions while we're here LEFT JOIN s.invoice invoice JOIN s.payer payer WHERE s.id IN (:ids) AND s.invoice IS NULL AND payer.id = :payer_id" )->setParameters(['ids'=>$ids,'payer_id'=>$payer_id])->getResult(); $form = $event->getForm(); $form->add('sessions',EntityType::class,[ 'class'=>Session::class, 'multiple'=>true, 'choices' => $sessions, 'by_reference'=>false,// <-- you do NOT want to forget this ]); } public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'data_class' => Invoice::class, ]); } }
如果有人有更好的想法,请赐教。


0
投票
这是一个未解决的问题,请参阅此处的解决方法。

https://github.com/symfony/symfony/issues/42451#issuecomment-1049069268

有点难看,但是有用!

它曾经在现场使用 ->resetViewTransformers() 方法来工作:

->add('myField', ChoiceType::class,[ 'label' => 'My Field', 'required' => false, 'multiple' => true, 'expanded' => false, ]) ->get('myField')->resetViewTransformers()
    
© www.soinside.com 2019 - 2024. All rights reserved.