Symfony3 / Doctrine - 有效地构建数据树而不进行延迟加载

时间:2016-05-11 15:50:19

标签: tree doctrine symfony nested-sets

我有一个表示树的模型,如下所示:

class Category
{

  //the definition of the $id and $parent_id is abbreviated

  /**
   * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
   */
  private $children;

  /**
   * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
   * @ORM\JoinColumn(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,每次请求任何类别的数据时, BUT 都会创建一个数据库请求。我搜索了网页并查看了this文章,其中描述了在学说中创建分层数据。

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

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

向前谢谢!

1 个答案:

答案 0 :(得分:0)

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查询。这可能看起来有点 hack-ish 但它确实有效。

class Category
{
    // ...

    /**
     * @ORM\OneToMany(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 %}

希望这会有所帮助,它不是很漂亮但它有效。