Search code examples
symfonydoctrine-orm

The form's view data is expected to be an instance of MyEntity but is an instance of ArrayCollection


I can create a form which works fine but getting errors when trying to flush data to the database:

The controller action looks like this:

$purchase = new Purchase();
        $form = $this->createFormBuilder($purchase)
            ->add('addresses', AddressType::class)
            ->add('quantity', IntegerType::class)
            ->add('toBeDeliveredAt', DateType::class)
            ->add('comment', TextareaType::class)
            ->add('save', SubmitType::class, array('label' => 'Submit'))
            ->getForm();

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $em->persist($purchase);
            $em->flush();
        }

Class AddressType looks like this:

public function buildForm(FormBuilderInterface $builder, array $options)

{
    $builder
        ->add('title', TextType::class)
        ->add('firstname', TextType::class)
        ->add('lastname', TextType::class)
        ->add('street', TextType::class)
        ->add('city', TextType::class)
        ->add('zipcode', TextType::class)
        ->add('country', TextType::class)
        ->add('phone', TextType::class)
        ->add('email', EmailType::class);
}

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

But that gives me the following error:

The form's view data is expected to be an instance of class XxxBundle\Entity\Address, but is an instance of class Doctrine\Common\Collections\ArrayCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\Common\Collections\ArrayCollection to an instance of XxxBundle\Entity\Address.

If I set the data_class option to null I get

Warning: spl_object_hash() expects parameter 1 to be object, string given

How can I add a view transformer that transforms an instance of class ArrayCollection to an instance of Address?


Solution

  • Addresses is an array collection so you have to use it as an collection in your form like this :

    $builder->add('addresses', CollectionType::class, array(
                'entry_type' => AddressType::class
            ));
    

    Instead of this :

    $builder->add('addresses', AddressType::class)
    

    In this way you embed Address Form into Purchase Form

    You can see the doc about embed a collection of form here

    Explanations (sorry for my bad English) :

    You have to think about object.

    In your Purchase Entity you have a collection of Address because a Purchase object can have many address that is to say to persist Purchase object you have to give a collection of addresses.

    So when you built your Purshase form, Symfony expects an array collection for addresses input. It is what I wrote above. With this, if you are in the case where you want to add a new Purchase object, when you go to view the form, to display fields corresponding to the Address, you have to do this into your form:

        <ul class="addresses">
                {# iterate over each existing address and render these fields #}
                {% for address in form.addresses %}
                    <li>{{ form_row(address.title) }}</li>
    
                     //do this for all the others fields ...
    
                {% endfor %}
        </ul>
    

    But if you want to add many address dynamically, you have to do 2 things :

    First, you have to do add 'allow_add' => true in the form builder

    $builder->add('addresses', CollectionType::class, array(
            'entry_type'   => AddressType::class,
            'allow_add'    => true,
        ));
    

    Second, the allow_add also makes a "prototype" variable available to you in the form html generated : this is where Javascript come in :) To do work this, you have to write some Javascript and you can see a good example here

    I hope that it's clearer