Модель списка смежностей с двумя таблицами
Поэтому я думаю, что моя проблема сводится к двум вопросам:
Как мне построить обходную древовидную структуру в PHP, когда дерево хранится в MySQL (между двумя таблицами), используя подход списка моделей смежности, учитывая производительность?
Каков приемлемый подход к отображению дерева в нужных форматах без дублирования кода обхода и засорения логики операторами if/else и switch?
Ниже более подробно:
Я использую Zend Framework.
Я работаю с анкетой. Он хранится в базе данных MySQL между двумя отдельными таблицами: questions и question_groups. Каждая таблица расширяет соответствующие классы Zend_Db_Table_*. Иерархия представлена с использованием модели списка смежности.
Я понимаю, что проблемы, с которыми я сталкиваюсь, вероятно, связаны с тем, что я вставляю древовидную структуру в СУБД, поэтому я открыт для альтернатив. Тем не менее, я также храню анкеты респондентов и их ответы, так что альтернативные подходы должны были бы поддержать это.
Анкета должна отображаться в различных форматах HTML:
- Как форма для ввода ответов (используя Zend_Form)
- В виде упорядоченного списка (вложенного) с вопросами (и некоторыми группами) в виде ссылок для просмотра ответов по вопросу или группе.
- В виде упорядоченного списка (вложенного) с ответами, прилагаемыми к каждому вопросу.
Вопросы - это листовые узлы, и группы вопросов могут содержать другие группы вопросов и / или вопросы. В совокупности для обработки и отображения требуется чуть более 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()
также на любом из этих промежуточных потомков и рекурсивно выведите дерево в любом формате, который вы хотите.