Модель списка смежностей с двумя таблицами

Поэтому я думаю, что моя проблема сводится к двум вопросам:

  1. Как мне построить обходную древовидную структуру в PHP, когда дерево хранится в MySQL (между двумя таблицами), используя подход списка моделей смежности, учитывая производительность?

  2. Каков приемлемый подход к отображению дерева в нужных форматах без дублирования кода обхода и засорения логики операторами if/else и switch?

Ниже более подробно:

Я использую Zend Framework.

Я работаю с анкетой. Он хранится в базе данных MySQL между двумя отдельными таблицами: questions и question_groups. Каждая таблица расширяет соответствующие классы Zend_Db_Table_*. Иерархия представлена ​​с использованием модели списка смежности.

Я понимаю, что проблемы, с которыми я сталкиваюсь, вероятно, связаны с тем, что я вставляю древовидную структуру в СУБД, поэтому я открыт для альтернатив. Тем не менее, я также храню анкеты респондентов и их ответы, так что альтернативные подходы должны были бы поддержать это.

Анкета должна отображаться в различных форматах HTML:

  1. Как форма для ввода ответов (используя Zend_Form)
  2. В виде упорядоченного списка (вложенного) с вопросами (и некоторыми группами) в виде ссылок для просмотра ответов по вопросу или группе.
  3. В виде упорядоченного списка (вложенного) с ответами, прилагаемыми к каждому вопросу.

Вопросы - это листовые узлы, и группы вопросов могут содержать другие группы вопросов и / или вопросы. В совокупности для обработки и отображения требуется чуть более 100 строк.

В настоящее время у меня есть помощник вида, который выполняет всю обработку с использованием рекурсии для извлечения потомков группы question_group (запрос, который выполняет UNION между двумя таблицами: QuestionGroup::getChildren($id)). Кроме того, при отображении вопросника с ответом на вопрос необходимы два дополнительных запроса для получения ответа респондента и его ответа на каждый вопрос.

Хотя время загрузки страницы не очень велико, такой подход кажется неправильным. Рекурсия плюс множественные запросы к базе данных почти для каждого узла не заставляют меня чувствовать себя очень тепло и нечетко внутри.

Я пытался использовать рекурсивные и рекурсивные методы для массива полного дерева, возвращенного из UNION, для построения иерархического массива для его обхода и отображения. Однако, похоже, что это не работает, поскольку есть дублированные идентификаторы узлов из-за того, что группы и вопросы хранятся в отдельных таблицах. Может быть, я что-то там упускаю...

В настоящее время логика отображения дерева в перечисленных выше форматах довольно запутанная. Я бы предпочел не дублировать логику обхода повсюду. Однако повсюду условные выражения не дают и самого легко поддерживаемого кода. Я читал о Посетителях, Декораторах и некоторых итераторах PHP SPL, но мне все еще неясно, как все это будет работать вместе с классами, расширяющими Zend_Db_Table, Zend_Db_Table_Rowset и Zend_Db_Table_Row. Тем более, что я не решил предыдущую проблему построения иерархии из базы данных. Было бы неплохо добавить новые форматы отображения (или изменить существующие) несколько легко.

1 ответ

Решение
  • Список Смежности традиционно дает вам parent_id столбец в каждой строке, который связывает строку с ее непосредственным родителем. parent_id NULL, если строка является корнем дерева. Но это приводит к тому, что вы запускаете много SQL-запросов, что дорого.

  • Добавить другой столбец root_id поэтому каждая строка знает, к какому дереву она принадлежит. Таким образом, вы можете получить все узлы данного дерева с помощью одного SQL-запроса. Добавьте метод к вашему 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 Класс должен содержать защищенные объекты данных для ссылки на родительский элемент и массив дочерних элементов. 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() также на любом из этих промежуточных потомков и рекурсивно выведите дерево в любом формате, который вы хотите.

Другие вопросы по тегам