3

Так что я думаю мой вопрос сводится к двум вопросам:смежности Список модели с двумя столами

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

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

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

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

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

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

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

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

Вопросы - это листовые узлы, а вопросы_группы могут содержать другие вопросы_группы и/или вопросы. Комбинированный, для обработки и отображения имеется более 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, чтобы получить 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 своих детей. Класс должен содержать защищенные объекты данных для ссылки на родительский элемент и массив дочерних элементов. Объект также может иметь функцию 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

+1

Билл, спасибо за разъяснение. Он работает красиво. Я все еще работаю над интеграцией листьев (вопросов) в микс. Когда я закончу, я отправлю свое окончательное решение. Хотелось бы надеяться, что это принесет пользу тому, кто может столкнуться с тем же сценарием. – Lauren

+0

Я рад, что это сработало для вас. Btw, я изменил 'initTree()' выше как функцию 'public', потому что он должен быть вызван классом Table. Вероятно, вы уже нашли то же самое. –