所以我认为我的问题归结为两个问题:
当使用Adjacency List Model方法将树存储在MySQL(两个表之间)时,如何在保持性能的同时在PHP中构建可遍历的树结构?
在不重复遍历代码并使用if / else和switch语句乱丢逻辑的情况下,以所需格式显示树的可维护方法是什么?
以下是更多详情:
我正在使用Zend Framework。
我正在处理调查问卷。它存储在两个独立的表之间的MySQL数据库中:questions和question_groups。每个表都扩展了适当的Zend_Db_Table_ *类。层次结构使用邻接列表模型方法表示。
我意识到我遇到的问题可能是因为我将树结构填充到RDBMS中,所以我对替代方案持开放态度。但是,我也存储问卷调查对象及其回答,因此需要采用其他方法来支持。
问卷需要以各种HTML格式显示:
问题是叶节点,question_groups可以包含其他question_groups和/或问题。合并后,处理和显示的行数将超过100行。
目前,我有一个视图帮助程序,它使用递归执行所有处理以检索question_group的子项(在两个表之间执行UNION的查询:QuestionGroup :: getChildren($ id))。此外,在显示问卷回答的问卷时,需要另外两个查询来检索受访者及其对每个问题的回答。
虽然页面加载时间不是很长,但这种方法感觉不对。几乎每个节点的递归加上多个数据库查询都不会让我感到非常温暖和模糊。
我尝试了recursion-less和从UNION返回的完整树数组的递归方法,以构建一个分层数组来遍历和显示。但是,由于组和问题存储在单独的表中,因此存在重复的节点ID,这似乎会中断。也许我错过了那里的东西......
目前,以上面列出的格式显示树的逻辑非常混乱。我不想在整个地方复制遍历逻辑。但是,整个地方的条件也不会产生最容易维护的代码。我已经阅读了访问者,装饰器和一些PHP SPL迭代器,但我仍然不清楚如何与扩展Zend_Db_Table,Zend_Db_Table_Rowset和Zend_Db_Table_Row的类一起工作。特别是因为我还没有解决以前从数据库构建层次结构的问题。很容易添加新的显示格式(或修改现有格式)。
答案 0 :(得分:4)
邻接列表传统上在每行中为您提供一个parent_id
列,用于将行链接到其直接父级。如果行是树的根,则parent_id
为NULL。但这导致您运行许多SQL查询,这很昂贵。
添加另一列root_id
,以便每行知道它所属的树。这样,您可以使用单个SQL查询获取给定树的所有节点。在Table
类中添加一个方法,以按树的根ID获取Rowset
。
class QuestionGroups extends Zend_Db_Table_Abstract
{
protected $_rowClass = 'QuestionGroup';
protected $_rowsetClass = 'QuestionGroupSet';
protected function fetchTreeByRootId($root_id)
{
$rowset = $this->fetchAll($this
->select()
->where('root_id = ?', $root_id)
->order('id');
);
$rowset->initTree();
return $rowset;
}
}
编写一个扩展Zend_Db_Table_Row
的自定义类并编写函数来检索给定行的父级以及其子级的Rowset
。 Row
类应包含受保护的数据对象,以引用父项和子项数组。 Row
对象还可以具有getLevel()
函数和面包屑的getAncestorsRowset()
函数。
class QuestionGroup extends Zend_Db_Table_Row_Abstract
{
protected $_children = array();
protected $_parent = null;
protected $_level = null;
public function setParent(Zend_Db_Table_Row_Abstract $parent)
{
$this->_parent = $parent;
}
public function getParent()
{
return $this->_parent;
}
public function addChild(Zend_Db_Table_Row_Abstract $child)
{
$this->_children[] = $child;
}
public function getChildren()
{
return $this->_children;
}
public function getLevel() {}
public function getAncestors() {}
}
编写一个扩展Zend_Db_Table_Rowset
的自定义类,它具有迭代行集中行的功能,设置父引用和子引用,以便随后可以将它们作为树遍历。此外,Rowset
应具有getRootRow()
功能。
class QuestionGroupSet extends Zend_Db_Table_Rowset_Abstract
{
protected $_root = null;
protected function getRootRow()
{
return $this->_root;
}
public function initTree()
{
$rows = array();
$children = array();
foreach ($this as $row) {
$rows[$row->id] = $row;
if ($row->parent_id) {
$row->setParent($rows[$row->parent_id]);
$rows[$row->parent_id]->addChild($row);
} else {
$this->_root = $row;
}
}
}
}
现在,您可以在行集上调用getRootRow()
,然后返回根节点。获得根节点后,可以调用getChildren()
并循环它们。然后你也可以在任何这些中间孩子上调用getChildren()
,并递归输出你想要的任何格式的树。