Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get entities from a unidirectional many to many relation with Doctrine2 and Symfony2

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!

like image 696
Beliasus Avatar asked May 22 '13 11:05

Beliasus


1 Answers

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.

like image 71
Nicolai Fröhlich Avatar answered Nov 16 '22 06:11

Nicolai Fröhlich