Symfony form not saving entity with ManyToMany relation

只愿长相守 提交于 2019-12-09 18:45:50

问题


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

回答1:


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.




回答2:


In my case with a services <-> projects scenario, where services has "inversedBy" and projects has "mappedBy" I had to do this in my project controller's edit action so that when editing a project the services you checked would be persisted.

public function editAction(Request $request, Project $project = null)
{
    // Check entity exists blurb, and get it from the repository, if you're inputting an entity ID instead of object ...

    // << Many-to-many mappedBy hack
    $servicesOriginal = new ArrayCollection();
    foreach ($project->getServices() as $service) {
        $servicesOriginal->add($service);
    }
    // >> Many-to-many mappedBy hack

    $form = $this->createForm(ProjectType::class, $project);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        // << Many-to-many mappedBy hack
        foreach ($servicesOriginal as $service) {
            if (!$project->getServices()->contains($service)) {
                $service->removeProject($project);
                $em->persist($service);
            }
        }

        foreach ($project->getServices() as $service) {
            $service->addProject($project);
            $em->persist($service);
        }
        // >> Many-to-many mappedBy hack

        $em->persist($project);
        $em->flush();

        return; // I have a custom `redirectWithMessage()` here, use what you like ...
    }

    return $this->render("Your-template", [
        $form       => $form->createView(),
        $project    => $project,
    ]);
}

This works for both adding and removing entities in the many-to-many from the "mappedBy" side, so EntityType inputs should work as intended.

What's going on here is we're first building an "original" collection containing all of the service entities already linked to for this project. Then when the form is saving we're ensuring:

  • First that any unchecked services (those in the original collection but not the project object) have the project removed from their internal collection, then persisted.
  • Second that any newly checked services each add the project to their internal collection, then persisted.

Important: This depends on your entity's addService() and addProject() methods respectively check that each others' collections do not contain duplications. If you don't do this you'll end up with an SQL level error about a duplicate record insertion.

In the service entity I have:

/**
 * Add project
 *
 * @param Project $project
 *
 * @return Service
 */
public function addProject(Project $project)
{
    if (!$this->projects->contains($project)) {
        $this->projects->add($project);
    }

    if (!$project->getServices()->contains($this)) {
        $project->getServices()->add($this);
    }

    return $this;
}

In the project entity I have:

/**
 * Add service
 *
 * @param Service $service
 *
 * @return Project
 */
public function addService(Service $service)
{
    if (!$this->services->contains($service)) {
        $this->services->add($service);
    }

    if (!$service->getProjects()->contains($this)) {
        $service->getProjects()->add($this);
    }

    return $this;
}

You could alternatively check this in your controller instead, but makes sense if the model validates this itself when possible, as the model would break anyway if there were duplicates from any source.

Finally in your controller's create action you'll likely need this bit too just before $em->persist($project). (You won't need to work with an "original" collection as none will exist yet.)

// << Many-to-many mappedBy hack
foreach ($project->getServices() as $service) {
    $service->addProject($project);
    $em->persist($service);
}
// >> Many-to-many mappedBy hack


来源:https://stackoverflow.com/questions/30463674/symfony-form-not-saving-entity-with-manytomany-relation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!