Sistema de árbol para manejar categorias de contenido

Sistema de arbol para categorias en php y mysql

Sistema de árbol para manejar categorias de contenido

Todos los que trabajamos programación en algún momento nos encontramos con una situación característica en donde necesitamos realizar una búsqueda tipo árbol de categorias de acuerdo a nuestras necesidades y la estructura de nuestra paginas y/o aplicación donde manejamos productos, perfiles, títulos o cualquier otro tipo de datos que por su naturaleza esta ligada a un registro padre o pivote.

PHP no es la excepción. La estructura de árbol lineal es muy frecuente y es un recurso muy sencillo para manejar por ejemplo categorías de un contenido especifico. Para esto es necesario realizar consultas en la base de datos donde la tabla relacional de nuestras categorías solo muestra una rama, es decir, que se maneja de manera escalar.

Para solucionar este tipo de búsqueda podemos utilizar una función php bastante sencilla que realiza una “búsqueda profunda”, la cual nos permite desplegar el árbol completo de una categoría en particular hasta llegar al ultimo registro de la cadena.

Por ejemplo, supongamos que tenemos una tabla en nuestra base de datos que se llame categorías y la utilizamos para agrupar los contenidos o artículos por medio de estas:

CREATE TABLE IF NOT EXISTS `categories` (
`category_id` int(11) NOT NULL auto_increment,
`category_parent_id` int(11) NOT NULL default '0',
`category_name` varchar(60) default NULL,
`category_status` tinyint(1) NOT NULL default '0',
PRIMARY KEY  (`category_id`),
KEY `category_parent_id` (`category_parent_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ;

Vamos a insertar valores o categorias en forma de arbol, es decir que cada categoria puede o no tener una categoria primaria o madre:

INSERT INTO `categories` (`category_id`, `category_parent_id`, `category_name`, `category_status`) VALUES
(1, 0, 'Noticas', 1),
(2, 0, 'Videos', 1),
(3, 0, 'Clasificados', 1),
(4, 0, 'Directorio', 1),
(5, 1, 'Nacionales', 1),
(6, 1, 'Internacionales', 1),
(7, 5, 'Deportes', 1),
(8, 7, 'Baseball', 1),
(9, 1, 'Pais', 1);

En esta tabla tenemos valores a los que llamamos categorías las cuales están o no relacionadas entre si en algunos casos. Por ejemplo,  “Noticias”, es una categoría primaria ya que no tiene madre o “parent_id”; pero “Nacionales” e “Internacionales” están por debajo de “Noticias”. De igual forma, “Deportes” es a su vez una sub-categoría de “Nacionales” y “Baseball” es a su vez una sub-categoría de esta. En una estructura de diagrama se vería de la siguiente forma:

Tomando como ejemplo “Noticias” cuyo id es 1 tendríamos lo siguiente:

Noticias
|– Nacionales
|          |—Deportes
|                        |–Baseball
|
|– Internacionales
|– País

Para obtener todos los registros pertenecientes a “Noticias”, tendríamos que buscar por todos los registros cuya categoría sea ella misma, y también las categorías debajo de esta sin importar la ultima rama de esta estructura de árbol.

Para lograr esto, podemos hacerlo a manera manual, es decir utilizando la clausula “IN” en nuestro SELECT y colocando manualmente los IDs de las categorías que “sabemos pertenecen a “Noticias”. Por ejemplo:

SELECT * FROM categories WHERE category_id IN ('1','5','6','7','8','9');

Si bien podemos hacerlo de esta forma, imaginemos que tenemos que utilizar el query en diferentes ocasiones y, que si es necesario, debemos buscar una sub-categoría y sus subsecuentes hijos, tendríamos que realizar cambios manuales. Es decir, si quisiéramos buscar solo las noticias “Nacionales”, entonces tendríamos que modificar la consulta de la siguiente forma:

SELECT * FROM categories WHERE category_id IN ('5','7','8');

Ahora, imaginemos que necesitamos hacerlo de forma dinámica, lo cual podría plantear un gran problema ya que no sabemos cual seria la categoría de 1er nivel que deseamos consultar con sus subsecuentes ramas o hijos. Complicado verdad?

Descuiden, existe una manera sencilla de lograr esto. Primero necesitaremos crear una función que realice la búsquedas dinámicas  a partir de un ID el cual seria nuestro ID padre, madre o pivote. De esta manera podemos obtener las sub-categorías o ramas hasta llegar a la ultima rama o categoría final de la estructura de árbol.

Lo primero es, tener nuestro query primario o el cual queremos reutilizar cuantas veces sea necesario. que es el mismo query o consulta que ya vimos, pero que ahora ya lo tendremos que indicar los IDs de manera manual sino, que nuestra función de ayuda hará este trabajo por nosotros.

SELECT * FROM categories WHERE category_id IN ();

Ahora creamos nuestra función en nuestro archivo común de funciones o librería para hacer include de este y utilizar nuestra función cuantas veces sea necesario:

Vamos a llamar a nuestra función con el nombre “deepscan”, ya que eso es lo que hace. Realiza una búsqueda a profundidad de la relación de arbol de un ID especifico.

<?php
function deepscan($id){

    $sql_1 = "SELECT category_id FROM categories WHERE category_parent_id ='".$id."'";
    $result_1 = $db->query($sql_1);
    $a = $db->fetch_array($result_1);
    $sql_2 = "SELECT category_id FROM categories WHERE category_parent_id ='".$id."'";
    $result_2 = $db->query($sql_2);

    $array = $id;

    if (mysql_num_rows($result_2) != '0'){

        while ($a2 = $db->fetch_array($result_2)) {
            echo $a['category_id'].' - '.$a2['category_id'].'<br>';
            flush();
            $que[] = $a2['category_id'];
        }
    }

    if($que){
        $key = array_search($level,$que);

        if ($key != '') { unset($que[$key]); }

        foreach ($que as $scan){
            deepscan($scan);
        }
    }

}    
?>

Si bien notan las consultas $sql_1 y $sql_2 son exactamente la misma. La idea es utilizarlos como un anillo o bucle para realizar la consulta escalar o para ir descendiendo según la profundidad de la categoría hasta llegar a la ultima categoría del árbol perteneciente al ID indicado.

Esto nos mostrara como resultado el id de la categoria primaria mas las sub-categorias subsiguientes indicandonos a que parent_id pertenece y siguiendo el oreden hereditario. Algo así,

1-1
5-1
6-1
7-5
8-7

La columna de la izquierda nos muestra todos los id dentro del arbol, y la de la derecha nos indica a que parent_id pertence la categoria mostrada. Esto es un debug, para que puedan ver como funciona la funcion.

Ahora si queremos utilizar esta funcion en nuestro query primario, necesitaremos hacer unos cambios:

function deepscan($id){

$sql_1 = "SELECT category_id FROM categories WHERE category_parent_id ='".$id."'";
$result_1 = $db->query($sql_1);
$a = $db->fetch_array($result_1);
$sql_2 = "SELECT category_id FROM categories WHERE category_parent_id ='".$id."'";
$result_2 = $db->query($sql_2);

$array = $id;

if (mysql_num_rows($result_2) != '0'){
while ($a2 = $db->fetch_array($result_2))        {

//Comentamos el print out
//echo $a[category_id'].' - '.$a2['category_id'].'<br>';

//alimentamos la variable $array con las categorías encontradas
$array .= "','".$a2['category_id'];

flush();
$que[] = $a2['category_id'];
}
}

if($que){
$key = array_search($level,$que);

if ($key != '') { unset($que[$key]); }

foreach ($que as $scan){

//Volvemos y alimentamos la variable $array ahora con las categorias hijas de la ya encontrada y que ya esta incluida en la variable array
$array .= "','".deepscan($scan);
}
}
return $array;
}

Ahora tenemos el ID primario o a partir del cual queremos realizar un búsqueda profunda de la ramificación del árbol a partir de esta alimentando la función deepscan para iniciar la búsqueda. Ahora solo tenemos que combinar nuestra consulta con la funcion para que nos muestre todos los registro que pertenecen a la categoria indicada incluyendo los registros de la ramificación de esta hasta llegar a la ultima rama o categoria hijo al final del arbol.

Por tanto, al final tenemos algo como esto:

$sql = "SELECT * FROM categories WHERE category_id IN ('".deepscan('1')."'")";

Nótese que he colocado comillas simples antes y despues de colocar la llamada a la funcion deepscan(). Esto es partiendo de que estamos utilizando nuestra consulta dentro de los tags de php <?php  … ?> y que la consulta es un string como generalmente se utiliza y .deepscan(‘1’). entre los puntos es un llamado a la funcion como parte de una variable funciona de php y cuyo resultado lo queremos como parte del string, y no la funcion misma.

Con esto podemos forma dinámica  consultar todos los registros pertenecientes a una categoría y las categorías por debajo de esta. Este función es muy útil para trabajar portales, sistema de ventas, inventarios, en fin, cualquier sistema basado en PHP+Mysql que requiera de este tipo de presentación de información relacional.

Espero les sea de utilidad y recuerden CODE IS POETRY!

2 Replies to “Sistema de árbol para manejar categorias de contenido

  1. Hey! Me vino muy bien tu código para hacer un árbol de categorías y subcategorías. Pero echándole un vistazo hay un pequeño error en la última funcion en:

    $a2[‘agent_id’];

    y

    $que[] = $a2[‘agent_id’];

    donde habría que sustituir el “agent_id” por “category_id” para que funcione bien en el ejemplo que pusiste.

    Gracias crack!

Comments are closed.