Symfony/Twig - Рекурсивное уменьшение запросов к БД

У меня есть дерево категорий в шаблоне ветки:

{% for category in categories %}
    <li>
        <div class="li"><a href="{{ path('accessories_list', { 'category' : category.url }) }}">{{ category.name|trans({}, 'categories')|raw }}</a></div>
        {% if category.children is not empty %}
            <ul>
                {% include "default/_menu_links.html.twig" with {'categories':category.children} only %}
            </ul>
        {% endif %}
    </li>
{% endfor %}

Он создает +- 53 запроса к базе данных, если у меня есть 6 категорий и 7 подкатегорий в каждой отдельной категории.

Есть ли способ уменьшить это число? Я использую useResultCache(true) в доктрине, но похоже, что он не загружается из кеша (по крайней мере, не в режиме разработки).

Как вы работаете с деревьями категорий?

ОБНОВЛЕНИЕ: Сущность:

...
    /**
     * One Category has Many Subcategories.
     * @ORM\OneToMany(targetEntity="Category", mappedBy="parent", cascade={"persist"}))
     */
    private $children;
    /**
     * Many Subcategories have One Category.
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children", cascade={"persist"})
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     */
    private $parent;
...
    /**
     * Add child
     *
     * @param \App\Entity\Product\Category $child
     *
     * @return Category
     */
    public function addChild(\App\Entity\Product\Category $child): Category
    {
        $this->children[] = $child;
        return $this;
    }

    /**
     * Remove child
     *
     * @param \App\Entity\Product\Category $child
     */
    public function removeChild(\App\Entity\Product\Category $child)
    {
        $this->children->removeElement($child);
    }

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

    /**
     * Set parent
     *
     * @param \App\Entity\Product\Category $parent
     *
     * @return Category
     */
    public function setParent(\App\Entity\Product\Category $parent = null): Category
    {
        $this->parent = $parent;
        return $this;
    }

    /**
     * Get parent
     *
     * @return \App\Entity\Product\Category
     */
    public function getParent()
    {
        return $this->parent;
    }
...

person user8810516    schedule 03.12.2017    source источник
comment
Обычно, если количество ваших запросов к базе данных зависит от извлеченных данных (больше данных = больше запросов), вам следует рассмотреть возможность использования операторов JOIN для уменьшения количества запросов к базе данных. Было бы полезно, если бы вы могли предоставить некоторый код ваших сущностей доктрины.   -  person baris1892    schedule 03.12.2017
comment
@ radon66 Привет, спасибо за ответ. Обновил мой вопрос. Думаете, лучше раз в день генерировать категории в json, кэшировать и использовать на сайте?   -  person user8810516    schedule 03.12.2017


Ответы (1)


Согласно моему комментарию: вы должны JOINваши подкатегории. Я предполагаю, что вы в настоящее время делаете что-то подобное, чтобы получить свой categories:

   public function getCategories()
    {
        return $this->getEntityManager()->createQueryBuilder()
            ->select("category")
            ->from("App:Category", "category")
            ->where("category.parent IS NULL")
            ->getQuery()->getResult();
     }

Поэтому, если вы сейчас перебираете этот массив категорий и пытаетесь получить доступ к свойству children, для каждого дочернего элемента будет запущен подзапрос, что вызывает такое большое количество запросов к базе данных.

Вместо этого вы должны JOIN сделать это следующим образом:

 public function getCategories()
    {
        return $this->getEntityManager()->createQueryBuilder()
            ->select("category", "subcat")
            ->from("App:Category", "category")
            ->leftJoin("category.children", "subcat")
            ->where("category.parent IS NULL")
            ->getQuery()->getResult();
    }

Если теперь вы выполните итерацию по categories и получите доступ к свойству children, дополнительный запрос не будет запущен!

Использование приведенного выше запроса и этого фрагмента в twig приведет только к одному запросу к базе данных:

   {% for category in categories %}
        <p>cat={{ category.id }}</p>

        {% for subcat in category.children %}
            <p>subcat={{ subcat.id }}</p>
        {% endfor %}
        <hr>
    {% endfor %}
person baris1892    schedule 03.12.2017
comment
Привет, спасибо, попробовал ваше решение и получил такое же количество запросов. Может быть, я должен просто загрузить его в json и кэшировать. - person user8810516; 03.12.2017
comment
Вы могли бы сделать это, но это не решит проблему. Не могли бы вы опубликовать свой код, как ваши categories извлекаются? Использование моего приведенного выше кода отлично работает при новой установке symfony. leftJoin это важный метод! - person baris1892; 03.12.2017