Вопрос

Так что я подумай моя проблема сводится к двум вопросам:

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

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

Ниже приведены более подробные сведения:

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top