0

I'm trying to create a ManyToMany relation beetwin services of a company. Each service had N parents services and N children services.

I looked at the doctrine documentation here : Many-To-Many, Self-referencing and I implemented it as followed :

Here is my service entity :

<?
namespace AppBundle\Entity;

class Service
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Service", mappedBy="enfants", cascade={"persist"})
     */
    private $parents;

    /**
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Service", inversedBy="parents")
     * @ORM\JoinTable(name="app_services_hierarchy",
     *      joinColumns={@ORM\JoinColumn(name="parent_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="enfant_id", referencedColumnName="id")}
     *      )
     */
    private $enfants;

    public function __construct()
    {
        $this->enfants  = new ArrayCollection();
        $this->parents  = new ArrayCollection();
    }

    public function getId(){
        return $this->id;
    }

    //--------------------------------------------------Enfants
    public function getEnfants(){
        return $this->enfants;
    }

    public function setEnfants($enfant){
        $this->enfants = $enfant;
    }

    public function addEnfant(Service $s){
        $this->enfants[] = $s;
        return $this;
    }

    public function removeEnfant(Service $s){
        $this->enfants->removeElement($s);
    }

    //--------------------------------------------------Parents
    public function getParents(){
        return $this->parents;
    }

    public function setParents($parents){
        $this->parents = $parents;
    }

    public function addParent(Service $s){
        $this->parents[] = $s;
        return $this;
    }

    public function removeParent(Service $s){
        $this->parents->removeElement($s);
    }

}

And here is my edit function( Controller.php) :

public function editAction(Request $request, $id)
{
    $service  = $this->getDoctrine()->getRepository(Service::class)->find($id);
    $form     = $this->createForm(ServiceType::class, $service);
    $form     ->handleRequest($request);

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

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager ->persist($service);

        dump($service);

        $entityManager ->flush();
    }

    return $this->render('AppBundle:Service:edit.html.twig', array(
        'form'      => $form->createView(),
    ));
}

And the generated form looks like :

ServiceType generated

PROBLEM :

My problem is that the childrens are updated but not the parents. I can see the parents in the $service variable when I dump() it in my controler but the only ones updated in my database table (app_services_hierarchie) are the children.

Gauthier
  • 1,116
  • 2
  • 16
  • 39
  • 1
    Have a look there https://stackoverflow.com/questions/5033825/doctrine-2-manytomany-cascade – Gregoire Ducharme Mar 27 '18 at 13:18
  • Could have be this but it's still not working ... The weird part is that when i update, the form is reloaded, the parents I selected before are selected (so my entity is filled with parents) but nothing in database so they disapear as soon as I reload the page... – Gauthier Mar 27 '18 at 13:24

1 Answers1

1

The difference between $parents and $enfants in your code is that the service you are looking at is the Owning side in case of your $enfants mapping, but not in the case of your $parents mapping.

Doctrine will not store the $parents unless you tell it to do so via cascade={"persist"}.

/**
 * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Service", mappedBy="enfants", cascade={"persist"})
 */

This is basically the same anwer given in the post linked by @GregoireDucharme.

Edit: after some research, apparently this problem cannot be solved using cascade. According to the Doctrine documentation:

Doctrine will only check the owning side of an association for changes.

So what you have to do is tell your $parents to also update the $children property.

public function addParent(Service $s){
    $this->parents[] = $s;
    $s->addEnfant($this);
    return $this;
}

public function removeParent(Service $s){
    $this->parents->removeElement($s);
    $s->removeEnfant($this);
}

In your form, make sure to specify the following:

->add('parents', 'collection', array(
    'by_reference' => false,
    //...
))

(I haven't spellchecked any of the code above, so tread carefully.)

If 'by_reference' is set to true, addParent and removeParent will not be called.

Credit goes to this blog post by Anny Filina.

It also states that you can remove the cascade option from your $parents property, but you probably should add cascade={"persist","remove"} to your $enfants property.

Micha
  • 523
  • 10
  • 26
  • I tried this solution before but it is still not working, the entity is updated but not the database. The database is just updated for the children (enfants) / I edited my question to add the cascade persist (even if it is not working, you both told me to add it, I may be a part of the solution, and it makes sens) – Gauthier Mar 27 '18 at 15:07
  • have you tried adding `refresh` as well? `cascade={"persist", "refresh"}`. really not sure if that has anything to do with "updating" entities, but it is worth a try. – Micha Mar 27 '18 at 15:30
  • 1
    @Gauthier edited my response. looks like the `cascade` option at that position is not the solution you were looking for. – Micha Mar 27 '18 at 16:34
  • I think I'm so close, I get the error "Expected argument of type "AppBundle\Entity\Service", "string" given" when I try to update, and the form is not displayed as a choice list anymore. I feel I'm closer. Thanks anyway for looking this, I think you found the problem @micha – Gauthier Mar 28 '18 at 10:14