Ver Mensaje Individual
  #2 (permalink)  
Antiguo 27/08/2013, 10:04
Nisrokh
 
Fecha de Ingreso: septiembre-2009
Ubicación: Neuquén
Mensajes: 142
Antigüedad: 14 años, 7 meses
Puntos: 12
Respuesta: [APORTE] Estructura de árbol desde de base de datos

La función table_tree():
Código PHP:
Ver original
  1. <?php
  2.  
  3. /**
  4.  * @author Diego P. M. Baltar <[email protected]>
  5.  * @copyright (c) 2013
  6.  */
  7.  
  8. /**
  9.  * Construye una estructura de árbol desde una tabla, donde cada fila tiene un
  10.  * campo con su id único, y un campo indicando el id de su padre.
  11.  *
  12.  * La estructura de las opciones:
  13.  * <pre>
  14.  * array(
  15.  *     // Claves de entrada
  16.  *     'id_key'        => 'id',
  17.  *     'parent_id_key' => 'parent_id',
  18.  *     // Claves de salida
  19.  *     'value_key'     => 'value',
  20.  *     'parent_key'    => 'parent',
  21.  *     'children_key'  => 'children',
  22.  *     'nodes_key'     => 'nodes',
  23.  *     'orphans_key'   => 'orphans',
  24.  *     // Comparador para ordenar los nodos
  25.  *     'comparator'    => SORT_REGULAR // O un "callable" usado por usort()
  26.  * )
  27.  * </pre>
  28.  *
  29.  * @todo Detectar errores recursivos.
  30.  * @todo Detectar ubicaciones múltiples (en un mísmo grupo).
  31.  *
  32.  * @param array $table La tabla de datos.
  33.  * @param bool $sort Indica si los nodos deben ser ordenados, o no.
  34.  * @param array $options Opciones de entrada y salida.
  35.  * @return array
  36.  */
  37. function table_tree($table, $sort = false, $options = array())
  38. {
  39.     if (!is_array($table) || empty($table)) {
  40.         return array();
  41.     }
  42.    
  43.     // Opciones por defecto
  44.     static $default_options = array(
  45.         'id_key'        => 'id',
  46.         'parent_id_key' => 'parent_id',
  47.         'value_key'     => 'value',
  48.         'parent_key'    => 'parent',
  49.         'children_key'  => 'children',
  50.         'nodes_key'     => 'nodes',
  51.         'orphans_key'   => 'orphans',
  52.         'comparator'    => SORT_REGULAR
  53.     );
  54.    
  55.     // Establecer opciones que no fueron especificadas
  56.     if (empty($options)) {
  57.         $options = $default_options;
  58.     } else {
  59.         foreach ($default_options as $name => $value) {
  60.             if (!isset($options[$name])) {
  61.                 $options[$name] = $value;
  62.             }
  63.         }
  64.     }
  65.    
  66.     // Acceso corto a las claves de entrada
  67.     $id_key = $options['id_key'];
  68.     $parent_id_key = $options['parent_id_key'];
  69.    
  70.     // Acceso corto a las claves de salida
  71.     $value_key = $options['value_key'];
  72.     $parent_key = $options['parent_key'];
  73.     $children_key = $options['children_key'];
  74.     $nodes_key = $options['nodes_key'];
  75.     $orphans_key = $options['orphans_key'];
  76.    
  77.     // El árbol final (sigue la estructura de un nodo, más 2 items)
  78.     $tree = array(
  79.         $value_key    => null,
  80.         $parent_key   => null,
  81.         $children_key => array(),
  82.         $nodes_key    => array(),
  83.         $orphans_key  => array()
  84.     );
  85.    
  86.     // Datos temporales
  87.     $nodes = &$tree[$nodes_key];     // Mapa de nodos (ref. nodos en el árbol)
  88.     $orphans = &$tree[$orphans_key]; // Nodos que no se encontro su padre
  89.     $on_hold = array();              // Lista de nodos en espera de su padre
  90.     $on_hold_children = array();     // Nodos en lista de espera
  91.     $on_hold_children_map = array(); // Mapa de nodos en lista de espera
  92.     $sortable_children = array();    // Nodos que deberían ser ordenados
  93.    
  94.     // Agregar el árbol al mapa de nodos bajo el id "0"
  95.     $nodes[0] = &$tree;
  96.    
  97.     // Construír el árbol
  98.     foreach ($table as $index => $row) {
  99.         // Forzar los datos hacia un array, sólo para obtener los ids
  100.         $row = (array) $row;
  101.         $node_id = $row[$id_key];
  102.         $node_parent_id = $row[$parent_id_key];
  103.        
  104.         // Ignorar filas con id = "0"
  105.         if ((int) $node_id === 0) {
  106.             continue;
  107.         }
  108.        
  109.         // Crear nodo
  110.         $new_node = array(
  111.             $value_key => $table[$index],
  112.             $parent_key => null,
  113.             $children_key => array()
  114.         );
  115.        
  116.         // Indica si el padre del nodo actual está en espera
  117.         $parent_is_on_hold = false;
  118.        
  119.         // ¿Se ha encontrado ya el nodo padre de este nodo?
  120.         if (isset($nodes[$node_parent_id])) {
  121.             $parent = &$nodes[$node_parent_id];
  122.         } elseif (isset($on_hold[$node_parent_id])) {
  123.             $on_hold_key = $on_hold[$node_parent_id];
  124.             $parent = &$on_hold_children_map[$on_hold_key][$node_parent_id];
  125.             $parent_is_on_hold = true;
  126.         } elseif (isset($parent)) {
  127.             unset($parent);
  128.         }
  129.        
  130.         // Dar nodo a su padre (si éste ha sido encontrado)
  131.         if (isset($parent)) {
  132.             $parent_children = &$parent[$children_key];
  133.             $parent_children[] = $new_node;
  134.             $parent_children_count = count($parent_children);
  135.             $node = &$parent_children[$parent_children_count - 1];
  136.            
  137.             // Establecer el nodo padre
  138.             $node[$parent_key] = &$parent;
  139.            
  140.             if ($parent_is_on_hold) {
  141.                 // Agregar nodo al mapa de nodos en espera
  142.                 $on_hold[$node_id] = $on_hold[$node_parent_id];
  143.                 $on_hold_children_map[$on_hold[$node_id]][$node_id] = &$node;
  144.             } else {
  145.                 // Agregar nodo al mapa final
  146.                 $nodes[$node_id] = &$node;
  147.             }
  148.            
  149.             // Agregar nodos hijos para ordenar, sólo si es necesario
  150.             if ($sort && $parent_children_count > 1
  151.                     && !isset($sortable_children[$node_parent_id])) {
  152.                 $sortable_children[$node_parent_id] = &$parent_children;
  153.             }
  154.         } else {
  155.             // Agregar nodo a la lista de espera, hasta que aparezca su padre
  156.             $on_hold[$node_id] = $node_parent_id;
  157.            
  158.             if (!isset($on_hold_children[$node_parent_id])) {
  159.                 $on_hold_children[$node_parent_id] = array();
  160.             }
  161.            
  162.             // Poner el nodo en espera
  163.             $on_hold_children[$node_parent_id][] = $new_node;
  164.             $node = &$on_hold_children[$node_parent_id]
  165.                 [count($on_hold_children[$node_parent_id]) - 1];
  166.            
  167.             if (!isset($on_hold_children_map[$node_parent_id])) {
  168.                 $on_hold_children_map[$node_parent_id] = array();
  169.             }
  170.            
  171.             // Agregar nodo al mapa de lista de espera
  172.             $on_hold_children_map[$node_parent_id][$node_id] = &$node;
  173.         }
  174.        
  175.         // ¿És este nodo el padre de alguno de los nodos en espera?
  176.         if (isset($on_hold_children[$node_id])) {
  177.             // Agregar nodo al árbol final, actualizar referencias, y establecer
  178.             // su nodo padre (manipular 1 nodo manualmente)
  179.             if (count($on_hold_children[$node_id]) < 2) {
  180.                 $on_hold_node = &$on_hold_children[$node_id][0];
  181.                 $on_hold_node[$parent_key] = &$node;
  182.                 $node[$children_key][] = $on_hold_node;
  183.                 $on_hold_node_value = (array) $on_hold_node[$value_key];
  184.                 $on_hold_node_id = $on_hold_node_value[$id_key];
  185.                 $on_hold_children_map[$node_id][$on_hold_node_id]
  186.                     = &$node[$children_key][count($node[$children_key]) - 1];
  187.             } else {
  188.                 foreach ($on_hold_children[$node_id] as &$on_hold_node) {
  189.                     $on_hold_node[$parent_key] = &$node;
  190.                     $node[$children_key][] = $on_hold_node;
  191.                     $on_hold_node_value = (array) $on_hold_node[$value_key];
  192.                     $on_hold_node_id = $on_hold_node_value[$id_key];
  193.                     $on_hold_children_map[$node_id][$on_hold_node_id]
  194.                         = &$node[$children_key]
  195.                             [count($node[$children_key]) - 1];
  196.                 }
  197.             }
  198.            
  199.             // Agregar nodos al mapa final
  200.             $tree[$nodes_key]+= $on_hold_children_map[$node_id];
  201.            
  202.             // Borrar referencias hacia la lista de espera
  203.             unset($on_hold_children[$node_id]);
  204.             unset($on_hold_children_map[$node_id]);
  205.            
  206.             // Agregar nodos hijos para ordenar, sólo si es necesario
  207.             if ($sort && count($node[$children_key]) > 1
  208.                     && !isset($sortable_children[$node_id])) {
  209.                 $sortable_children[$node_id] = &$node[$children_key];
  210.             }
  211.         }
  212.     }
  213.    
  214.     // Obtener los nodos "huérfanos", donde su parent_id != 0 pero de todas
  215.     // maneras no ha sido encontrado
  216.     if (!empty($on_hold_children)) {
  217.         $orphans = $on_hold_children;
  218.     }
  219.    
  220.     // Ordernar los nodos, sólo si es necesario
  221.     if (!empty($sortable_children)) {
  222.         // Obtener el comparador establecido
  223.         $comparator = $options['comparator'];
  224.        
  225.         foreach ($sortable_children as &$unsorted_children) {
  226.             if (is_callable($comparator)) {
  227.                 usort($unsorted_children, $comparator);
  228.             } else {
  229.                 sort($unsorted_children, $comparator);
  230.             }
  231.         }
  232.     }
  233.    
  234.     return $tree;
  235. }

Ahora, un simple ejemplo de como utilizarlo con una base de datos:

Código PHP:
Ver original
  1. // MySQL info.
  2. define('MYSQL_HOSTNAME', 'localhost');
  3. define('MYSQL_USERNAME', 'root');
  4. define('MYSQL_PASSWORD', '123456');
  5. define('MYSQL_DATABASE', 'test');
  6.  
  7. // Crear conexión
  8. $sql_connection = mysqli_connect(
  9.     MYSQL_HOSTNAME,
  10.     MYSQL_USERNAME,
  11.     MYSQL_PASSWORD,
  12.     MYSQL_DATABASE
  13. );
  14.  
  15. if (!$sql_connection) {
  16.     exit(sprintf(
  17.         'Database connection error: (%s) %s',
  18.         mysqli_connect_errno(),
  19.         mysqli_connect_error()
  20.     ));
  21. } else {
  22.     mysqli_query($sql_connection, 'SET NAMES \'utf8\'');
  23. }
  24.  
  25. // Obtener los datos del grupo "0"
  26. $sql_query = '
  27.     SELECT
  28.         links.id,
  29.         links.name,
  30.         links_hierarchy.group_id,
  31.         links_hierarchy.parent_id,
  32.         links_hierarchy.order_no
  33.     FROM links
  34.     INNER JOIN links_hierarchy
  35.         ON links_hierarchy.link_id = links.id
  36.     WHERE links_hierarchy.group_id = 0
  37.         AND links.id > 0
  38. ';
  39.  
  40. // Ejecutar consulta
  41. $sql_query_result = mysqli_query($sql_connection, $sql_query);
  42. $table = array();
  43.  
  44. while ($row = mysqli_fetch_assoc($sql_query_result)) {
  45.     $table[] = $row;
  46. }
  47.  
  48. mysqli_free_result($sql_query_result);
  49. mysqli_close($sql_connection);
  50.  
  51. // Aplicar shuffle() sólo para demostrarles que no importa el orden, el árbol
  52. // siempre se construirá normalmente
  53. shuffle($table);
  54.  
  55. // Función para el ordenamiento por el campo "name"
  56. function compare_nodes($a, $b) {
  57.     $a = $a['value']['name'];
  58.     $b = $b['value']['name'];
  59.    
  60.     return strcasecmp($a, $b);
  61. }
  62.  
  63. // Crear árbol
  64. $tree = table_tree(
  65.     $table,
  66.     true,
  67.     array(
  68.         'comparator' => 'compare_nodes'
  69.     )
  70. );
  71.  
  72. // Obtener el nodo con el id "24"
  73. $node = $tree['nodes'][24];
  74. // Obtener la referencia al nodo padre
  75. $parent_node = $node['parent'];
  76. // Obtener el nodo hijo no. 3, suponiendo que existe
  77. $child_node_3 = $node['children'][2];
  78. // Obtener el valor del nodo (la fila original, obtenida de la consulta SQL)
  79. $node_value = $node['value'];
  80.  
  81. // Los nombres de las claves, serán distintas si las especificamos en el
  82. // parámetro $options, pero estas son utilizadas por defecto

(Continúa en comentarios)
__________________
Amigos de Foros del Web: seamos más solidarios. ¡No dejemos que un tema se valla al final de las páginas con 0 (cero) respuestas! ¡Gracias por su ayuda! :-)