簡體   English   中英

Symfony2 表單事件和模型轉換器

[英]Symfony2 form events and model transformers

我正在努力與 Symfony2 的表單構建器、事件和轉換器搏斗……希望這里有人更有經驗,可以提供幫助!

我有一個表單字段(選擇下拉列表),其中包含一些映射到實體的值(候選列表)。 這些選項之一是“其他”。 假設現在沒有 AJAX,當用戶提交表單時,我想檢測他們是否選擇了“其他”(或不在候選名單中的任何其他選項)。 如果他們選擇了這些選項之一,則應顯示完整的選項列表,否則只顯示候選列表。 應該很容易吧? ;)

所以,我有我的表格類型,它顯示基本的候選名單就好了。 代碼如下所示:

namespace Company\ProjectBundle\Form\Type;

use ...

class FancyFormType extends AbstractType {
    private $fooRepo;

    public function __construct(EntityManager $em, FooRepository $fooRepo)
    {
        $this->fooRepo = $fooRepo;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        /** @var Bar $bar */
        $bar = $builder->getData();
        $fooTransformer = new FooToStringTransformer($options['em']);

        $builder
            ->add($builder
                ->create('linkedFoo', 'choice', array(
                    'choices' => $this->fooRepo->getListAsArray(
                        $bar->getLinkedfoo()->getId()
                    ),
                ))
                ->addModelTransformer($fooTransformer)
            )
        ;

        // ...

    }

    // ...
}

現在,我想檢查提交的值,因此我使用了表單事件偵聽器,如下所示。

public function buildForm(FormBuilderInterface $builder, array $options) {
    // ... This code comes just after the snippet shown above

    $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
        /** @var EntityManager $em */
        $em = $event->getForm()->getConfig()->getOption('em');

        $data = $event->getData();
        if (empty($data['linkedFoo'])) return;
        $selectedFoo = $data['linkedfoo'];

        $event->getForm()->add('linkedFoo', 'choice', array(
            'choices' => $em
                ->getRepository('CompanyProjectBundle:FooShortlist')
                ->getListAsArray($selectedFoo)
            ,
        ));
        //@todo - needs transformer?
    });
}

但是,它失敗並顯示如下錯誤消息:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int in \path\to\project\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458 

我認為這個錯誤是因為當linkedFoo被覆蓋時它刪除了modelTransformer 我嘗試了在事件關閉時訪問構建器的各種方法,但這似乎不起作用(返回值出乎意料)。 除了$event->getForm()->add()之外,我還應該在事件中使用其他方法嗎? 或者我的方法有更根本的問題嗎?

基本上我不想弄亂linkedFoo字段的配置/轉換器/標簽,除了更改可用的選項......還有其他方法可以做到這一點嗎? 例如像$form->getField()->updateChoices()

在此先感謝您提供的任何幫助!

C

PS 有沒有比 Symfony 網站上更好的文檔或關於表單、事件等的討論? 例如,PRE_SET_DATA、PRE_SUBMIT、SUBMIT 等之間有什么區別? 他們什么時候被解雇? 它們應該用來做什么? 繼承如何與自定義表單字段一起使用? 什么是 Form 和 Builder,它們是如何交互的,你應該什么時候處理它們? 您應該如何、何時以及為何使用可以通過$form->getConfig()->getFormFactory()訪問的 FormFactory ? 等等..


編輯:為了回應弗洛里安的建議,這里有一些關於嘗試過但不起作用的事情的更多信息:

如果您嘗試像這樣在事件中獲取 FormBuilder:

/** @var FormBuilder $builder */
$builder = $event->getForm()->get('linkedFoo')->getConfig();

$event->getForm()->add($builder
    ->create('linkedFoo', 'choice', array(
        'choices' => $newChoices,
        'label'   =>'label',
    ))
    ->addModelTransformer(new FooToStringTransformer($em))
);

然后你得到錯誤:

FormBuilder methods cannot be accessed anymore once the builder is turned
into a FormConfigInterface instance.

那么你嘗試像弗洛里安建議的那樣,即

$event->getForm()->add('linkedFoo', 'choice', array(
    'choices' => $newChoices,
));
$event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooToStringTransformer($em));

...但您會收到此錯誤:

Notice: Object of class Proxies\__CG__\Company\ProjectBundle\Entity\Foo could not be converted to int 
in C:\path\to\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceList.php line 458

這似乎表明第二行(添加 ModelTransformer)從未被調用,因為->add()調用在您到達那里之前失敗了。

感謝 sstok(在 github 上)的想法,我想我現在已經可以使用了。 關鍵是創建一個自定義的 Form Type,然后使用它來添加 ModelTransformer。

創建自定義表單類型:

namespace Caponica\MagnetBundle\Form\Type;

use ...

class FooShortlistChoiceType extends AbstractType {
    protected $em;

    public function __construct(EntityManager $entityManager)
    {
        $this->em                   = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $fooTransformer = new FooToStringTransformer($this->em);

        $builder
            ->addModelTransformer($fooTransformer)
        ;
    }

    public function getParent() {
        return 'choice';
    }

    public function getName() {
        return 'fooShortlist';
    }
}

為新類型創建服務定義:

company_project.form.type.foo_shortlist:
    class: Company\ProjectBundle\Form\Type\FooShortlistChoiceType
    tags:
        - { name: form.type, alias: fooShortlist }
    arguments:
        - @doctrine.orm.entity_manager

主窗體的代碼現在看起來像這樣:

namespace Company\ProjectBundle\Form\Type;

use ...

class FancyFormType extends AbstractType {
    private $fooRepo;

    public function __construct(FooRepository $fooRepo)
    {
        $this->fooRepo = $fooRepo;
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        /** @var Bar $bar */
        $bar = $builder->getData();
        $fooTransformer = new FooToStringTransformer($options['em']);

        $builder
            ->add('linkedFoo', 'fooShortlist', array(
                'choices' => $this->fooRepo->getListAsArray(
                    $bar->getLinkedfoo()->getId()
                ),
            ))
        ;

        $builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
            /** @var EntityManager $em */
            $em = $event->getForm()->getConfig()->getOption('em');

            $data = $event->getData();
            if (empty($data['linkedFoo'])) return;
            $selectedFoo = $data['linkedFoo'];

            $event->getForm()->add('linkedFoo', 'fooShortlist', array(
                'choices'       => $em->getRepository('CaponicaMagnetBundle:FooShortlist')->getListAsArray($selectedFoo),
                'label'         => 'label'
            ));
        });

        // ...

    }

    // ...
}

關鍵是此方法允許您將 ModelTransformer 嵌入自定義字段類型中,這樣,每當您添加此類型的新實例時,它都會自動為您添加 ModelTransformer 並防止之前的“無法添加字段變壓器並且不能在沒有場的情況下添加變壓器”

你的聽眾看起來(幾乎 :) )沒問題。

只需使用 PRE_SUBMIT。 在這種情況下, $event->getData()將是發送的原始表單數據(數組)。 $selectedFoo將潛在地包含“其他”。

如果是這種情況,您將通過在偵聽器中使用 formFactory 將“短”“選擇”字段替換為完整字段。

$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
    $data = $event->getData();
    if (empty($data['linkedFoo']) || $data['linkedFoo'] !== 'other') {
        return;
    }

    // now we know user choose "other"
    // so we'll change the "linkedFoo" field with a "fulllist"


    $event->getForm()->add('linkedFoo', 'choice', array(
        'choices' => $fullList, // $em->getRepository('Foo')->getFullList() ?
    ));
    $event->getForm()->get('linkedFoo')->getConfig()->addModelTransformer(new FooTransformer);
});

你問了很多我不知道從哪里開始的問題。

關於 dataTransformers:除非您想將原始數據轉換為不同的表示形式(“2013-01-01” -> new DateTime(“2013-01-01”)),否則您不需要轉換器。

對於仍在尋找在表單事件中添加/重新添加模型轉換器的更好方法的任何人,我認為最好的解決方案是這篇文章中的所有學分都歸於@Toilal 以獲得這個出色的解決方案

因此,如果您實現 ModelTransformerExtension 並將其定義為服務,並更改一些代碼,例如,從

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            array($this, 'onPreSetData')
        );
    $builder->add(
                $builder
                    ->create('customer', TextType::class, [
                        'required' => false,
                        'attr' => array('class' => 'form-control selectize-customer'),
                    ])
                    ->addModelTransformer(new CustomerToId($this->customerRepo))
            )
            ;
}

類似於:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addEventListener(
                FormEvents::PRE_SET_DATA,
                array($this, 'onPreSetData')
            );
    $builder->add('customer', TextType::class, [
                'required' => false,
                'attr' => array('class' => 'form-control selectize-customer'),
                'model_transformer' => new CustomerToId($this->customerRepo),
            ]
        )
        ;
}

現在,如果我們在 eventlistener 函數中刪除並重新添加所需的字段,則該字段的模型轉換器不會丟失。

protected function onPreSetData(FormEvent $event)
{
    $form = $event->getForm();
    $formFields = $form->all();
    foreach ($formFields as $key=>$value){
        $config = $form->get($key)->getConfig();
        $type = get_class($config->getType()->getInnerType());
        $options = $config->getOptions();

        //you can make changes to options/type for every form field here if you want 

        if ($key == 'customer'){
            $form->remove($key);
            $form->add($key, $type, $options);
        }
    }
}

請注意,這是一個簡單的示例。 我已經使用此解決方案輕松處理表單以在不同位置具有多個字段狀態。

使用匿名函數作為事件處理程序從父作用域繼承$builder也可以。

$builder->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) use ($builder) {
    $data = $event->getData();

    if (empty($data['linkedFoo'])) {
       return;
    }
    $builder->add('linkedFoo', 'choice', [
        'choices' => $this->fooRepo->getListAsArray(
            $data->getLinkedfoo()->getId()
         ),
    ]);
    $builder->get('linkedFoo')
        ->addModelTransformer(
            new FooTransformer()
        );

});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM