3

したがって、私の問題は2つの質問に要約されると思います。

  1. パフォーマンスを考慮しながら隣接リスト モデル アプローチを使用してツリーが MySQL (2 つのテーブル間) に格納されている場合、PHP でトラバース可能なツリー構造を構築するにはどうすればよいですか?

  2. トラバーサル コードを複製したり、if/else ステートメントや switch ステートメントでロジックを散らかしたりせずに、必要な形式でツリーを表示するための保守可能なアプローチは何ですか?

詳細は以下のとおりです。

Zend フレームワークを使用しています。

問診票を扱っています。これは、質問と質問グループの 2 つの別個のテーブル間の MySQL データベースに保存されます。各テーブルは、適切な Zend_Db_Table_* クラスを拡張します。階層は、隣接リスト モデル アプローチを使用して表されます。

私が直面している問題は、ツリー構造を RDBMS に詰め込んでいるという事実が原因である可能性が高いことを認識しているため、代替手段を受け入れています。ただし、アンケートの回答者とその回答も保存しているため、別のアプローチでそれをサポートする必要があります。

アンケートは、さまざまな HTML 形式で表示する必要があります。

  1. 回答を入力するためのフォームとして (Zend_Form を使用)
  2. 質問別またはグループ別の回答を表示するためのリンクとして、質問 (およびいくつかのグループ) を含む順序付けられた (ネストされた) リストとして。
  3. 各質問に回答が追加された順序付きリスト (ネストされた) として。

質問はリーフ ノードであり、question_groups には他の question_group や質問を含めることができます。組み合わせると、処理および表示する行が 100 行を少し超えます。

現在、再帰を使用してすべての処理を行い、question_group の子 (2 つのテーブル間で UNION を実行するクエリ: QuestionGroup::getChildren($id)) を取得するビュー ヘルパーがあります。さらに、質問の回答とともにアンケートを表示する場合、各質問に対する回答者とその回答を取得するために、追加の 2 つのクエリが必要です。

ページの読み込み時間はそれほど長くはありませんが、このアプローチは間違っているように感じます. ほとんどすべてのノードに対して再帰と複数のデータベース クエリを実行しても、内部が非常に暖かくぼんやりしているとは感じません。

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 クエリで特定のツリーのすべてのノードを取得できます。ツリーのルート ID でTableをフェッチするメソッドをクラスに追加します。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ます。RowgetLevel()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 に答える