8

Is there any way to grab the nav menu items as a multidimensional array instead of a flat array?

By a tree-like structure I mean something that would preserve relationship between child and parent items, like so (this is just an example)…

array(
  array(
    'post_type' => 'page',
    'post_name' => 'Home',
    'children' => array() 
  ),
  array(
    'post_type' => 'page',
    'post_name' => 'About Us',
    'children' => array(
      array(
        'post_type' => 'page',
        'post_name' => 'Our History',
        'children' => array() 
      )
    ) 
  )
)

There is a wp_get_nav_menu_items() function but it returns a 1-dimensional array with all the items on the same level, which is not what I want. Does WordPress include a built-in way to get a multidimensional array for my menu items? If not, what is the best way to get wp_get_nav_menu_items() as a tree-like structure into a multidimensional array in terms of performance?

  • 3
    that 1-dimensional array contains all the data you need to build a tree if you use a recursive function. for each of the menu item IDs, look for other menu items with matching ID in the object parent field, those will be its children. – Milo Dec 2 '14 at 4:52
  • I know I can make a tree out of it, but I was wondering if there is already any such option in wp. – YemSalat Dec 2 '14 at 10:52
  • What is your use-case? The Walker class handles the depth of sorted nav menu items automatically, even if the array is flat. – Matt van Andel Jul 31 '15 at 23:37
  • 1
    Your edit is wrong. I edited the title back (changed a couple words) The output of nav_items is a flat array, its not a tree in any sense. My use case is - I want the nav items as a tree, so I can do thins with it on my own, without having to use WP's broken abstractions. – YemSalat Aug 1 '15 at 11:43
  • I clarified the question a bit, to make it more clear what I want. – YemSalat Aug 1 '15 at 12:04
16
+100

The problem of building a tree from a flat array has been solved here with this, slightly modified, recursive solution:

/**
 * Modification of "Build a tree from a flat array in PHP"
 *
 * Authors: @DSkinner, @ImmortalFirefly and @SteveEdson
 *
 * @link https://stackoverflow.com/a/28429487/2078474
 */
function buildTree( array &$elements, $parentId = 0 )
{
    $branch = array();
    foreach ( $elements as &$element )
    {
        if ( $element->menu_item_parent == $parentId )
        {
            $children = buildTree( $elements, $element->ID );
            if ( $children )
                $element->wpse_children = $children;

            $branch[$element->ID] = $element;
            unset( $element );
        }
    }
    return $branch;
}

where we added the prefixed wpse_children attribute to avoid name collision.

Now we only have to define a simple helper function:

/**
 * Transform a navigational menu to it's tree structure
 *
 * @uses  buildTree()
 * @uses  wp_get_nav_menu_items()
 *
 * @param  String     $menud_id 
 * @return Array|null $tree 
 */
function wpse_nav_menu_2_tree( $menu_id )
{
    $items = wp_get_nav_menu_items( $menu_id );
    return  $items ? buildTree( $items, 0 ) : null;
}

Now it becomes super easy to transform a navigational menu into it's tree structure with:

$tree = wpse_nav_menu_2_tree( 'my_menu' );  // <-- Modify this to your needs!
print_r( $tree );

For JSON, we can simply use:

$json = json_encode( $tree );

For a slightly different version, where we handpicked the attributes, check out the first revision of this answer here.

Update: Walker Class

Here's a rather sketchy idea how we could try to hook into the recursive part of the the display_element() method of the abstract Walker class.

$w = new WPSE_Nav_Menu_Tree;
$args = (object) [ 'items_wrap' => '', 'depth' => 0, 'walker' => $w ];
$items = wp_get_nav_menu_items( 'my_menu' );
walk_nav_menu_tree( $items, $args->depth, $args );
print_r( $w->branch );  

where WPSE_Nav_Menu_Tree is an extension of the Walker_Nav_Menu class:

class WPSE_Nav_Menu_Tree extends Walker_Nav_Menu
{
   public $branch = [];

   public function display_element($element, &$children, $max_depth, $depth = 0, $args, &$output )
   {
      if( 0 == $depth )
         $this->branch[$element->ID] = $element;

      if ( isset($children[$element->ID] ) )
         $element->wpse_children = $children[$element->ID];

      parent::display_element($element, $children, $max_depth, $depth, $args, $output);
   }
}

This might give us an alternative approach if it works.

  • thanks, it's always interesting & fun to see different approaches to problem solving - your's looks pretty cool +1. @ialocin – birgire Jul 29 '15 at 20:56
  • 1
    Same here, but we knew already who voted :) Exploring possibilities is fun! The rest often is like assembly-line work, which is...lets just say not fun. – Nicolai Jul 29 '15 at 21:01
  • Thanks, I was hoping there would be a "native" WP function for this. I'll wait bit more to see if someone posts any other solutions, otherwise this will be the chosen answer. – YemSalat Jul 29 '15 at 23:56
  • I updated the answer with another kind of approach @YemSalat – birgire Jul 30 '15 at 15:04
  • Whoa! That makes my mind swirl. I never dealt with the Walker class before (I know it exists though) I was hoping there would be a more performant way of doing it with a couple SQL queries, but I really don't want to get into WP db structure. For now I would prefer your first approach where its cycling through wp_get_nav_menu_items recursively. – YemSalat Jul 30 '15 at 15:12
3

In short the function bellow does create the tree of objects by putting children into a new children property inside the parent object.

Code:

function wpse170033_nav_menu_object_tree( $nav_menu_items_array ) {
    foreach ( $nav_menu_items_array as $key => $value ) {
        $value->children = array();
        $nav_menu_items_array[ $key ] = $value;
    }

    $nav_menu_levels = array();
    $index = 0;
    if ( ! empty( $nav_menu_items_array ) ) do {
        if ( $index == 0 ) {
            foreach ( $nav_menu_items_array as $key => $obj ) {
                if ( $obj->menu_item_parent == 0 ) {
                    $nav_menu_levels[ $index ][] = $obj;
                    unset( $nav_menu_items_array[ $key ] );
                }
            }
        } else {
            foreach ( $nav_menu_items_array as $key => $obj ) {
                if ( in_array( $obj->menu_item_parent, $last_level_ids ) ) {
                    $nav_menu_levels[ $index ][] = $obj;
                    unset( $nav_menu_items_array[ $key ] );
                }
            }
        }
        $last_level_ids = wp_list_pluck( $nav_menu_levels[ $index ], 'db_id' );
        $index++;
    } while ( ! empty( $nav_menu_items_array ) );

    $nav_menu_levels_reverse = array_reverse( $nav_menu_levels );

    $nav_menu_tree_build = array();
    $index = 0;
    if ( ! empty( $nav_menu_levels_reverse ) ) do {
        if ( count( $nav_menu_levels_reverse ) == 1 ) {
            $nav_menu_tree_build = $nav_menu_levels_reverse;
        }
        $current_level = array_shift( $nav_menu_levels_reverse );
        if ( isset( $nav_menu_levels_reverse[ $index ] ) ) {
            $next_level = $nav_menu_levels_reverse[ $index ];
            foreach ( $next_level as $nkey => $nval ) {
                foreach ( $current_level as $ckey => $cval ) {
                    if ( $nval->db_id == $cval->menu_item_parent ) {
                        $nval->children[] = $cval;
                    }
                }
            }
        }
    } while ( ! empty( $nav_menu_levels_reverse ) );

    $nav_menu_object_tree = $nav_menu_tree_build[ 0 ];
    return $nav_menu_object_tree;
}

Usage:

$nav_menu_items = wp_get_nav_menu_items( 'main-menu' );
wpse170033_nav_menu_object_tree( $nav_menu_items );

Output:

Array
(
 [0] => WP_Post Object
  (
   [ID] => 51
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 51
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=51
   [menu_order] => 1
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 51
   [menu_item_parent] => 0
   [object_id] => 2
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page/
   [title] => Example-Page-1
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
     [0] => WP_Post Object
      (
       [ID] => 80
       [post_author] => 1
       [post_date] => 2015-06-27 14:03:31
       [post_date_gmt] => 2015-06-27 12:03:31
       [post_content] => 
       [post_title] => 
       [post_excerpt] => 
       [post_status] => publish
       [comment_status] => open
       [ping_status] => open
       [post_password] => 
       [post_name] => 80
       [to_ping] => 
       [pinged] => 
       [post_modified] => 2015-07-29 20:55:10
       [post_modified_gmt] => 2015-07-29 18:55:10
       [post_content_filtered] => 
       [post_parent] => 2
       [guid] => http://example.com/?p=80
       [menu_order] => 2
       [post_type] => nav_menu_item
       [post_mime_type] => 
       [comment_count] => 0
       [filter] => raw
       [db_id] => 80
       [menu_item_parent] => 51
       [object_id] => 69
       [object] => page
       [type] => post_type
       [type_label] => Page
       [url] => http://example.com/example-page/subpage-1/
       [title] => Subpage-1
       [target] => 
       [attr_title] => 
       [description] => 
       [classes] => Array
        (
         [0] => 
        )
       [xfn] => 
       [children] => Array
        (
        )
      )
    )
  )
 [1] => WP_Post Object
  (
   [ID] => 49
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 49
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=49
   [menu_order] => 3
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 49
   [menu_item_parent] => 0
   [object_id] => 41
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page-2/
   [title] => Example-Page-2
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
    )
  )
 [2] => WP_Post Object
  (
   [ID] => 48
   [post_author] => 1
   [post_date] => 2015-06-26 21:13:23
   [post_date_gmt] => 2015-06-26 19:13:23
   [post_content] => 
   [post_title] => 
   [post_excerpt] => 
   [post_status] => publish
   [comment_status] => open
   [ping_status] => open
   [post_password] => 
   [post_name] => 48
   [to_ping] => 
   [pinged] => 
   [post_modified] => 2015-07-29 20:55:10
   [post_modified_gmt] => 2015-07-29 18:55:10
   [post_content_filtered] => 
   [post_parent] => 0
   [guid] => http://example.com/?p=48
   [menu_order] => 4
   [post_type] => nav_menu_item
   [post_mime_type] => 
   [comment_count] => 0
   [filter] => raw
   [db_id] => 48
   [menu_item_parent] => 0
   [object_id] => 42
   [object] => page
   [type] => post_type
   [type_label] => Page
   [url] => http://example.com/example-page-3/
   [title] => Example-Page-3
   [target] => 
   [attr_title] => 
   [description] => 
   [classes] => Array
    (
     [0] => 
    )
   [xfn] => 
   [children] => Array
    (
     [0] => WP_Post Object
      (
       [ID] => 79
       [post_author] => 1
       [post_date] => 2015-06-27 14:03:31
       [post_date_gmt] => 2015-06-27 12:03:31
       [post_content] => 
       [post_title] => 
       [post_excerpt] => 
       [post_status] => publish
       [comment_status] => open
       [ping_status] => open
       [post_password] => 
       [post_name] => 79
       [to_ping] => 
       [pinged] => 
       [post_modified] => 2015-07-29 20:55:10
       [post_modified_gmt] => 2015-07-29 18:55:10
       [post_content_filtered] => 
       [post_parent] => 42
       [guid] => http://example.com/?p=79
       [menu_order] => 5
       [post_type] => nav_menu_item
       [post_mime_type] => 
       [comment_count] => 0
       [filter] => raw
       [db_id] => 79
       [menu_item_parent] => 48
       [object_id] => 70
       [object] => page
       [type] => post_type
       [type_label] => Page
       [url] => http://example.com/example-page-3/subpage-2/
       [title] => Subpage-2
       [target] => 
       [attr_title] => 
       [description] => 
       [classes] => Array
        (
         [0] => 
        )
       [xfn] => 
       [children] => Array
        (
         [0] => WP_Post Object
          (
           [ID] => 78
           [post_author] => 1
           [post_date] => 2015-06-27 14:03:31
           [post_date_gmt] => 2015-06-27 12:03:31
           [post_content] => 
           [post_title] => 
           [post_excerpt] => 
           [post_status] => publish
           [comment_status] => open
           [ping_status] => open
           [post_password] => 
           [post_name] => 78
           [to_ping] => 
           [pinged] => 
           [post_modified] => 2015-07-29 20:55:10
           [post_modified_gmt] => 2015-07-29 18:55:10
           [post_content_filtered] => 
           [post_parent] => 70
           [guid] => http://example.com/?p=78
           [menu_order] => 6
           [post_type] => nav_menu_item
           [post_mime_type] => 
           [comment_count] => 0
           [filter] => raw
           [db_id] => 78
           [menu_item_parent] => 79
           [object_id] => 76
           [object] => page
           [type] => post_type
           [type_label] => Page
           [url] => http://example.com/example-page-3/subpage-2/subpage-3/
           [title] => Subpage-3
           [target] => 
           [attr_title] => 
           [description] => 
           [classes] => Array
            (
             [0] => 
            )
           [xfn] => 
           [children] => Array
            (
             [0] => WP_Post Object
              (
               [ID] => 87
               [post_author] => 1
               [post_date] => 2015-06-27 15:27:08
               [post_date_gmt] => 2015-06-27 13:27:08
               [post_content] => 
               [post_title] => 
               [post_excerpt] => 
               [post_status] => publish
               [comment_status] => open
               [ping_status] => open
               [post_password] => 
               [post_name] => 87
               [to_ping] => 
               [pinged] => 
               [post_modified] => 2015-07-29 20:55:10
               [post_modified_gmt] => 2015-07-29 18:55:10
               [post_content_filtered] => 
               [post_parent] => 76
               [guid] => http://example.com/?p=87
               [menu_order] => 7
               [post_type] => nav_menu_item
               [post_mime_type] => 
               [comment_count] => 0
               [filter] => raw
               [db_id] => 87
               [menu_item_parent] => 78
               [object_id] => 85
               [object] => page
               [type] => post_type
               [type_label] => Page
               [url] => http://example.com/example-page-3/subpage-2/subpage-3/subpage-4/
               [title] => Subpage-4
               [target] => 
               [attr_title] => 
               [description] => 
               [classes] => Array
                (
                 [0] => 
                )
               [xfn] => 
               [children] => Array
                (
                 [0] => WP_Post Object
                  (
                   [ID] => 366
                   [post_author] => 1
                   [post_date] => 2015-07-29 20:52:46
                   [post_date_gmt] => 2015-07-29 18:52:46
                   [post_content] => 
                   [post_title] => 
                   [post_excerpt] => 
                   [post_status] => publish
                   [comment_status] => open
                   [ping_status] => open
                   [post_password] => 
                   [post_name] => 366
                   [to_ping] => 
                   [pinged] => 
                   [post_modified] => 2015-07-29 20:55:10
                   [post_modified_gmt] => 2015-07-29 18:55:10
                   [post_content_filtered] => 
                   [post_parent] => 85
                   [guid] => http://example.com/?p=366
                   [menu_order] => 8
                   [post_type] => nav_menu_item
                   [post_mime_type] => 
                   [comment_count] => 0
                   [filter] => raw
                   [db_id] => 366
                   [menu_item_parent] => 87
                   [object_id] => 112
                   [object] => page
                   [type] => post_type
                   [type_label] => Page
                   [url] => http://example.com/example-page-3/subpage-2/subpage-3/subpage-4/subpage-5/
                   [title] => Subpage-5
                   [target] => 
                   [attr_title] => 
                   [description] => 
                   [classes] => Array
                    (
                     [0] => 
                    )
                   [xfn] => 
                   [children] => Array
                    (
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)
  • A tree-like structure in WordPress is not a multidimensional array. It is an array of objects with parentage information. – Matt van Andel Aug 1 '15 at 0:14
  • Tried about 10 different solutions for this issue. Thank you for this great solution, it keeps it in a nice WP Object structure. This needs to be accepted actually! – Johan Pretorius Aug 11 '17 at 14:03
  • @JohanPretorius Thanks and my pleasure. Well people are looking for different things. I assume the OP found the other answer more helpful. It's all good. – Nicolai Aug 15 '17 at 20:32
1

Modified version of accepted answer where it takes into consideration the menu_order property of the menu items in order to retain the proper order of the original flat array. menu_order is set automatically by WordPress so no need to check for anything:

function buildTree(array &$flatNav, $parentId = 0) {
    $branch = [];

    foreach ($flatNav as &$navItem) {
      if($navItem->menu_item_parent == $parentId) {
        $children = buildTree($flatNav, $navItem->ID);
        if($children) {
          $navItem->children = $children;
        }

        $branch[$navItem->menu_order] = $navItem;
        unset($navItem);
      }
    }

    return $branch;
}

Usage:

// get navs
$locations = get_nav_menu_locations();

// get menu items by menu name
$flatMainNav = wp_get_nav_menu_items($locations['main']);
$mainNav = buildTree($flatMainNav);
-2

There may be a misunderstanding here about WordPress nav menu items as tree-like structures.

Tree-like structures in WordPress ARE NOT MULTIDIMENSIONAL ARRAYS!

Note that while the returned menu items array is flat, it is still a tree-like structure because each item contains information about it's parentage (the parent value is either 0 if the item has no parent - or the id of the parent item if it does).

When you pass such an array to the Walker class, it loops through the results and creates two arrays - one containing top-level items, and another containing child items in format $parent_id => array() where the array contains menu items that are direct children of that element.

The walker then loops through the top level items array, processing that item and then checking the children array to see if there are any children for the current element, and processes each of those the same way, recursively.

If you want to know how to convert a WordPress tree-like structure into a multidimensional array, that is a different question entirely (and not technically a WordPress question). But the information returned by wp_get_nav_menu_items() is a tree-like structure... and you can use Walker as-is to handle it.

If you want to see the exact code that WordPress's Walker class runs to walk the array, take a look at Walker->walk() on WordPress Trac from lines 213-258. You could use that code as-is to build a multidimensional array, if that's what you're after.

Walkers

WordPress is designed to use the Walker class to process it's tree-like structures. If you are simply rendering a menu, or really just need the ultimate output, you may want to consider using wp_nav_menu() to output your menu…

Example:

wp_nav_menu(array(
    'menu' => 6, // your menu id, name, or slug
    'echo' => true, // set this to false if you want a string back instead
    'walker' => new Your_Walker(),
));

You would extend the Walker class (e.g. Your_Walker()) to get whatever output you need. For an example, see this entry on the Codex.

  • 2
    In option A, $sorted_menu_items is still a "flat" array and the output of option B is a string. – birgire Jul 31 '15 at 3:44
  • I think there is a misunderstanding about how WordPress defines "tree-like structures". wp_get_nav_menu_items() returns a tree-like structure - that is, an array where each item contains parentage data. These structures are meant to rendered with a Walker class. If the use-case here merely involves converting a "flat" array into a multidimensional array based on parentage data (e.g. 'post_parent' => 123 ), this question is not technically about WordPress and should be moved to Stack Overflow. – Matt van Andel Aug 1 '15 at 0:24
  • 1
    Look, I don't care what WordPress defines "tree-like structures" (I don't think this sentense even makes sense) All I care about is having a multi dimensional array, which I can do stuff with on my own. – YemSalat Aug 1 '15 at 12:03
  • You are NOT going to get that as default WordPress behavior. As others have stated, you have all the information you need to restructure the array how you want, and I linked you to specific areas in WordPress core to use as a reference. This isn't really a WordPress question as much as a PHP one. You can either use the Walker class as-is, or you can copy the relevant lines from Walker::walk() as I stated, to build your array. – Matt van Andel Aug 1 '15 at 19:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.