web-dev-qa-db-ja.com

symfony2フォームイベントとモデルトランスフォーマー

私は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フィールドのconfig/transformers/labels exceptをいじって、利用可能な選択肢を変更したくありません...これを行う他の方法はありますか?例えば。 $form->getField()->updateChoices()のようなもの?

あなたが提供できるあらゆる助けを事前に感謝します!

C

追伸SymfonyのWebサイトよりも優れたドキュメントやフォーム、イベントなどの議論はありますか?例えば。 PRE_SET_DATA、PRE_SUBMIT、SUBMITなどの違いは何ですか?彼らはいつ解雇されますか?それらは何に使用する必要がありますか?継承はカスタムフォームフィールドでどのように機能しますか?フォームとビルダーとは何ですか?それらはどのように相互作用し、いつそれぞれを処理する必要がありますか? $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
_

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

23
caponica

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

29
caponica

リスナーは(ほ​​ぼ:))に見えます。

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"))に変換するまでは、トランスフォーマーは必要ありません。

1
Florian Klein