3

所以我認爲我的問題可以歸結爲兩個問題:鄰接表模型的兩個表

  1. 如何構建在PHP一筆畫樹結構,當樹存儲在MySQL(兩個表之間)使用鄰接列表模型的方法,同時記住性能?

  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))。另外,當顯示問卷回答的調查問卷時,需要額外的兩個查詢來檢索回答者以及他們對每個問題的回答。

雖然頁面加載時間不是很長,但這種方法感覺不對。遞歸加上多個數據庫查詢幾乎每個節點都不會讓我感到非常溫暖和模糊。

我試過recursion-less以及從UNION返回的完整樹數組的遞歸方法來構建一個遍歷和顯示的分層數組。但是,由於組和問題存儲在不同的表中,因此存在重複的節點標識,因此似乎會出現問題。也許我錯過了那裏的東西......

目前,以上面列出的格式顯示樹的邏輯相當混亂。我不想在遍地都重複遍歷邏輯。但是,遍佈各地的條件不會產生最易於維護的代碼。我已經閱讀了訪問者,裝飾器和一些PHP SPL迭代器,但我仍然不清楚這將如何與擴展Zend_Db_Table,Zend_Db_Table_Rowset和Zend_Db_Table_Row的類一起工作。特別是因爲我還沒有解決以前從數據庫構建層次結構的問題。很容易添加新的顯示格式(或修改現有的格式)。

回答

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。 類應包含受保護的數據對象,以引用父級和子級數組。 A 對象也可以具有用於麪包屑的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(),並以任何您想要的格式遞歸輸出樹。

+1

謝謝比爾。我爲這兩個表添加了一個root_id列,並實現了您建議的方法(http://pastie.org/762955)。雖然我不能看到大局。該方法如何遍歷Rowset工作中處理樹的行遍佈兩個表?我假設fetchTreeByRootId()需要使用UNION。或者會創建一個視圖是一個更好的解決方案?雖然我喜歡能夠利用現有的類... – Lauren 2010-01-01 02:16:28

+1

比爾,謝謝澄清。它的作品非常漂亮。我仍在致力於將葉子(問題)整合到組合中。一旦完成,我會發布我的最終解決方案。希望能夠讓可能遇到相同情況的其他人受益。 – Lauren 2010-01-05 12:33:12

+0

我很高興它爲你工作。順便說一句,我改變了'initTree()'成爲'public'函數,因爲它必須由Table類調用。你可能已經找到了同樣的東西。 – 2010-01-05 15:38:14