Hierarchical menu

I’m using Yii to create a simple CMS as my firs Yii application. I want to create hierarchical menu, consisting of categories, sub categories, sub sub categories and so on. I have the database:




CREATE TABLE IF NOT EXISTS `Categories` (

  `id` int(11) NOT NULL auto_increment, 

  `title` varchar(128) NOT NULL,

  `parent` int(11) default NULL,

  PRIMARY KEY  (`id`)

)ENGINE=MyISAM;



After I build a model, I wanted to see if the controller is working, so I made very simple view file:




<?php echo '<pre>'.$menu.'</pre>'; ?>



Next I wanted to build a controller. I made it to this:




<?php


class CategoryController extends MyController{

....

....

....

    /**

     * Lists categories

     */

    public function actionList(){


        $criteria=new CDbCriteria;

        $categories=Categories::model()->findAll($criteria);


        foreach($categories as $category){

         if(!$category->parent){

          $menu .= CHtml::link(CHtml::encode($category->title),array('post/list','category'=>$category->id)).'/n';

          $this->findChild($category->id, 1);

         }

        }


        $this->render('list',array(

            'menu'=>$menu

            ));

    }


....

....

....


    protected function findChild($category, $level){

       $criteria->condition="parent=".$category;

       $childs=Categories::model()->findAll($criteria);

     if($childs){

      foreach($childs as $child){

        $menu .= str_repeat('  ',$level).CHtml::link(CHtml::encode($child->title),array('post/list','category'=>$child->id)).'/n';

        findChild($child->id, $level+1);

     }

     }else{

     return false;

    }


....

....

....



I think that the code should work, but it doesn’t. When I lunch it I have an error: Object of class stdClass could not be converted to string. I cant find out where is the problem and I will be very happy if someone help me to solve it.

findChild($child->id, $level+1);

should be

$this->findChild($child->id, $level+1);

This is not optimal code either. What you are doing, is pulling all the category items, but then ignoring the children. Then you go on to re-query the database for the children of each parent.

I changed it to $this->findChild($child->id, $level+1);, but the problem still exist. Guess I will have to find another way for doing this. I have a lot of other ideas but I havent try them all yet. I will be very happy if someone suggest me a better solution.

I’ve gotten that error before and I think is was because of something silly… Like I forgot to put in model()-&gt; after a model call or something like that

You have $criteria->condition="parent=".$category; but I do not see where $criteria is defined

It seems that every application I look at has this structure, that is, a second query to retrieve the children.

Are you able to point me to an example class that is able to pull the required parent/child structure using a single query?

At least on the first query in actionList() you should only pull the items where the parent IS NULL, so not to pull child items.

You could also do the whole thing in one query but that’s more complicated. I’ve never written one of these before actually…

I try to use relation query to find child categories.

first build relation in Categories model.




	public function relations()

	{

		return array(

		  'child'=>array(self::HAS_MANY, 'Categories', 'parent'),

		);

	}



modify actionList() in CategoryController




	public function actionList()

	{

		$categories=Categories::model()->findAll('ISNULL(parent)');

		foreach($categories as $category)

		{

		  echo $category->title.'<br>';

		  $this->findChild($category,1);

		}		  

	}



modify findChild() in CategoryController




	protected function findChild($category,$level)

	{

		if($category->child) //child is relation name in Categories

		{

		  foreach($category->child as $child)

		  {

  		    echo str_repeat('&nbsp;&nbsp;',$level).$child->title.'<br>';

  		    $this->findChild($child,$level+1);

		  }

		}

	}



I don’t use view, just use ‘echo’ to show result.

I would recommend using the nestedset extension… saves alot of queries.