3

所以我认为我的问题归结为两个问题:

  1. 当树存储在 MySQL 中(在两个表之间)时,如何使用邻接列表模型方法在 PHP 中构建可遍历的树结构,同时牢记性能?

  2. 什么是一种可维护的方法来以所需的格式显示树而不复制遍历代码并用 if/else 和 switch 语句乱扔逻辑?

以下是更多详细信息:

我正在使用 Zend 框架。

我正在处理一份问卷。它存储在两个单独的表之间的 MySQL 数据库中:questions 和 question_groups。每个表都扩展了适当的 Zend_Db_Table_* 类。层次结构使用邻接列表模型方法表示。

我意识到我遇到的问题可能是由于我将树结构填充到 RDBMS 中,所以我对替代方案持开放态度。但是,我还存储了问卷调查对象和他们的回答,因此需要替代方法来支持这一点。

问卷需要以各种 HTML 格式显示:

  1. 作为输入响应的表单(使用 Zend_Form)
  2. 作为带有问题(和某些组)的有序列表(嵌套),作为按问题或按组查看响应的链接。
  3. 作为一个有序列表(嵌套),每个问题都附加了回复。

问题是叶节点,question_groups 可以包含其他 question_groups 和/或问题。加起来,有超过 100 行需要处理和显示。

目前,我有一个视图助手,它使用递归进行所有处理以检索 question_group 的子项(一个在两个表之间执行 UNION 的查询:QuestionGroup::getChildren($id))。此外,当显示带有问题响应的问卷时,需要额外的两个查询来检索受访者及其对每个问题的回答。

虽然页面加载时间不是很长,但这种方法感觉不对。递归加上几乎每个节点的多个数据库查询,并没有让我内心感到很温暖和模糊。

我已经在从 UNION 返回的完整树数组上尝试了无递归和递归方法,以构建一个分层数组来遍历和显示。但是,由于组和问题存储在单独的表中,因此存在重复的节点 ID,这似乎被打破了。也许我在那里遗漏了一些东西......

目前,以上面列出的格式显示树的逻辑相当混乱。我不想到处重复遍历逻辑。然而,到处都是条件语句也不会产生最容易维护的代码。我已经阅读了有关访问者、装饰器和一些 PHP SPL 迭代器的信息,但我仍然不清楚它们如何与扩展 Zend_Db_Table、Zend_Db_Table_Rowset 和 Zend_Db_Table_Row 的类一起工作。特别是因为我还没有解决以前从数据库构建层次结构的问题。稍微轻松地添加新的显示格式(或修改现有的)会很好。

4

1 回答 1

4
  • 邻接列表传统上parent_id在每一行中为您提供一列,将一行链接到其直接父级。如果该parent_id行是树的根,则为 NULL。但这会导致您运行许多 SQL 查询,这很昂贵。

  • 添加另一列root_id,以便每一行都知道它属于哪棵树。这样,您可以使用单个 SQL 查询获取给定树的所有节点。向您的类添加一个方法以通过树的根 idTable获取 a 。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()任何这些中间子级,并以您想要的任何格式递归地输出一棵树。

于 2009-12-31T21:15:01.983 回答