Модель списка смежности с двумя таблицами
-
22-09-2019 - |
Вопрос
Так что я подумай моя проблема сводится к двум вопросам:
Как мне построить проходимую древовидную структуру в PHP, когда дерево хранится в MySQL (между двумя таблицами), используя подход модели списка смежности, сохраняя при этом производительность?
Каков поддерживаемый подход к отображению дерева в необходимых форматах без дублирования кода обхода и засорения логики операторами if / else и switch?
Ниже приведены более подробные сведения:
Я использую Zend Framework.
Я работаю с вопросником.Он хранится в базе данных MySQL между двумя отдельными таблицами:вопросы и вопросы_группы.Каждая таблица расширяет соответствующие классы Zend_Db_Table_*.Иерархия представлена с использованием подхода модели списка смежности.
Я понимаю, что проблемы, с которыми я сталкиваюсь, скорее всего, связаны с тем фактом, что я внедряю древовидную структуру в СУБД, поэтому я открыт для альтернатив.Однако я также сохраняю респондентов анкеты и их ответы, так что альтернативные подходы должны были бы поддержать это.
Анкета должна быть отображена в различных форматах HTML:
- В качестве формы для ввода ответов (используя Zend_Form)
- В виде упорядоченного списка (вложенного) с вопросами (и некоторые группы) в виде ссылок для просмотра ответов по вопросам или группам.
- В виде упорядоченного списка (вложенного) с ответами, приложенными к каждому вопросу.
Вопросы являются конечными узлами, и question_groups могут содержать другие question_groups и / или вопросы.В совокупности требуется обработать и отобразить чуть более 100 строк.
В настоящее время у меня есть помощник view, который выполняет всю обработку с использованием рекурсии для извлечения дочерних элементов question_group (запрос, который выполняет ОБЪЕДИНЕНИЕ между двумя таблицами:QuestionGroup::getChildren($id)).Кроме того, при отображении анкеты с ответом на вопрос необходимы дополнительные два запроса для получения респондента и его ответа на каждый вопрос.
Хотя время загрузки страницы не очень велико, такой подход кажется неправильным.Рекурсия плюс несколько запросов к базе данных почти для каждого узла не заставляют меня чувствовать себя очень тепло и нечетко внутри.
Я пытался рекурсия-без и рекурсивные методы для полного массива дерева, возвращаемого из ОБЪЕДИНЕНИЯ, для построения иерархического массива для обхода и отображения.Однако это, похоже, не работает, поскольку существуют дублирующиеся идентификаторы узлов из-за того, что группы и вопросы хранятся в отдельных таблицах.Может быть, я чего-то там не понимаю...
В настоящее время логика отображения дерева в форматах, перечисленных выше, довольно запутана.Я бы предпочел не дублировать логику обхода повсюду.Однако повсеместные условные обозначения также не создают наиболее легко поддерживаемый код.Я прочитал о посетителях, декораторах и некоторых итераторах PHP SPL, но мне все еще неясно, как все это будет работать вместе с классами, расширяющими Zend_Db_Table, Zend_Db_Table_Rowset и Zend_Db_Table_Row.Тем более, что я не решил предыдущую проблему построения иерархии из базы данных.Было бы неплохо несколько упростить добавление новых форматов отображения (или изменение существующих).
Решение
Список смежности традиционно предоставляет вам
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
класс должен содержать защищенные объекты данных для ссылки на родительский элемент и массив дочерних элементов.ARow
объект также может иметь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()
также на любом из этих промежуточных дочерних элементов и рекурсивно выводите дерево в любом желаемом формате.