Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
231 views
in Technique[技术] by (71.8m points)

php - Symfony2 form events and model transformers

I'm getting tied in knots trying to wrestle with Symfony2's form builders, events and transformers... hopefully somebody here is more experienced and can help out!

I have a form field (select drop-down) which contains some values (a shortlist) which maps to an Entity. One of these options is "other". Assume there's no AJAX for now and when a user submits the form I want to detect if they chose 'other' (or any other option not in the shortlist). If they chose one of these options then the full list of options should be shown, otherwise just show the shortlist. Should be easy, right? ;)

So, I have my Form Type and it displays the basic shortlist just fine. The code looks something like this:

namespace CompanyProjectBundleFormType;

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)
            )
        ;

        // ...

    }

    // ...
}

Now, I want to check the value submitted so I use a Form Event Listener as follows.

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?
    });
}

However, it fails with an error message like this:

Notice: Object of class Proxies\__CG__CompanyProjectBundleEntityFoo could not be converted to int in pathoprojectsymfonysymfonysrcSymfonyComponentFormExtensionCoreChoiceListChoiceList.php line 458 

I presume this error is because when the linkedFoo was over-written it removed the modelTransformer? I tried various ways of accessing a builder in the event's closure but this didn't seem to work (the return values were unexpected). Is there some other method I should be using in the event other than $event->getForm()->add()? Or is there a more fundamental problem with my approach here?

Basically I don't want to mess with the linkedFoo field's config/transformers/labels except to change the choices available... is there some other way to do that? E.g. something like $form->getField()->updateChoices()?

Thanks in advance for any help you can offer!

C

P.S. is there any better documentation or discussion of the forms, events, etc than on the Symfony website? E.g. what's the difference between PRE_SET_DATA, PRE_SUBMIT, SUBMIT, etc? When are they fired? What should they be used for? How does inheritance work with custom form fields? What is a Form and a Builder, how do they interact and when should you deal with each? How, when and why should you use the FormFactory you can access through $form->getConfig()->getFormFactory()? Etc..


Edit: In response to Florian's suggestion here's some more info about things that were tried but do not work:

If you try to get the FormBuilder within the event like this:

/** @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))
);

Then you get the error:

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

So then you try something like Florian suggested, i.e.

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

...but you get this error instead:

Notice: Object of class Proxies\__CG__CompanyProjectBundleEntityFoo could not be converted to int 
in C:pathovendorsymfonysymfonysrcSymfonyComponentFormExtensionCoreChoiceListChoiceList.php line 458

Which seems to suggest that the second line (which adds the ModelTransformer) is never called because the ->add() call is failing before you can get there.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Thanks to the ideas from sstok (on github), I think I've got it working now. The key is to create a customised Form Type and then use that to add the ModelTransformer.

Create the custom Form Type:

namespace CaponicaMagnetBundleFormType;

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';
    }
}

Create a service definition for the new Type:

company_project.form.type.foo_shortlist:
    class: CompanyProjectBundleFormTypeFooShortlistChoiceType
    tags:
        - { name: form.type, alias: fooShortlist }
    arguments:
        - @doctrine.orm.entity_manager

The main form's code now looks something like this:

namespace CompanyProjectBundleFormType;

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'
            ));
        });

        // ...

    }

    // ...
}

The key is that this method allows you to embed the ModelTransformer within the custom field type so that, whenever you add a new instance of this type it automatically adds the ModelTransformer for you and prevents the previous loop of "can't add a field without a transformer AND can't add a transformer without a field"


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...