6

I have a database with a bunch of categories, some are children:

Array
(
    [0] => Array
        (
            [id] => 1
            [name] => Home Improvement
            [slug] => Home-Improvement
            [parent] => 
            [user_id] => 1
            [order] => 1
        )

    [1] => Array
        (
            [id] => 2
            [name] => Asbestos Abatement & Removal
            [slug] => Asbestos-Abatement-Removal
            [parent] => 1
            [user_id] => 1
            [order] => 8
        )

    [2] => Array
        (
            [id] => 3
            [name] => Asphalt & Asphalt Products
            [slug] => Asphalt-Asphalt-Products
            [parent] => 1
            [user_id] => 1
            [order] => 9
        )

    [3] => Array
        (
            [id] => 4
            [name] => Bathroom
            [slug] => Bathroom
            [parent] => 1
            [user_id] => 1
            [order] => 10
        )

    [4] => Array
        (
            [id] => 5
            [name] => Kitchen Cabinets
            [slug] => Kitchen-Cabinets
            [parent] => 1
            [user_id] => 1
            [order] => 11
        )

    [5] => Array
        (
            [id] => 6
            [name] => Ceilings
            [slug] => Ceilings
            [parent] => 1
            [user_id] => 1
            [order] => 12
        )

    [6] => Array
        (
            [id] => 7
            [name] => Cleaning
            [slug] => Cleaning
            [parent] => 1
            [user_id] => 1
            [order] => 13
        )

    [7] => Array
        (
            [id] => 8
            [name] => Closet Organizers & Accessories
            [slug] => Closet-Organizers-Accessories
            [parent] => 1
            [user_id] => 1
            [order] => 14
        )

    [8] => Array
        (
            [id] => 9
            [name] => Concrete
            [slug] => Concrete
            [parent] => 1
            [user_id] => 1
            [order] => 15
        )

    [9] => Array
        (
            [id] => 10
            [name] => Contractors & Service Providers
            [slug] => Contractors-Service-Providers
            [parent] => 1
            [user_id] => 1
            [order] => 16
        )

What I'm trying to output is something like this:

<ul>
    <li>Parent
        <ul>
            <li>Child</li>
        </ul>
    </li>
    <li>Parent with no Children</li>
</ul>

I'm trying to build a recursive tree script in PHP, but I'm stuck. Here's what I have so far. I'm stuck on what to do between the else: and endif; in the foreach. (And I'm using that syntax just for easier reading here.) Any suggestions?

echo $this->categories->makeTree(0, $this->db->get('categories')->result_array());

public static function makeTree($parent, $array)
{
  if (!is_array($array)) return '';

  $output = '<ul>';

  foreach($array as $key => $value):
    if ($value['parent'] == $parent):
        $output .= '<li>';

        if ($value['parent'] == NULL):
            $output .= $value['name'];
        else:

        endif;
    endif;

    $output .= '</li>';
  endforeach;

  $output .= '</ul>';
  return $output;
}

EDIT 1

I was able to get this working, although I have a database call in a foreach loop, which probably isn't the best idea:

public function makeTree($parent, $array)
{
  if (!is_array($array)) return FALSE;

  $output = '<ul>';

  foreach($array as $key => $value):
    if ($value['parent'] == $parent):
        $output .= '<li>';

        if ($value['parent'] == NULL):
            $output .= $value['name'];

            $subcategories = ci()->db->get_where('categories', array('parent' => $value['id']));

            if ($subcategories->num_rows() > 0):
                $output .= $this->makeTree($value['id'], $subcategories->result_array());
            endif;
        else:
            $output .= $value['name'];
            $output .= '</li>';
        endif;
    endif;

  endforeach;

  $output .= '</ul>';
  return $output;
}

EDIT 2

Here is my final solution, reusing the array instead of doing a DB query:

public function makeTree($parent, $array)
{
  if (!is_array($array) OR empty($array)) return FALSE;

  $output = '<ul>';

  foreach($array as $key => $value):
    if ($value['parent'] == $parent):
        $output .= '<li>';

        if ($value['parent'] == NULL):
            $output .= $value['name'];

            $matches = array();

            foreach($array as $subkey => $subvalue):
                if ($subvalue['parent'] == $value['id']):
                    $matches[$subkey] = $subvalue;
                endif;
            endforeach;

            $output .= $this->makeTree($value['id'], $matches);

        else:
            $output .= $value['name'];
            $output .= '</li>';
        endif;
    endif;

  endforeach;

  $output .= '</ul>';

  return $output;
}
1
  • It is currently traversing 2 levels. Can we traverse it up to n level? Commented Dec 1, 2015 at 11:34

5 Answers 5

10

Though this seems answered, have a look here. With the shown function you can convert your flat data to nested data with only one iteration. Creating an ul-list from that nested data is then very easy. For example:

function nested2ul($data) {
  $result = array();

  if (sizeof($data) > 0) {
    $result[] = '<ul>';
    foreach ($data as $entry) {
      $result[] = sprintf(
        '<li>%s %s</li>',
        $entry['name'],
        nested2ul($entry['children'])
      );
    }
    $result[] = '</ul>';
  }

  return implode($result);
}

echo nested2ul(array(flat2nested( $yourFlatData ));

The good thing about this approach is that you don't have to re-iterate again and again over the input data just to find the child-elements.

Sign up to request clarification or add additional context in comments.

2 Comments

I like that, but I keep getting "Undefined offset: 0" at return $m[$r][0]; in the makeRecursive function. That function is expecting data like array(array('id' => 5273, 'parent' => 0)) whereas my array is Array([0] => Array([id] => 1, [parent] => 0)). Any suggestion on how I can fix this?
@dallen the two snippets you posted are actually the same. I guess the problem is that your root parent value is an empty string. Whereas the helper function uses 0 (see the $r parameter). An empty string as an array key is a bit problematic, so if you could change that in your input data, I think the rest should work.
2

Here is my final solution, reusing the array instead of doing a DB query. If you do have a better solution, please post!

public function makeTree($parent, $array)
{
  if (!is_array($array) OR empty($array)) return FALSE;

  $output = '<ul>';

  foreach($array as $key => $value):
    if ($value['parent'] == $parent):
        $output .= '<li>';

        if ($value['parent'] == NULL):
            $output .= $value['name'];

            $matches = array();

            foreach($array as $subkey => $subvalue):
                if ($subvalue['parent'] == $value['id']):
                    $matches[$subkey] = $subvalue;
                endif;
            endforeach;

            $output .= $this->makeTree($value['id'], $matches);

        else:
            $output .= $value['name'];
            $output .= '</li>';
        endif;
    endif;

  endforeach;

  $output .= '</ul>';

  return $output;
}

Comments

1

I usualy use something like this, please note

1st this piece of code is using deprecated mysql_*

2nd you should have one database field named level, if NULL it is the main category, if it has a number, it is a subcategory of the category with that number as id

function getFamilies($level = 0) {
    $level++;
    $sql = "SELECT id from families WHERE level IS NULL";
    if (mysql_num_rows($result) > 0) {
        echo "<ul>";
            while($row = mysql_fetch_assoc($result)) {
                echo "<li>".$row['id'];
                    getSubFamilies($level, $row['id']);
                echo "</li>";
            }
        echo "</ul>";
    }
}

function getSubFamilies($level, $id) {
    $level++;
    $sqlSubFamilies = "SELECT id  FROM families WHERE level = ".$id."";
    $resultSubFamilies = mysql_query($sqlSubFamilies);
    if (mysql_num_rows($resultSubFamilies) > 0) {
        echo = "<ul>";
            while($rowSubFamilies = mysql_fetch_assoc($resultSubFamilies)) {
                echo "<li>".$rowSubFamilies['id'];
                    getSubFamilies($level, $rowSubFamilies['id']);
                echo "</li>";
            }
        echo "</ul>";
    }
}

getFamilies($level = 0);

1 Comment

in your case, level is parent, so "select id, name from categories where parent is null"
0

try this:

$cats = $this->db->get('categories')->result_array();

echo $this->categories->makeTree(0, $cats);

public static function makeTree($parent, $array)
{
  if (!is_array($array)) return '';

  $output = '<ul>';

  foreach($array as $key => $value):
    if ($value['parent'] == $parent):
        $output .= '<li>';

        if ($value['parent'] == NULL):
            $output .= $value['name'];
        else:

        endif;
    endif;

    $output .= '</li>';

    $output .= $this->categories->makeTree($value['parent'], $cats);

  endforeach;

  $output .= '</ul>';
  return $output;
}

Comments

-2

I think this method using anonymous function is very simple.

//--------------------------- PRINT NESTED CATEGORIES
$cats_childs = array();

$q = $db->query("SELECT id, parent, name FROM categories");

while ($r = $db->row($q))
{
    $cats_childs[$r['parent']][$r['id']] = $r;
}

$nested2ul = function($items, $childs = null) use (&$nested2ul) {
    if (!empty($items)) {
        echo '<ul>';
        foreach ($items as $r) {
            echo '<li>';
            echo $r['name'];
            $nested2ul($childs[$r['id']]);
            echo '</li>';
        }
        echo '</ul>';
    }
};

echo $nested2ul($cats_childs[0], $cats_childs);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.