Many to many relations are one of the mostly used relations in the relational database. It took a while for me to figure how to properly configure this relation in Symfony Doctrine. I am sharing this so it might be useful for someone.

Used very simple example students and course. A student can opt for multiple courses and a single course can be opted by multiple students.

And Entity class should be like this…

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Student
 *
 * @ORM\Table(name="ab_student")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\StudentRepository")
 */
class Student
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255)
     */
    private $email;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="dob", type="datetime")
     */
    private $dob;

    /**
     * @var ArrayCollection
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Course", inversedBy="students", fetch="EAGER")
     * @ORM\JoinTable(
     *     name="ab_student_2_course",
     *     joinColumns={@ORM\JoinColumn(name="course_id", referencedColumnName="id", nullable=false, onDelete="cascade")},
     *     inverseJoinColumns={@ORM\JoinColumn(name="student_id", referencedColumnName="id", nullable=false, onDelete="cascade")}
     * )
     */
    private $courses;

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

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Student
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set email
     *
     * @param string $email
     *
     * @return Student
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set dob
     *
     * @param \DateTime $dob
     *
     * @return Student
     */
    public function setDob($dob)
    {
        $this->dob = $dob;

        return $this;
    }

    /**
     * Get dob
     *
     * @return \DateTime
     */
    public function getDob()
    {
        return $this->dob;
    }

    /**
     * @return ArrayCollection
     */
    public function getCourses()
    {
        return $this->courses;
    }

    /**
     * @param ArrayCollection $courses
     */
    public function setCourses($courses)
    {
        $this->courses = $courses;
    }

}

namespace AppBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Course
 *
 * @ORM\Table(name="ab_course")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CourseRepository")
 */
class Course
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;


    /**
     * @var ArrayCollection
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Student", mappedBy="courses", cascade={"all"})
     */
    private $students;

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

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Course
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return ArrayCollection
     */
    public function getStudents()
    {
        return $this->students;
    }

    /**
     * @param ArrayCollection $students
     */
    public function setStudents($students)
    {
        $this->students = $students;
    }

}

The above entities will create the following SQL and it will also create the cross-reference table.

CREATE TABLE ab_student 
  ( 
     id    INT auto_increment NOT NULL, 
     name  VARCHAR(255) NOT NULL, 
     email VARCHAR(255) NOT NULL, 
     dob   DATETIME NOT NULL, 
     PRIMARY KEY(id) 
  ) 
DEFAULT CHARACTER SET utf8 
COLLATE utf8_unicode_ci 
engine = innodb; 

CREATE TABLE ab_student_2_course 
  ( 
     course_id  INT NOT NULL, 
     student_id INT NOT NULL, 
     INDEX idx_87d76467591cc992 (course_id), 
     INDEX idx_87d76467cb944f1a (student_id), 
     PRIMARY KEY(course_id, student_id) 
  ) 
DEFAULT CHARACTER SET utf8 
COLLATE utf8_unicode_ci 
engine = innodb; 

CREATE TABLE ab_course 
  ( 
     id   INT auto_increment NOT NULL, 
     name VARCHAR(255) NOT NULL, 
     PRIMARY KEY(id) 
  ) 
DEFAULT CHARACTER SET utf8 
COLLATE utf8_unicode_ci 
engine = innodb; 

ALTER TABLE ab_student_2_course 
  ADD CONSTRAINT fk_87d76467591cc992 FOREIGN KEY (course_id) REFERENCES 
  ab_student (id) ON DELETE CASCADE; 

ALTER TABLE ab_student_2_course 
  ADD CONSTRAINT fk_87d76467cb944f1a FOREIGN KEY (student_id) REFERENCES 
  ab_course (id) ON DELETE CASCADE; 

Hope this helps 😉