Lista di adiacenza Modello con due tavoli
-
22-09-2019 - |
Domanda
I pensare il mio problema si riduce a due domande:
-
Come faccio a costruire una struttura ad albero percorribile in PHP quando l'albero è memorizzato in MySQL (tra due tabelle) utilizzando l'approccio Adiacenze Elenco modelli, mantenendo le prestazioni in mente?
-
Che cosa è un approccio mantenibile a visualizzare l'albero nei formati necessari senza duplicare il codice attraversamento e sporcare la logica con if / else e switch?
Di seguito sono riportati ulteriori dettagli:
Sto utilizzando Zend Framework.
Sto lavorando con un questionario. E 'conservato in un database MySQL tra i due distinti tavoli: domande e question_groups. Ciascuna tabella estende classi corrispondenti Zend_Db_Table_ *. La gerarchia è rappresentata utilizzando l'approccio Adiacenze Elenco modelli.
Mi rendo conto che i problemi che sto funzionando in è probabilmente dovuto al fatto che sto ripieno una struttura ad albero in un RDBMS, quindi sono aperto a soluzioni alternative. Tuttavia, sto anche la memorizzazione di intervistati al questionario e le loro risposte in modo da approcci alternativi avrebbero bisogno di sostenere questo.
Il questionario deve essere visualizzato in vari formati HTML:
- Come forma di inserimento risposte (utilizzando Zend_Form)
- Come un elenco ordinato (nidificato) con domande (e alcuni gruppi) come i collegamenti per visualizzare le risposte per domanda o per gruppo.
- Come un elenco ordinato (nidificato) con le risposte allegati ad ogni domanda.
Le domande sono nodi foglia e question_groups possono contenere altri question_groups e / o domande. Combinato, ci sono un po 'più di 100 righe da elaborare e visualizzare.
Attualmente, ho un aiutante vista che fa tutto il processo utilizzando la ricorsione per recuperare i figli di un question_group (una query che esegue un'unione tra le due tabelle: QuestionGroup :: getChildren ($ id)). Inoltre quando la visualizzazione questionario con la risposta domanda di ulteriori due query sono necessari per recuperare il convenuto e la loro risposta ad ogni domanda.
Mentre il tempo di caricamento della pagina non è molto lunga questo approccio si sente male. Ricorsione più più query di database per quasi ogni nodo non mi fanno sentire molto caldo e al sicuro all'interno.
Ho provato ricorsione -less e metodi ricorsivi sulla gamma completa dell'albero restituiti dall'unione di costruire una matrice gerarchica per attraversare e visualizzazione. Tuttavia, sembra che per abbattere in quanto non vi sono duplicati ID dei nodi a causa del fatto che i gruppi e le domande sono memorizzati in tabelle separate. Forse mi manca qualcosa là ...
Al momento, la logica per visualizzare l'albero nei formati elencati sopra è un bel pasticcio. Preferirei non duplicare la logica di attraversamento in tutto il luogo. Tuttavia, i condizionali in tutto il posto non producono il codice più facilmente mantenibile sia. Ho letto su Visitatori, decoratori e alcuni dei iteratori PHP SPL ma mi sento ancora chiaro in che modo sarebbe lavorare tutti insieme con le classi che vengono estendono Zend_Db_Table, Zend_Db_Table_Rowset e Zend_Db_Table_Row. Soprattutto visto che non ho risolto il problema precedente della costruzione della gerarchia dal database. Sarebbe bello per aggiungere nuovi formati di visualizzazione (o modificare quelli esistenti) un po 'facile.
Soluzione
-
La lista di adiacenza tradizionalmente ti dà una colonna
parent_id
in ogni riga che si collega una riga al suo genitore immediato. Ilparent_id
è NULL se la riga è la radice di un albero. Ma questo porta ad eseguire molte query SQL, che è costoso. -
Aggiungi un altro
root_id
colonna in modo ogni riga sa quale albero a cui appartiene. In questo modo è possibile recuperare tutti i nodi di un determinato albero con una singola query SQL. Aggiungere un metodo alla classeTable
per andare a prendere unRowset
da root id dell'albero.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; } }
-
Scrivi una estensione
Zend_Db_Table_Row
classe personalizzata e funzioni di scrittura per recuperare la data genitore riga e anche unaRowset
dei suoi figli. La classeRow
dovrebbe contenere oggetti di dati protetti per fare riferimento al genitore e la matrice dei bambini. Un oggettoRow
può anche avere una funzionegetLevel()
e una funzione pergetAncestorsRowset()
pangrattato.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() {} }
-
Scrivi una
Zend_Db_Table_Rowset
estende classe personalizzata che ha una funzione per scorrere le righe nel set di righe, l'impostazione genitori e bambini riferimenti in modo da poter successivamente li attraversare come un albero. Anche ilRowset
dovrebbe avere una funzione digetRootRow()
.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; } } } }
Ora è possibile chiamare getRootRow()
su un set di righe, e restituisce il nodo principale. Una volta che hai il nodo principale, è possibile chiamare getChildren()
e anello su di loro. Poi si può chiamare getChildren()
anche su uno di questi bambini intermedi, e ricorsivamente un albero di uscita in qualsiasi formato che si desidera.