Search code examples
phpsymfonyformbuilder

Symfony form not saving entity with ManyToMany relation


I have problem saving entity trough form with ManyToMany relations.

I can not save fields that are on "mappedBy" side of relation.

Code below is not saving anything to database and not trowing any errors:

// Entity/Pet
/**
 * @var \Doctrine\Common\Collections\Collection
 *
 * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Customer", mappedBy="pet", cascade={"persist"})
 */
private $customer;

/**
 * Set customer
 *
 * @param \AppBundle\Entity\Customer $customer
 * @return Pet
 */
public function setCustomer($customer)
{
    $this->customer = $customer;

    return $this;
}

// Entity/Customer
/**
 * @var Pet
 *
 * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Pet", inversedBy="customer", cascade={"persist"})
 * @ORM\JoinTable(name="customer_pet",
 *   joinColumns={
 *     @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
 *   },
 *   inverseJoinColumns={
 *     @ORM\JoinColumn(name="pet_id", referencedColumnName="id")
 *   }
 * )
 */
private $pet;

// PetType.php
$builder->add('customer', 'entity', 
          array(
            'class' => 'AppBundle:Customer',
            'property' => 'firstname',
            'empty_value' => 'Choose owner',
            'multiple' => true
          ));

It is working the other way around. So if I am saving something from CustomerType everything works.

EDIT:

Solution below worked for me but after couple days I found a problem with that solution. If form will be submitted with value that has been already saved in the database then Symfony will trow an error. To prevent that I had to check if given customer has been already assigned to the pet.

Checking of currently assigned customers had to be done on the beginning of function and not after form submission because for some reason after submission Pet() object contains submitted values not only those present in the db.

So on the beginning I've putted all already assigned customers in to the array

  $em = $this->getDoctrine()->getManager();
  $pet = $em->getRepository('AppBundle:Pet')->find($id);
  $petOriginalOwners = array();
  foreach ($pet->getCustomer() as $petCustomer) 
  {
      $petOriginalOwners[] = $petCustomer->getId();
  } 

And after form submission I've checked if submitted ID's are in the array

if ($form->isValid()) 
{
  foreach ($form['customer']->getData()->getValues() as $v) 
  {
    $customer = $em->getRepository('AppBundle:Customer')->find($v->getId());
    if ($customer && !in_array($v->getId(), $petOriginalOwners) )      
    {
      $customer->addPet($pet);
    }
  }
  $em->persist($pet);
  $em->flush();
  return $this->redirect($this->generateUrl('path'));
} 

Solution

  • In Symfony2 the entity with the property with the inversedBy doctrine comment is the one that is supposed to EDIT THE EXTRA TABLE CREATED BY THE MANYTOMANY RELATION. That is why when you create a customer it inserts the corresponding rows in that extra table, saving the corresponding pets.

    If you want the same behavior to happen the other way around, I recommend:

    //PetController.php
    public function createAction(Request $request) {
        $entity = new Pet();
        $form = $this->createCreateForm($entity);
        $form->submit($request);
    
    
    
        if ($form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            foreach ($form['customer']->getData()->getValues() as $v) {
                $customer = $em->getRepository('AppBundle:Customer')->find($v->getId());
                if ($customer) {
                    $customer->addPet($entity);
                }
            }
            $em->persist($entity);
            $em->flush();
    
            return $this->redirect($this->generateUrl('pet_show', array('id' => $entity->getId())));
        }
    
        return $this->render('AppBundle:pet:new.html.twig', array(
                    'entity' => $entity,
                    'form' => $form->createView(),
        ));
    }
    
    private function createCreateForm(Pet $entity) {
            $form = $this->createForm(new PetType(), $entity, array(
                'action' => $this->generateUrl('pet_create'),
                'method' => 'POST',
            ));
    
            return $form;
        }
    

    These two are but standard Symfony2 CRUD-generated actions in the controller corresponding to Pet entity.

    The only tweak is the foreach structure inserted in the first action, that way you forcibly add the same pet to each customer you select in the form, thus getting the desired behavior.

    Look, it is highly probable THIS is not the RIGHT WAY, or the PROPER WAY, but is A WAY and it works. Hope it helps.