Mystx
Mystx

Reputation: 103

How to loop through array with indeterminate amount of children arrays

Im trying to list categories with sub categories in my app - I can use either PHP or Javascript / Jquery for the following:

I have an array of categories with sub categories appended as additional arrays

the trick is that it can go as deep as there are sub categories. and sub categories can also have sub categories.

Therefore for each category it can have as many children each of whom can have many children arrays.

What would be the best way to loop through them to create a dropdown list?

Here is the structure when dumping the main array:

array (size=2)
  0 => 
    array (size=4)
      'id' => int 1
      'name' => string 'Stationery' (length=10)
      'parent_id' => int 0
      'childs' => 
        array (size=1)
          0 => 
            array (size=4)
              ...
  1 => 
    array (size=3)
      'id' => int 4
      'name' => string 'boots' (length=5)
      'parent_id' => int 0

notice sub zero has a "childs" array when dumping this array i get:

array (size=1)
  0 => 
    array (size=4)
      'id' => int 2
      'name' => string 'pens' (length=4)
      'parent_id' => int 1
      'childs' => 
        array (size=1)
          0 => 
            array (size=4)
              ...

Notice this too has a child attached which when dumped looks like:

array (size=1)
  0 => 
    array (size=4)
      'id' => int 3
      'name' => string 'penfillers' (length=10)
      'parent_id' => int 2
      'childs' => 
        array (size=1)
          0 => 
            array (size=3)
              ...

Sneaky - this one also has another child!

This can go as deep as there are sub categories

How would i loop through them and have the output in a dropdown list? Im stumped as to how to loop infinitely until the chain ends.

Thanks Jason

Upvotes: 1

Views: 1201

Answers (3)

Paul Saunders
Paul Saunders

Reputation: 278

Here is a really simple example of how you could do it using recursion. I'm sure there is better ways but this is a very simple function so you can see the concept. The function calls itself until the job is done (I'm using the square bracket array notation here so make sure your PHP version supports it)

<?php

    function getItems(array $items)
    {
            $return = [];

            foreach ($items as $item) {
                    $return[] = $item;

                    if (isset($item['childs']) && count($item['childs']) > 0) {
                            $return = array_merge(
                                    $return,
                                    getItems($item['childs'])
                            );
                    }

            }

            return $return;
    }

    $array = [
            0 => [
                    'id' => 1,
                    'childs' => []
            ],
            1 => [
                    'id' => 2,
                    'childs' => [
                            0 => [
                                    'id' => 3,
                                    'childs' => []
                            ]
                    ]
            ]
    ];

    print_r(getItems($array));

Then just loop over the results to create your select options. Hope this helps

Upvotes: 1

Koala Yeung
Koala Yeung

Reputation: 7873

You should recursively yield all the options in the array. There are 2 ways to implement it. Depends on your PHP version.

To make the core logic cleaner, let's say we'd render the output with these utilities:

//
// some function to tidy up outputs
//

// simply make the recursive level visible
function pad_level($string, $level) {
    // no pad for 0 level
    if ($level == 0) return $string;

    // pad some '-' to show levels
    $pad = str_repeat('-', $level);
    return $pad . ' ' . $string;
}

// render a leaf into standardized info array for an option
function option_from($item, $level) {
    return array(
        'value' => $item['id'],
        'display_value' => pad_level($item['name'], $level),
    );
}

// render options into HTML select
function render_select($name, $options) {
    $output = '';
    foreach ($options as $option) {
        $output .= '  '.
            '<option value="'.htmlspecialchars($option["value"]).'">'.
            htmlspecialchars($option["display_value"]).
            '</option>'."\n";
    }
    return '<select name="'.htmlspecialchars($name).'">'."\n".
        $output."</select>\n";
}

// render options into plain text display
function render_plain_text($name, $options) {
    $output = '';
    foreach ($options as $option) {
        $output .= $option["value"].' => '.$option["display_value"]."\n";
    }
    return $output;
}

These are the 2 methods:

//
// core logic
//

// Method 1: Generator. Available with PHP 5 >= 5.5.0
function options_in($array, $level=0) {
    foreach ($array as $leaf) {
        yield option_from($leaf, $level);

        // yield the children options, if any
        if (isset($leaf['childs']) && is_array($leaf['childs'])) {
            foreach (options_in($leaf['childs'], $level+1) as $option) {
                yield $option;
            }
        }
    }
}

// Method 2: Normal recursion then merge arrays. For PHP 4 or after
function get_options($array, $level=0) {
    $output = array();

    // yield for the option array
    foreach ($array as $leaf) {
        $output[] = option_from($leaf, $level);
        // yield the children
        if (isset($leaf['childs']) && is_array($leaf['childs'])) {
            $childs = get_options($leaf['childs'], $level+1);
            $output = array_merge($output, $childs); // this could be slow
        }
    }

    return $output;
}

And this is how you actually render some HTML from it:

// dummy input
$input = array(
    array(
        'id' => 1,
        'name' => 'Stationery',
        'parent_id' => 0,
        'childs' => array(
            array(
                'id' => 2,
                'name' => 'Pencil',
                'parent_id' => 1,
            ),
            array(
                'id' => 3,
                'name' => 'Pen',
                'parent_id' => 1,
            ),
            array(
                'id' => 5,
                'name' => 'Notepad',
                'parent_id' => 1,
                'childs' => array(
                    array(
                        'id' => 8,
                        'name' => 'Blue Pad',
                        'parent_id' => 3,
                    ),
                    array(
                        'id' => 9,
                        'name' => 'Red Pad',
                        'parent_id' => 3,
                    ),
                    array(
                        'id' => 10,
                        'name' => 'iPad',
                        'parent_id' => 3,
                    ),
                ),
            ),
        ),
    ),
    array(
        'id' => 4,
        'name' => 'boots',
        'parent_id' => 0,
    ),
);

// method 1, preferred
echo "\nMethod 1\n";
echo render_select('mySelect', options_in($input));
echo render_plain_text('mySelect', options_in($input));

// method 2
echo "\nMethod 2\n";
echo render_select('mySelect', get_options($input));
echo render_plain_text('mySelect', get_options($input));

Upvotes: 1

barun
barun

Reputation: 403

create a sub function, input of the sub function would be object ,this function will check if it is an array or a simple element, if it is an array then it will call the function again on that element else return the element. just elaborate "Gerald Schneider"'s answer.

Upvotes: 0

Related Questions