23

Symfony2 のフォーム ビルダー、イベント、およびトランスフォーマーと格闘しようとして、結び目で縛られています...うまくいけば、ここの誰かがより経験豊富で、助けてくれます!

エンティティにマップするいくつかの値 (ショートリスト) を含むフォーム フィールド (選択ドロップダウン) があります。これらのオプションの 1 つが「その他」です。今のところ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 の Web サイトよりも、フォーム、イベントなどのドキュメントやディスカッションが優れているものはありますか? たとえば、PRE_SET_DATA、PRE_SUBMIT、SUBMIT などの違いは何ですか? 彼らはいつ解雇されますか?それらは何に使用する必要がありますか?継承はカスタム フォーム フィールドでどのように機能しますか? Form と Builder とは何ですか?それらはどのように相互作用し、いつそれぞれを処理する必要がありますか? 経由でアクセスできる FormFactory をいつ、どのように使用する必要があります$form->getConfig()->getFormFactory()か? 等..


編集:Florianの提案に応えて、試したものの機能しないことに関する詳細情報を次に示します。

次のようにイベント内で 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

->add()これは、そこに到達する前に呼び出しが失敗しているため、2 行目 (ModelTransformer を追加する) が呼び出されないことを示唆しているようです。

4

4 に答える 4

30

(github の) sstok からのアイデアのおかげで、今はうまくいっていると思います。重要なのは、カスタマイズされたフォーム タイプを作成し、それを使用して 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 が追加され、「これなしではフィールドを追加できない」という以前のループを防ぐことができます。トランスフォーマー AND フィールドなしでトランスフォーマーを追加することはできません」

于 2013-10-25T13:05:10.263 に答える