Search code examples
phpsymfonydoctrine-ormentities

ManyToOne in a twig template


I have the following tables (and entities in / ):

+------------------+                  +--------------------+
|  forum_sections  |                  |  forum_categories  |
+------------------+                  +--------------------+
| id               ├--------|         | id                 |
| title            |        |---------| section            |
| position         |                  | position           |
| created_at       |                  | title              |
+------------------+                  | description        |
                                      +--------------------+

I want to archive the following html page (without the bullets ofcource):

  • this is a title (from forum_sections->title)
    • forum 1 (from forum_categories->title)
    • forum 2 (from forum_categories->title)
  • this is another title (from forum_sections->title)
    • forum 3 (from forum_categories->title)

I did some research on how to do this. So far I tried the following in my twig layout:

{% for section in sections %}
    <h1>title</h1>

    {% for category in categories %}
        bla bla
    {% endfor %}
{% endfor %}

Is this even possible with a 'findAll' query in Doctrine2? I tried some combinations, but they seem to get all the boards ignoring the sections.


Solution

  • I think the problem is in your entities, the way you define the relationship between entitites. You need to use bidirectional mapping http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html#one-to-many-bidirectional

    I updated your entities to fit your needs.

    • Added categories to ForumSection entity
    • Added addCategory(), removeCategory(), getCategories() methods to ForumSection entity
    • Added inversedBy annotation to ForumCategory entity to complete te relationship
    • I used getCategories() method to get all the categories for the section in twig.

    The entities;

    ForumSection.php

    namespace AppBundle\Entity;
    
    /**
     *  @version    0.0.1
     *  @copyright  (c) 2015 Ricardo Jacobs. All rights reserved.
     *  @license    Proprietary and confidential source code.
     */
    
    use Doctrine\ORM\Mapping as ORM;
    use Doctrine\Common\Collections\ArrayCollection;
    
    /**
     * @ORM\Entity(repositoryClass="AppBundle\Entity\Repositories\Forum")
     * @ORM\HasLifecycleCallbacks
     * @ORM\Table(name="forum_sections")
     */
    class ForumSection
    {
        /**
         * @ORM\Column(type="integer", name="id")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @ORM\Column(type="string", length=255, name="title")
         */
        private $title;
    
        /**
         * @ORM\Column(type="integer", name="position")
         */
        private $position;
    
        /**
         * @ORM\Column(type="datetime")
         */
        private $created_at;
    
        /**
         * @ORM\OneToMany(targetEntity="AppBundle\Entity\ForumCategory", mappedBy="section")
         */
        private $categories;
    
    
        public function __construct() {
            $this->categories = new ArrayCollection();
        }
    
        public function getCategories() {
           return $this->categories;
        }
    
        /**
         * Add category
         *
         * @param AppBundle\Entity\ForumCategory
         * @return ForumSection
         */
        public function addCategory(\AppBundle\Entity\ForumCategory $category)
        {
            $this->categories[] = $category;
    
            return $this;
        }
    
        /**
         * Remove category
         *
         * @param AppBundle\Entity\ForumCategory $category
         */
        public function removeCategory(\AppBundle\Entity\ForumCategory $category)
        {
            $this->categories->removeElement($category);
        }
    
    
    
        /**
         * @ORM\PrePersist
         * @ORM\PreUpdate
         */
        public function updatedTimestamps() {
            if ($this->getCreatedAt() == null) {
                $this->setCreatedAt(new \DateTime('NOW'));
            }
        }
    
        /**
         * @return mixed
         */
        public function getId() {
            return $this->id;
        }
    
        /**
         * @return mixed
         */
        public function getTitle() {
            return $this->title;
        }
    
        /**
         * @param $title
         * @return $this
         */
        public function setTitle($title) {
            $this->title = $title;
    
            return $this;
        }
    
        /**
         * @return mixed
         */
        public function getPosition() {
            return $this->position;
        }
    
        /**
         * @param $position
         * @return $this
         */
        public function setPosition($position) {
            $this->position = $position;
    
            return $this;
        }
    
        /**
         * @return Date
         */
        public function getCreatedAt() {
            return $this->created_at;
        }
    
        /**
         * @param $date
         */
        public function setCreatedAt($date) {
            $this->created_at = $date;
    
            return $this;
        }
    }
    

    ForumCategory.php

    <?php
    
    namespace AppBundle\Entity;
    
    /**
     *  @version    0.0.1
     *  @copyright  (c) 2015 Ricardo Jacobs. All rights reserved.
     *  @license    Proprietary and confidential source code.
     */
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity(repositoryClass="AppBundle\Entity\Repositories\Forum")
     * @ORM\Table(name="forum_categories")
     */
    class ForumCategory
    {
        /**
         * @ORM\Column(type="integer", name="id")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @ORM\ManyToOne(targetEntity="AppBundle\Entity\ForumSection", inversedBy="categories")
         * @ORM\JoinColumn(name="section", referencedColumnName="id")
         */
        private $section;
    
        /**
         * @ORM\Column(type="integer", name="position")
         */
        private $position;
    
        /**
         * @ORM\Column(type="string", length=255, name="title")
         */
        private $title;
    
        /**
         * @ORM\Column(type="string", length=255, nullable=true, name="description")
         */
        private $description;
    
        /**
         * @return mixed
         */
        public function getId() {
            return $this->id;
        }
    
        /**
         * @return mixed
         */
        public function getSection() {
            return $this->section;
        }
    
        /**
         * @param $section
         * @return $this
         */
        public function setSection($section) {
            $this->section = $section;
    
            return $this;
        }
    
        /**
         * @return mixed
         */
        public function getPosition() {
            return $this->position;
        }
    
        /**
         * @param $position
         * @return $this
         */
        public function setPosition($position) {
            $this->position = $position;
    
            return $this;
        }
    
        /**
         * @return mixed
         */
        public function getTitle() {
            return $this->title;
        }
    
        /**
         * @param $title
         * @return $this
         */
        public function setTitle($title) {
            $this->title = $title;
    
            return $this;
        }
    
        /**
         * @return mixed
         */
        public function getDescription() {
            return $this->description;
        }
    
        /**
         * @param $description
         * @return $this
         */
        public function setDescription($description) {
            $this->description = $description;
    
            return $this;
        }
    }
    

    The controller;

    public function indexAction()
    {
        $em = $this->get('doctrine')->getManager();
        $sections = $sections = $this->getDoctrine() ->getRepository('AppBundle:ForumSection') ->findAll();
    
        return $this->render('AppBundle:Default:index.html.twig', array('sections'=>$sections));
     }
    

    The template;

    <ol>
        {% for forum in sections %}
            <li>
                <h2>{{forum.title}} </h2>
                <ul>
                    {% for category in forum.getCategories() %}
                        <li>{{category.title}}</li>
                    {% endfor %}
                </ul>
            </li>   
        {% endfor %}
    </ol>
    

    Now you have access to categories related to each forum_section row in your database.

    And this is the output after I run the code

    1. hello

      • hello section 1
      • hello section 2

    2. world

      • world section 1
      • world section 2

    I have to mention that Doctrine uses lazy loading by default.Doctrine will perform extra queries to find categories related to forums. If you have n forums you will have n + 1 queries. In my case I used 2 forum sections and here are the queries.

    This one finds all the forum_sections;

    SELECT t0.id AS id1, t0.title AS title2, t0.position AS position3, t0.created_at AS created_at4 FROM forum_sections t0

    Then for each forum, doctrine executes another query to find categories with different parameters. This queries don't get executed untill you call 'forum.getCategories()` method.

    SELECT t0.id AS id1, t0.position AS position2, t0.title AS title3, t0.description AS description4, t0.section AS section5 FROM forum_categories t0 WHERE t0.section = ? [Parameters: 1,2] Check out fetch-join concept to learn more about lazy loading and alternatives. http://blog.bemycto.com/good-practices/2015-05-31/understanding-doctrine-orm-lazy-load-fetch-join/