我有一个代表树的模型,它看起来像这样:
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
,这仍然比满载好。
但还有另一种方式。在运行时,children
是 PersistentCollection
的一个实例,它有一个方法 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 %}
希望这有帮助,它不漂亮,但它有效。