Symfony3/Doctrine — 有效地构建数据树,无需延迟加载



我有一个代表树的模型,它看起来像这样:

class Category
{
  //the definition of the $id and $parent_id is abbreviated
  /**
   * @ORMOneToMany(targetEntity="Category", mappedBy="parent")
   */
  private $children;
  /**
   * @ORMManyToOne(targetEntity="Category", inversedBy="children")
   * @ORMJoinColumn(name="parent_id", referencedColumnName="id")
   */
  private $parent = null;
  public function __construct()
  {
    $this->children = new ArrayCollection();
  }
  public function getChildren()
  {
    return $this->children;
  }
  //Getter and Setters following
}

为了检索结果树,我创建了一个简单的RecursiveIterator,它有效,每次请求类别的任何数据时都会创建一个数据库请求。我在网上搜索了一下,看到了这篇文章,它描述了在教义中创建分层数据。

如果这种方式在Symfony中是不可能的,则可以在单个查询中加载数据库中的所有条目,并构造所有对象一次,然后构建树。

所以我的问题是:如何在Symfony中使用Doctrine Hierarchical Data?如果不可能,如何在单个查询中加载数据库中的所有行?

提前感谢!

Category实体中

使children属性EXTRA_LAZY,以防您访问它们。 这将停止某些方法的满载触发器,特别是 count ,以防万一。

http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/tutorials/extra-lazy-associations.html

添加一个方法hasChildren以返回true如果有子项。 如果你只是count($this->children)它只会触发count查询,因为EXTRA_LAZY,这仍然比满载好。

但还有另一种方式。在运行时,childrenPersistentCollection 的一个实例,它有一个方法 getSnapshot 该方法返回集合中元素的最后一个快照。 换句话说,只有加载的内容,甚至不会发出count查询。这可能看起来有点黑客化,但它有效。

class Category
{
    // ...
    /**
     * @ORMOneToMany(targetEntity="Category", mappedBy="parent", fetch="EXTRA_LAZY")
     */
    private $children;
    // ...
    public function hasChildren()
    {
        return count($this->children->getSnapshot()) > 0;
    }
}

现在要加载所有需要的实体/行,请创建一个findByRootId的方法 CategoryRepository . 即使您将$level设置为大于数据库中的实际级别,这仍然有效。

由于额外的连接,性能会受到每个附加级别的影响,但除非你的包含数千个元素,否则它总体上运行良好。

public function findByRootId($id, $level = 10)
{
    $children = '';
    $joins = '';
    for ($j = 0, $i = 1; $i <= $level; $i++, $j++) {
        $children .= ", c{$i}";
        $joins .= "LEFT JOIN c{$j}.children c{$i} ";
    }
    // Change `YourBundleName` with you actual namespace/bundle name
    $query = "SELECT c0 {$children}
              FROM YourBundleName:Category c0
              {$joins}
              WHERE c0.id = :rootId";
    return $this
            ->getEntityManager()
            ->createQuery($query)
            ->setParameter('rootId', $id)
            ->getSingleResult();
}

最后,在渲染时,如果您尝试访问未加载的子项,原则将触发完全加载事件。使用 hasChildren 方法查看是否加载了子项。

注意:子项可能存在,但由于指定的level而未加载。

{% macro tree(parent) %}
    <ul>
        <li>
            {{ parent.name }}
            {% if parent.hasChildren %}
                {% for child in parent.children %}
                    {{ _self.tree(child) }}
                {% endfor %}
            {% endif %}
        </li>
    </ul>
{% endmacro %}
{% import _self as builder %}
{% block body %}
    {{ builder.tree(parent) }}
{% endblock %}

希望这有帮助,它不漂亮,但它有效。

最新更新