1

I would like to write a recursive PHP function to retrive all the children for the specified category. I tried the one described here but it didn't output what I have expected.

My categories table looks like this:

CREATE TABLE `categories` (
 `category_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
 `category_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
 `category_slug` varchar(256) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
 `category_parent` smallint(5) unsigned NOT NULL DEFAULT '0',
 `category_description_ro` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
 `category_description_en` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
 PRIMARY KEY (`category_id`)
) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=latin1

Bellow is an example of data in the table:

category id | category name | category_parent

1            Categoria 1            0        
2            Categoria 2            0        
3            Categoria 3            0        
4            Categoria 1.1          1        
5            Categoria 1.2          1        
6            Categoria 1.3          1        
7            Categoria 1.1.2        4 

Thanks.

Community
  • 1
  • 1
Psyche
  • 8,513
  • 20
  • 70
  • 85
  • 1
    Note that this is a rather inefficient way of storing/retrieving a hierarchy in the database. You might want to go with a [materialised path](http://www.google.com/search?q=materialised+path) scheme instead. – troelskn Jul 26 '10 at 10:14
  • Inefficient yes, but it is possible to accomplish this task with one query + php array manipulation. – Salman A Jul 26 '10 at 10:21
  • Another reasonable scheme is the nested sets or nested intervals scheme. – Gumbo Jul 26 '10 at 10:24
  • @troelskn, @Gumbo: unfortunately I can't change the structure. There are thousands of products already added in these categories. – Psyche Jul 26 '10 at 10:38
  • Changing the structure can also be done reasonably simply. We use something similar to this one (http://articles.sitepoint.com/article/hierarchical-data-database) and I had no trouble migrating all our existing data. The end result is one single query that retrieves all children without any PHP manipulation - it lets the DB do what it's good at without bogging down your web server. – El Yobo Jul 26 '10 at 11:08
  • The only change that has to be made, is to add an extra column. It's basically a sort of index of your data. *Materialised path* is especially good if you have few write operations, but many reads. Otherwise, the more complex *Nested set* scheme may be worth investigating. That's a bit more involved though. – troelskn Jul 26 '10 at 12:31

3 Answers3

14
function get_categories($parent = 0)
{
    $html = '<ul>';
    $query = mysql_query("SELECT * FROM `categories` WHERE `category_parent` = '$parent'");
    while($row = mysql_fetch_assoc($query))
    {
        $current_id = $row['category_id'];
        $html .= '<li>' . $row['category_name'];
        $has_sub = NULL;
        $has_sub = mysql_num_rows(mysql_query("SELECT COUNT(`category_parent`) FROM `categories` WHERE `category_parent` = '$current_id'"));
        if($has_sub)
        {
            $html .= get_categories($current_id);
        }
        $html .= '</li>';
    }
    $html .= '</ul>';
    return $html;
}

print get_categories();
fabrik
  • 14,094
  • 8
  • 55
  • 71
  • @Salman A: I tried your example, but it only returns the first category. – Psyche Jul 26 '10 at 10:12
  • 1
    It isn't Salman's mistake (thanks for the edit, Salman). Modifying the code i think i know where's the error. – fabrik Jul 26 '10 at 10:14
  • @fabrik: it's a little better after that change, but I still can't get the full tree. It stops at "Categoria 1.1.2". – Psyche Jul 26 '10 at 10:20
  • Please give me a moment i'll sort out. – fabrik Jul 26 '10 at 10:25
  • The code above is working for me (except that my SQL queries isn't the same). Was any PHP/MySQL error there? I modified the row that checks $has_subs maybe it will be fine. – fabrik Jul 26 '10 at 10:37
  • @fabrik: so you can get the full tree with that code? Still doesn't work for me. There's no error, but the output stops at "Categoria 1.1.2". – Psyche Jul 26 '10 at 10:42
  • How many rows do you have in your table? How many rows doesn't displayed? – fabrik Jul 26 '10 at 10:58
  • @fabrik: I have the categories listed in my post. So 7 categories and it only displays "Categoria 1", "Categoria 1.1" and "Categoria 1.1.2". – Psyche Jul 26 '10 at 11:40
  • Allright, that means the snippet isn't detecting child nodes. Because $current_id must be right there's something wrong with my $has_sub query. Modified my post at this query hope it will work. – fabrik Jul 26 '10 at 11:45
  • Can you tell me what's the output of "SELECT COUNT(`category_parent`) FROM `categories` WHERE `category_parent` = 1"? – fabrik Jul 26 '10 at 12:10
  • Hmm, this is strange. I have a MySQL singleton class for database connection and operations. I wrote the code again using plain MySQL functions and it works. – Psyche Jul 26 '10 at 12:14
  • Great! :) So the recursion hasn't worked because of singleton? – fabrik Jul 26 '10 at 12:17
  • I do wonder why isn't it working with that MySQL class I have? – Psyche Jul 26 '10 at 13:34
  • Even it doesn't calling you MySQL methods? – fabrik Jul 26 '10 at 13:58
  • If I use plain MySQL functions it works. If I use that singleton MySQL class, it doesn't. – Psyche Jul 26 '10 at 17:08
0

I've found quite a simple example that works over an array of categories retrieved from a DB. The query "SELECT * FROM categories" will create an array of all the categories.

In my categories table I have the fields 'id','parent_id'and 'name'. That's all. Base categories have parent id set as 0. Subcats have parent_id set as parent category ID. Simple right ? Here is the code :

$categories = [

            ['id'=>1,'parentID'=>0, 'name' => 'Main 1' ],
            ['id'=>2,'parentID'=>0, 'name' => 'Main 2' ],
            ['id'=>3,'parentID'=>0, 'name' => 'Main 3' ],
            ['id'=>4,'parentID'=>0, 'name' => 'Main 4' ],
            ['id'=>5,'parentID'=>1, 'name' => 'sub 1 - 1' ],
            ['id'=>6,'parentID'=>1, 'name' => 'sub 1 - 2' ],

            ['id'=>11,'parentID'=>5, 'name' => 'sub 1 - 2' ],
            ['id'=>12,'parentID'=>5, 'name' => 'sub 1 - 2' ],
            ['id'=>13,'parentID'=>5, 'name' => 'sub 1 - 2' ],
            ['id'=>14,'parentID'=>6, 'name' => 'sub 1 - 2' ],


            ['id'=>7,'parentID'=>2, 'name' => 'sub 2 - 1' ],
            ['id'=>8,'parentID'=>2, 'name' => 'sub 2 - 2' ],
            ['id'=>9,'parentID'=>3, 'name' => 'sub 3 - 1' ],
            ['id'=>10,'parentID'=>3, 'name' => 'sub 3 - 2' ]
        ];

        function getCategories($categories, $parent = 0)
        {
            $html = "<ul>";
            foreach($categories as $cat)
            {
                if($cat['parentID'] == $parent)
                {
                    $current_id = $cat['id'];
                    $html .= "<li>" . $cat['name'] ;
                        $html .= getCategories($categories, $current_id);

                    $html .= "</li>";
                }
            }
            $html .= "</ul>";
            return $html;
        }



        echo getCategories($categories) ;

$categories array is an effect of one sql query "SELECT * FROM categories"

11mb
  • 1,339
  • 2
  • 16
  • 33
Severo
  • 1
  • 1
-1

Here is something I use for displaying nested links, it only makes one DB query then caches the results in a useful array..

function generate_menu($parent, $menu_array=Array(), $level = 0, $first=0)
    {
        $has_childs = false;

        if (empty($menu_array)) {

            $rs = mysql_query("SELECT id, parent, name FROM cats");

            while ( $row = mysql_fetch_assoc($rs) )
            {
                    $menu_array[$row['id']] = array('name' => $row['name'],'parent' => $row['parent']);
            }        
        }

        foreach ($menu_array as $key => $value)
        {
            if ($value['parent'] == $parent) 
            {   
                //if this is the first child print '<ul>'           
                if ($has_childs === false) {
                        //don't print '<ul>' multiple times             
                        $has_childs = true;
                        if ($first == 0){
                            echo "<ul id=\"nav\">\n";
                            $first = 1;
                        } else {
                            echo "\n<ul>\n";
                        }
                }
                $pad = str_repeat('&#8211; ', $level);
                echo "<li><a href=\"http://" . $value['url'].'/\">' . $value['name'] . "</a>";
                generate_menu($key, $menu_array, $level + 1, $first);
                //call function again to generate nested list for subcategories belonging to this category
                echo "</li>\n";
            }
        }
        if ($has_childs === true) echo "</ul>\n";
    }
Ryun
  • 715
  • 6
  • 10