Справка по рекурсии PHP, необходимая для создания древовидной структуры

Вот структура таблицы, которую я имею:

CREATE TABLE menu ( menuid int(11) NOT NULL AUTO_INCREMENT, menuname varchar(100) NOT NULL DEFAULT '', menulink varchar(100) NOT NULL DEFAULT '', menuparentId int(11) NOT NULL DEFAULT '0', menuhasChild smallint(1) NOT NULL DEFAULT '0', menustatus smallint(1) NOT NULL DEFAULT '1', menuorder int(11) NOT NULL DEFAULT '0', PRIMARY KEY (menuid) ) 

Я использую рекурсивную функцию для создания структуры меню из этого и не могу здесь:

 function categoriesTree($id=0){ $s = "SELECT * FROM menu WHERE menuparentId = '".$id."' ORDER BY menuorder, menuid "; $rid = $this->db->query($s)->result_array(); $treeArray = array(); foreach($rid as $row){ $treeArray[$row['menuid']] = $row; if($row['menuhasChild']==1){ $treeArray[$row['menuid']] = $this->categoriesTree(); //results in Fatal error: Maximum function nesting level of '100' reached, aborting! } } retrun $treeArray; } 

Этот метод является частью модели в классе модели CodeIgniter. Есть ли лучший способ создать дерево?

Я думаю, вы должны добавить id в качестве параметра в свой вызов функции.

 $this->categoriesTree($row['menuid']) 

В противном случае вы вызываете функцию точно так же каждый раз.

Да, есть намного лучший способ. Так называемый модифицированный алгоритм обхода дерева предзаказов. Вы можете найти много информации, перейдя по ссылке, и я уверен, что переполнение стека тоже.

Преимущества заключаются в том, что вы можете получить целое поддерево, используя только один запрос. ВЫБОРЫ будут быстрыми, но изменения будут более тяжелыми.

Вот лучший пример. Это скорректированная форма первого ответа.

  function categoriesTree($id=0) { $s = "SELECT * FROM design_menu WHERE menuparentId = '" . $id . "' ORDER BY menuorder, menuid "; $rid = $this->db->query($s)->result_array(); $treeArray = array(); foreach ($rid as $row) { $treeArray[$row['menuid']] = $row; if ($row['menuhasChild'] == 1) { $treeArray[$row['menuname']] = $this->categoriesTree($row['menuid']); //results in Fatal error: Maximum function nesting level of '100' reached, aborting! } } return $treeArray; } 

Линия :

 $treeArray[$row['menuid']] = $this->categoriesTree(); 

должно быть :

 $treeArray[$row['menuid']] = $this->categoriesTree($row['menuid']); 
 <?php require_once ROOT_PATH . '/lib/dao/MySQLClass.php'; require_once ROOT_PATH . '/lib/confs/Conf.php'; /** * Generate HTML for multi-dimensional menu from MySQL database * with ONE QUERY and WITHOUT RECURSION * @author J. Bruni */ //print_r($_SESSION['symfony/user/sfUser/culture']);die; class MenuBuilder { /** * MySQL connection */ var $conn; /** * Menu items */ var $items = array(); /** * HTML contents */ var $html = array(); //var $culture = $_SESSION['symfony/user/sfUser/culture']; var $culture; var $columnName; /** * Create MySQL connection */ function MenuBuilder() { $conf = new Conf(); $db=new MySQLClass($conf); $this->conn = mysql_connect($db->myHost .':'.$db->myHostPort, $db->userName, $db->userPassword); $this->culture=$_SESSION['language']; } /** * Perform MySQL query and return all results */ function fetch_assoc_all( $sql ) { if($this->culture=="en"){ $this->columnName='sm_mnuitem_name'; }else{ $this->columnName='sm_mnuitem_name_'.$this->culture; } //die(print_r($_SESSION)); if($_SESSION['user']=="USR001"){ $query="SELECT sm_mnuitem_id, sm_mnuitem_parent, ".$this->columnName.", sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY sm_mnuitem_parent, sm_mnuitem_position;"; } else{ $query="select * from hs_hr_sm_mnuitem m left join hs_hr_sm_mnucapability c on m.sm_mnuitem_id=c.sm_mnuitem_id left join hs_hr_users u on u.sm_capability_id=c.sm_capability_id where u.id='".$_SESSION['user']."' ORDER BY m.sm_mnuitem_parent, m.sm_mnuitem_position;"; } //$result = mysql_query("SELECT sm_mnuitem_id, sm_mnuitem_parent, ".$this->columnName.", sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY sm_mnuitem_parent, sm_mnuitem_position;",$this->conn); $result = mysql_query($query,$this->conn); if ( !$result ){ return false; } $assoc_all = array(); while( $fetch = mysql_fetch_assoc( $result ) ){ $assoc_all[] = $fetch; } //die(print_r($assoc_all)); mysql_free_result( $result ); return $assoc_all; } /** * Get all menu items from database */ function get_menu_items() { // Change the field names and the table name in the query below to match tour needs $sql = 'SELECT sm_mnuitem_id, sm_mnuitem_parent, sm_mnuitem_name, sm_mnuitem_webpage_url, sm_mnuitem_position FROM hs_hr_sm_mnuitem ORDER BY s_mnuitem_parent, sm_mnuitem_position;'; return $this->fetch_assoc_all( $sql ); } /** * Build the HTML for the menu */ function get_menu_html( $root_id = 0 ) { $this->html = array(); $this->items = $this->get_menu_items(); //print_r($this->items);die(""); foreach ( $this->items as $item ) $children[$item['sm_mnuitem_parent']][] = $item; // loop will be false if the root has no children (ie, an empty menu!) $loop = !empty( $children[$root_id] ); // initializing $parent as the root $parent = $root_id; $parent_stack = array(); // HTML wrapper for the menu (open) //$this->html[] = '<div>'; $this->html[] = '<ul id="qm0" class="qmmc">'; while ( $loop && ( ( $option = each( $children[$parent] ) ) || ( $parent > $root_id ) ) ) { if ( $option === false ) { $parent = array_pop( $parent_stack ); // HTML for menu item containing childrens (close) $this->html[] = str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 ) . '</ul>'; $this->html[] = str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 - 1 ) . '</li>'; } elseif ( !empty( $children[$option['value']['sm_mnuitem_id']] ) ) { $tab = str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 - 1 ); // HTML for menu item containing childrens (open) $url=""; if($option['value']['sm_mnuitem_webpage_url']=="#"){ $url="javascript:void(0);"; }else{ $url=$option['value']['sm_mnuitem_webpage_url']; } $this->html[] = sprintf( '%1$s<li><a class="qmparent" href="%2$s">%3$s</a>', $tab, // %1$s = tabulation //$option['value']['sm_mnuitem_webpage_url'], // %2$s = link (URL) $url, $option['value'][$this->columnName] // %3$s = title ); $this->html[] = $tab . "\t" . '<ul>'; array_push( $parent_stack, $option['value']['sm_mnuitem_parent'] ); $parent = $option['value']['sm_mnuitem_id']; } else{ // HTML for menu item with no children (aka "leaf") if($_SESSION['user']!="USR001"){ if($option['value']['sm_mnuitem_webpage_url']!="#"){ $this->html[] = sprintf( '%1$s<li><a target="rightMenu" href="%2$s">%3$s</a></li>', str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 - 1 ), // %1$s = tabulation $option['value']['sm_mnuitem_webpage_url'], // %2$s = link (URL) $option['value'][$this->columnName] // %3$s = title ); } }else{ $this->html[] = sprintf( '%1$s<li><a target="rightMenu" href="%2$s">%3$s</a></li>', str_repeat( "\t", ( count( $parent_stack ) + 1 ) * 2 - 1 ), // %1$s = tabulation $option['value']['sm_mnuitem_webpage_url'], // %2$s = link (URL) $option['value'][$this->columnName] // %3$s = title ); } } } // HTML wrapper for the menu (close) $this->html[] = '</ul>'; //$this->html[] = '</div>'; return implode( "\r\n", $this->html ); } } ?> 

Создание меню с использованием древовидных структур с родительскими / дочерними отношениями, основанными на реляционной базе данных, очень громоздко. Реляционные базы данных ужасны для древовидных структур. Они требуют, чтобы вы писали много бизнес-логики, чтобы представлять ваши данные в читаемом формате. Чтобы обновить меню с дополнительной функциональностью, необходимо добавить в этот рекурсивный цикл … он может стать действительно волосатым в зависимости от того, насколько сложным вы хотите, чтобы ваше меню стало. Не говоря уже о том, что в конечном итоге вы захотите сфотографировать все это, потому что цикл становится довольно дорогостоящим при тяжелых нагрузках. Подумайте об этом, если у вас есть 5 пунктов меню верхнего уровня, 2 ребенка и каждый ребенок, у которых есть n детей, вы будете запускать 16 SQL-операторов.

Могу ли я предложить другое решение: JSON . Раньше у меня была таблица меню, и теперь я просто храню представление JSON в базе данных SQL (хотя даже это можно кэшировать в памяти / файловой системе). Меню JSON гораздо более компактно с точки зрения пространства, логично просто читать и не требует возиться с родительскими и дочерними идентификаторами. Он работает намного лучше с PHP (json_encode / decode), превращая меню в собственный массив. Как и с Javascript, что важно, если, например, вы делаете ajax-вызовы для изменения порядка меню в своем приложении. Иерархические древовидные структуры – вот что такое JSON. Это также устраняет необходимость отслеживать ваш «menuorder» (потому что порядок массива указан по существу)

Примерный формат меню выглядит следующим образом:

 { ["en": "Home", "fr": "Accueil"], ["en": "Settings", "fr": "Paramètres", "child": { ["en": "Email", "fr": "Email", "role": "EmailUser"] } } 

Как вы видите, он предлагает очень удобную функциональность, такую ​​как «роль», связанная с пунктом меню. Добавление такого типа функций не требует нового рекурсивного кода или изменений в вашей схеме SQL. Это действительно намного более гибко.

Таким образом, на самом деле не отвечая на этот вопрос, но, надеюсь, вы получите некоторые советы / понимание того, что я считаю лучшим решением этой проблемы.