Masha
Masha

Reputation: 857

symfony doctrine many_to_many with 3 entities

I got 3 entities:

1st:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ProactiveSubject
 *
 * @ORM\Table(name="ProactiveSubject")
 * @ORM\Entity
 */
class ProactiveSubject
{
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;

/**
 * @ORM\ManyToMany(targetEntity="ProactiveCheck", inversedBy="p_subjects")
 * @ORM\JoinTable(name="subject_check_operator")
 */
private $checks;

public function __construct() {
    $this->checks = new \Doctrine\Common\Collections\ArrayCollection();
  }
}

2nd:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ProactiveCheck
 *
 * @ORM\Table(name="ProactiveCheck")
 * @ORM\Entity
 */
class ProactiveCheck
{
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;

/**
 * @ORM\ManyToMany(targetEntity="ProactiveSubject", mappedBy="ProactiveChecks")
 */
private $p_subjects;

public function __construct() {
    $this->p_subjects = new \Doctrine\Common\Collections\ArrayCollection();
  }

}

and 3rd:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ProactiveOperator
 *
 * @ORM\Table(name="ProactiveOperator")
 * @ORM\Entity
 */
 class ProactiveOperator
{
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="IDENTITY")
 */
private $id;
/**
 * @ORM\ManyToMany(targetEntity="ProactiveSubject", inversedBy="p_subjects")
 *
 */
private $p_operator;

public function __construct() {
    $this->p_operator = new \Doctrine\Common\Collections\ArrayCollection();
  }

}

In short, 1 subject may have many checks, and these checks may have many operators, so it should look like that:

subject1 ==> check EQUALITY ==> operator =
subject1 ==> check GREATER  ==> operator >
subject2 ==> check AMOUNT ==> operator = or operator > or operator < etc... depending on user input

I need to make something like a a many_tomany_tomany connection in my db so 3 entities should be connected through 1 join table. The problem is that when I run doctrine:schema:update --force, it connects only 2 entities (operator and subject), but does not connect a Check entity. Any Ideas how to fix that and make a table subject_check_operator with these entities? Any ideas would be welcome. Thank you.

Upvotes: 2

Views: 1645

Answers (4)

Taras  Hanych
Taras Hanych

Reputation: 171

It is fascinating question and answer is interesting too. If you're faced to the issue, it is mean that your vision and understanding of it is wrong. It is not ManuToMany. This will be another entity that will be responsible for this complex relation. Even if you somehow get away with it. Think about what you will do if you need the date of creating such relations.

<?php

namespace App\Entity;

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class ChildrenFatherMotherMapping
{
    #[ORM\Column(type: Types::INTEGER, nullable: false)]
    #[ORM\Id]
    #[ORM\GeneratedValue(strategy: 'IDENTITY')]
    private int $id;

    #[ORM\ManyToOne(inversedBy: 'childFatherMotherMappings')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Child $child = null;

    #[ORM\ManyToOne(inversedBy: 'childFatherMotherMappings')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Father $father = null;

    #[ORM\ManyToOne(inversedBy: 'childFatherMotherMappings')]
    #[ORM\JoinColumn(nullable: false)]
    private ?Mother $mother = null;

    /** TODO: add getters and setters */
}

Upvotes: 0

lordrhodos
lordrhodos

Reputation: 2745

You have several issues here and to me it is not clear what your example with EQUALITY, GREATER, etc. means, but if this:

In short, 1 subject may have many checks, and these checks may have many operators, so it should look like that:

and

The problem is that when I run doctrine:schema:update --force, it connects only 2 entities (operator and subject), but does not connect a Check entity.

are your main issues here, than you need to fix your relational mapping, which contains several errors.

You need to decide wether you want the many-to-many relations to be bidirectional or unidirectional. For bidirectional relation please pay special attention to the documentation about owning and inversing sides of a relation

As you have already included mappedBy and inversedBy settings, I assume you aim for bidirectional relations. When working with bidirectional relations, you specify the attribute name for the mappedBy and inversedBy settings in the annotation and not the class names, etc.

ProactiveCheck

For the relation to subject mappedBy="ProactiveChecks" should be mappedBy="checks". And you are missing the relation to the operator completely

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ProactiveCheck
 *
 * @ORM\Table(name="ProactiveCheck")
 * @ORM\Entity
 */
class ProactiveCheck
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @ORM\ManyToMany(targetEntity="ProactiveSubject", mappedBy="checks")
     */
    private $p_subjects;

    /**
     * @ORM\ManyToMany(targetEntity="ProactiveOperator", inversedBy="checks")
     */
    private $operators;

    public function __construct() {
        $this->p_subjects = new \Doctrine\Common\Collections\ArrayCollection();
      }

}

ProactiveOperator

If many checks should have many operators, this class is mapped wrong. You are creating a relation to ProactiveSubject and have no relation to the ProactiveCheck entity. That's why it is not connecting when updating the database schema.

It should look like this:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * ProactiveOperator
 *
 * @ORM\Table(name="ProactiveOperator")
 * @ORM\Entity
 */
 class ProactiveOperator
{

    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;
    /**
     * @ORM\ManyToMany(targetEntity="ProactiveChecks", mappedBy="operators")
     *
     */
    private $p_operator;

    public function __construct() {
        $this->p_operator = new \Doctrine\Common\Collections\ArrayCollection();
      }
}

By the way, you can easily check the validity of your mapping with the following command: bin/console doctrine:schema:validate

Upvotes: 1

Jenne
Jenne

Reputation: 893

So your trying to make an Entity-(many-to-many)-Entity-(many-to-many)-Entity. This is no different than a single many to many. You were well on the way, however in your 3rd entity you sort of link to the first.

What you want is something (pseudo) like:

e1:
    many-to-many: e2

e2:
    many-to-many: e1
    many-to-many: e3

e3:
    many-to-many: e2

What your currently doing is pointing the reverse of e3 to the reverse of e1 (e2 > e1 property), so just make another property with its own many-to-many in e2 pointing to e3.

Just remember that each relation needs a property in the class if you want to access it.

This will however create two join tables. Which could be fine considering the situation. There is also another way where e2 is essentially the join table with some extra fields. Then you have a different situation and for that I would point you to Doctrine2: Best way to handle many-to-many with extra columns in reference table.

Upvotes: 0

t-n-y
t-n-y

Reputation: 1209

Maybe you can make an entity that will have the 3 relations you want (the equivalent of link table generated by doctrine, but custom)

Upvotes: 0

Related Questions