6

I'm currently working on a language assessment project which enables you to take an exam in the language you want and evaluate your level. I use Symfony2 framework and work with Doctrine2 as well. My issue is the following one:

I have two entities Exam and Question linked by a Many-To-Many relation (Exam being the owner). Each exam can be related to several questions, and each question can be related to several exams.

Here is my code:

Exam entity

/**
 * Exam
 *
 * @ORM\Table(name="cids_exam")
 * @ORM\Entity(repositoryClass="LA\AdminBundle\Entity\ExamRepository")
 */
class Exam
{
    ...

    /**
    * @ORM\ManyToMany(targetEntity="LA\AdminBundle\Entity\Question", cascade={"persist"})
    * @ORM\JoinTable(name="cids_exam_question")
    */
    private $questions;

    ...


    /**
     * Constructor
     */
    public function __construct()
    {
        $this->questions = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add questions
     *
     * @param \LA\AdminBundle\Entity\Question $questions
     * @return Exam
     */
    public function addQuestion(\LA\AdminBundle\Entity\Question $questions)
    {
        $this->questions[] = $questions;

        return $this;
    }

    /**
     * Remove questions
     *
     * @param \LA\AdminBundle\Entity\Question $questions
     */
    public function removeQuestion(\LA\AdminBundle\Entity\Question $questions)
    {
        $this->questions->removeElement($questions);
    }

    /**
     * Get questions
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getQuestions()
    {
        return $this->questions;
    }
}

As long as it is a unidirectional relation, there is no 'exams' attribute in my Question class.

Now, what I want to do is getting all the questions related to a specific exam, calling the getQuestions() method, like this:

$questions = $exam->getQuestions();

But this method returns an empty array, even if I have data in my database. If I var_dump the $exam variable, I can see the questions array is empty:

object(LA\AdminBundle\Entity\Exam)[47]
  private 'id' => int 5
  ...
  private 'questions' => 
    object(Doctrine\ORM\PersistentCollection)[248]
      private 'snapshot' => 
        array (size=0)
          empty
      private 'owner' => null
      private 'association' => null
      private 'em' => null
      private 'backRefFieldName' => null
      private 'typeClass' => null
      private 'isDirty' => boolean false
      private 'initialized' => boolean false
      private 'coll' => 
        object(Doctrine\Common\Collections\ArrayCollection)[249]
          private '_elements' => 
            array (size=0)
              ...

I think I could maybe write a findByExam() function in my QuestionRepository, but I don't really know how to implement the joins in this case.

Any help would be great!

Beliasus
  • 63
  • 1
  • 1
  • 4

2 Answers2

11

To have a findByExam() method in your QuestionRepository do the following:

 public function findByExam($exam)
 {
    $q = $this->createQueryBuilder('q')
        ->where('q.exam = :exam')
        ->setParameter('exam', $exam)
        ->getQuery();

    return $q->getResult();
 }

You could also create a bi-directional relationship not uni-directional !

Each exam can be related to several questions, and each question can be related to several exams.

Create a bi-directional relationship by adding this to your Question entity:

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Vendor\YourExamBundle\Entity\ExamInterface;

class Question 
{
    protected $exams;

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

    public function getExams()
    {
       return $this->exams;
    }

    public function addExam(ExamInterface $exam)
    {
        if !($this->exams->contains($exam)) {
            $this->exams->add($exam);
        }
        return $this;
    }

    public function setExams(Collection $exams)
    {
        $this->exams = $exams;

        return $this;
    }

    // ...

Afterwards you can use...

$question->getExams()

... in your controller.

To automatically join your related entities doctrine's fetch option can be used with:

  • LAZY ( loads the relations when accessed )
  • EAGER ( auto-joins the relations )
  • EXTRA_LAZY ( manual fetching )

example:

/**
 * @ManyToMany(targetEntity="Question",inversedBy="exams", cascade={"all"}, fetch="EAGER")
 */

Though eager loading has a downside in terms of performance it might be an option for you.

Doctrine Fetch with EAGER

Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application.

Read more about it in the Doctrine Documentation.

Another option you should check when working with relations is the cascade option.

See the Doctrine - Working with Associations chapter of the documentation.

Tip: You should create interfaces for exams and questions and use them instead of the original entity in your set and add methods to allow easier extending.

Nicolai Fröhlich
  • 51,330
  • 11
  • 126
  • 130
  • Thanks for your great answer! It will help a lot. I will work on it and let you know if I solved my problems or encountered others ;) – Beliasus May 22 '13 at 12:36
  • I tried to create a bi-directional relationship instead of the uni-directional, but still I can't get anything. Do you think it can be an issue in the controller, just before persisting an exam with its related questions into the database? I might have to actually add each questions to the exam instance? I mean do you know if the bind request in a form would call the addQuestion() function or do I have to do it manually? – Beliasus May 23 '13 at 08:48
  • After having searched for hours, I realized my problem was not coming at all from the many to many relationship but from something else which was interfering with it. Actually, my getQuestions() function from the Exam entity works, and this is the best way to do what I wanted, with a uni-directional relationship. Thank you for your help, I used your advice for the add functions, and had a good look at Doctrine Fetching too. – Beliasus May 23 '13 at 13:01
  • Im glad i could help - ask your original question to creating a findExam method in the repostory ( which is covered ) and fetching related entities you might want to accept my the answer. it will probably be helpful for others then :) – Nicolai Fröhlich May 23 '13 at 14:51
  • 2
    @Beliasus, can you share with us what finally solved your problem? I have the same problem, and you say "_[...], I realized my problem was not coming at all from the many to many relationship but from something else which was interfering with it_". What's the "something else"? Thanks! – thomaskonrad Sep 13 '13 at 13:30
  • Sir @nifr, your last sentence (the tip) seems very important but I unfortunately couldn't fully understand it. Any link please to understand why interfaces `allow easier extending`. Thanks in advance – Adib Aroui Aug 28 '16 at 18:06
0

Bi-Directional Relations using Doctrine2 ORM with association table exam_questions exam_id question_id

<?php

class Exams

 ....OTHER PROPERTIES...

 /**
  * Owning Side
  *
  * @ManyToMany(targetEntity="Questions", inversedBy="exams")
  * @JoinTable(name="exam_questions",
  *      joinColumns={@JoinColumn(name="exam_id", referencedColumnName="id")},
  *      inverseJoinColumns={@JoinColumn(name="question_id", referencedColumnName="id")}
  *      )
  */
 private $questions;

 ..OTHER CODES..

 }


 class Questions{


 ..OTHER CODES..

 /**
  * Inverse Side
  *
  * @ManyToMany(targetEntity="Exams", mappedBy="questions")
  */
 private $exams;

 ..OTHER CODES..

 }

http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#annref-manytomany

SudarP
  • 906
  • 10
  • 12