Recursive foreach() producing duplicate results when looping

WordPress navigation menu

This class is designed to iterate over a WordPress menu structure (nested arrays/objects) to produce a finished menu. While the source of my data is WordPress, I feel this question belongs on SO instead of WP.SO because the issue is rooted more in PHP (applies to anyone attempting a recursion).

For some reason, I’m seeing duplicate results in the hierarchy. Also, I’m noticing that certain HTML elements are not closing properly. It seems like I’ve nested everything properly, but the result is what you see here.

To assist with debugging, I’ve added some * to impact the markup visually. Maybe you guys know something I don’t. Fingers crossed and thanks in advance for your input!

My class

class Nav_Menu
{
    public $wp_nav;
    public $nested_nav;
    public $recursion_depth = 0;

    function __construct( $menu, $args = array() )
    {
        $format = new Format;

        if( $menu )
        {
            $this->wp_nav = wp_get_nav_menu_items($menu, $args);
            $this->nested_nav = $this->build_tree($this->wp_nav);

            $output = $this->build_output($this->nested_nav);
            $output_formatted = $format->HTML($output);

            // echo $output;
            echo $output_formatted;
        }
    }

    private function build_output( $menu = array() )
    {
        $output = '**';
        $output.= $this->recurse_menu($menu, $output);

        return $output;
    }

    private function recurse_menu( $menu = array(), $output )
    {
        global $post;

        if( !empty($menu) && !empty($output) )
        {
            $this->recursion_depth++;

            // ul classes
            $classes_ul = array();
            $classes_ul[] = ( $this->recursion_depth > 1 ? 'sub-menu' : '' );
            $classes_ul[] = 'depth-' . $this->recursion_depth;

            // process list wrappers
            $output.= '<ul class="' . $this->process_classes($classes_ul) . '">';

            // loop through menu items
            foreach( $menu as $menu_key => $menu_val )
            {
                // process list items
                $output.= '<li>' . $menu_val->title;

                // if necessary, handle children and recurse
                if( !empty($menu_val->children) )
                {
                    // recurse, and call this again
                    $output.= $this->recurse_menu($menu_val->children, $output);
                }

                // process list items
                $output.= '</li>';
            }

            // process list wrappers
            $output.= '</ul>';
        }

        return $output;
    }

    private function process_classes($classes = array())
    {
        if( !$classes )
        {
            return;
        }

        return trim(implode(' ', $classes));
    }

    private function build_tree( $elements = array(), $parent_id = 0 )
    {
        $branch = array();
        foreach($elements as $element)
        {
            if ($element->menu_item_parent == $parent_id)
            {
                $children = $this->build_tree($elements, $element->ID);
                if ($children)
                {
                    $element->children = $children;
                }

                $branch[] = $element;
            }
        }

        return $branch;
    }
}

$mynav = new Nav_Menu('Test Menu');

The resulting output

****
<ul class="depth-1">
    <li>
        One**
        <ul class="depth-1">
            <li>
                One
                <ul class="sub-menu depth-2">
                    <li>
                        Sub One
                    </li>
                    <li>
                        Sub Two
                    </li>
                    <li>
                        Sub Three
                    </li>
                </ul>
            </li>
            <li>
                Two
            </li>
            <li>
                Three**
                <ul class="depth-1">
                    <li>
                        One**
                        <ul class="depth-1">
                            <li>
                                One
                                <ul class="sub-menu depth-2">
                                    <li>
                                        Sub One
                                    </li>
                                    <li>
                                        Sub Two
                                    </li>
                                    <li>
                                        Sub Three
                                    </li>
                                </ul>
                            </li>
                            <li>
                                Two
                            </li>
                            <li>
                                Three
                                <ul class="sub-menu depth-3">
                                    <li>
                                        Sub One
                                    </li>
                                    <li>
                                        Sub Two
                                    </li>
                                </ul>
                            </li>
                            <li>
                                Four
                            </li>
                        </ul>

WordPress menu in the backend

Read more here: Recursive foreach() producing duplicate results when looping

Leave a Reply

Your email address will not be published. Required fields are marked *