25

I'm trying to access the entity for a given embedded form in the parent CollectionType inside FormBuilder:

ParentType

Class ParentType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('children', CollectionType::class, array(
            'entry_type' => ChildType::class
        );
    }
}

ChildType

class ChildType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $child = $builder->getData(); // this returns null
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'Vendor\Bundle\Entity\Child',
        );
    }
}

While this works in a normal form, $child is being returned as null. How can I access the Child entity inside ChildType?

Pmpr
  • 14,501
  • 21
  • 73
  • 91
Nick
  • 2,632
  • 3
  • 31
  • 43

2 Answers2

44

The answer lies in using Event Listeners which listen for the PRE_SET_DATA event.

It will pass your closure a FormEvent class which contains both the form and the data being bound to it.

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->addEventListener(FormEvents::PRE_SET_DATA,
        function (FormEvent $event) use ($builder)
        {
            $form = $event->getForm();
            $child = $event->getData();

            if ($child instanceof Child) {

                // Do what ever you like with $child entity data

            }
        }
    );
}
Pmpr
  • 14,501
  • 21
  • 73
  • 91
user1207727
  • 1,533
  • 1
  • 15
  • 18
  • 1
    To ask the obvious question, do you definitely have any Bars in your parent entity that's used for the form's data? Can you check by var_dumping your bars collection from the data in your parent form type? – user1207727 Mar 16 '12 at 21:10
  • Wonderful, thanks! This worked perfectly, and also led me to the part in the docs I overlooked: [How to Dynamically Generate Forms Using Form Events](http://symfony.com/doc/current/cookbook/form/dynamic_form_generation.html) – Nick Mar 16 '12 at 21:32
  • Ah, you caught my comment before I deleted it -- I lazily excluded the part of your answer with the formfactory, trying to continue with just the builder itself. I then decided to use your solution verbatim, and it worked. Sorry to confuse! – Nick Mar 16 '12 at 21:33
  • 1
    Fantastic! I'm glad it worked for you. Some of this stuff can seem quite mysterious in comparison to symfony 1, but slowly I'm starting to see the light at the end of the tunnel :) – user1207727 Mar 16 '12 at 21:37
  • Great example. Thanks. But when I add fields in the function as it was previously discussed, the fields are not included in the prototype of collection. I need these fields in the prototype.. Is it real? How should I fix this? – nni6 Nov 14 '12 at 08:34
  • Does anybody have a simple explanation about why couldn't I do simply `$builder->add()` inside the anonymous function? – Waiting for Dev... Nov 23 '12 at 18:02
  • 3
    This will not work in Symfony2.6. There is no DataEvent (you can still google and find a page with details of the class but the class is not included in Symfony2) – AntonioCS Feb 27 '15 at 12:00
  • 2
    On symfony 2.6. event class is `Symfony\Component\Form\FormEvent` – Pavel Dubinin Sep 01 '15 at 19:48
3

This is a more detailed solution based on user1207727.

Parent Type

class FrontentStatsInputFormType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('records', CollectionType::class, array(
                'entry_type' => FrontendStatsRecordType::class,
                'allow_add' => false,
                'allow_delete' => false,
                'label' => null,
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => null
        ));
    }
}

Child Type

class FrontendStatsRecordType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addEventListener(FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($builder)
            {
                $form = $event->getForm();
                $child = $event->getData();

                if ($child instanceof StatsRecord) {

                    // Do what ever you like with $child entity data
                    // $child->getSomeValue();

                    $form->add('value', TextType::class);
                }
            }
        );

    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\StatsRecord',
        ));
    }

}

Create form in controller

public function indexAction(Request $request, InputForm $inputForm) {

    $data = array();

    foreach ($inputForm->getStatsTemplates() as $template) {
        $statsRecord = new StatsRecord();
        $data['records'][] = $statsRecord;
    }


    $form = $this->createForm('AppBundle\Form\FrontentStatsInputFormType', $data);
    $form->handleRequest($request);


    if ($form->isSubmitted() && $form->isValid()) {

        $em = $this->getDoctrine()->getManager();

        // Get entries and persist them in the database
        $records = $form->get('records')->getData();
        foreach ($records as $record) {
            $em->persist($record);
        }

        $em->flush();

        return $this->redirectToRoute('frontend_index');
    }

    return $this->render('frontend/showInputForm.html.twig', array(
        'inputForm' => $inputForm,
        'form' => $form->createView(),
    ));
}
segli
  • 161
  • 1
  • 4