Преобразование вывода элементов nav_menu в древовидный многомерный массив

11

Есть ли способ получить элементы навигационного меню как многомерный массив вместо плоского массива?

Под древовидной структурой я подразумеваю что-то, что сохраняло бы отношения между дочерними и родительскими элементами, вот так (это всего лишь пример)…

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() 
      )
    ) 
  )
)

Есть wp_get_nav_menu_items()функция, но она возвращает одномерный массив со всеми элементами на одном уровне, а это не то, что мне нужно. Есть ли в WordPress встроенный способ получения многомерного массива для моих пунктов меню? Если нет, то как лучше всего wp_get_nav_menu_items()в виде древовидной структуры превратить в многомерный массив с точки зрения производительности?

YemSalat
источник
3
Этот одномерный массив содержит все данные, необходимые для построения дерева, если вы используете рекурсивную функцию. для каждого из идентификаторов пунктов меню ищите другие пункты меню с совпадающими идентификаторами в родительском поле объекта, это будут его дочерние элементы.
Майло
Я знаю, что из него можно сделать дерево, но мне было интересно, есть ли в wp такая возможность.
YemSalat
Какой у вас сценарий использования? WalkerКласс обрабатывает глубину отсортированных элементов меню навигации автоматически, даже если массив является плоским.
Мэтт ван Андел
1
Ваше редактирование неверно. Я отредактировал заголовок назад (изменил пару слов). Вывод nav_items - это плоский массив, в каком-то смысле это не дерево. Мой пример использования - я хочу, чтобы элементы навигации были представлены в виде дерева, поэтому я могу делать с ним все самостоятельно, без использования сломанных абстракций WP.
YemSalat
Я немного прояснил вопрос, чтобы было понятнее, чего я хочу.
YemSalat

Ответы:

21

Проблема построения дерева из плоского массива была решена здесь с этим, слегка модифицирована, рекурсивное решение:

/**
 * 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;
}

где мы добавили префикс wpse_children атрибут, чтобы избежать конфликта имен.

Теперь нам осталось только определить простую вспомогательную функцию:

/**
 * 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;
}

Теперь становится очень легко преобразовать навигационное меню в его древовидную структуру с помощью:

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

Для JSON мы можем просто использовать:

$json = json_encode( $tree );

Для немного другой версии, где мы выбрали атрибуты, ознакомьтесь с первой версией этого ответа здесь .

Обновление: класс Уокера

Вот довольно схематичная идея, как мы можем попытаться подключиться к рекурсивной части display_element()метода абстрактного Walkerкласса.

$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 );  

где WPSE_Nav_Menu_Treeрасширение Walker_Nav_Menuкласса:

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);
   }
}

Это может дать нам альтернативный подход, если он работает.

birgire
источник
спасибо, всегда интересно и весело видеть разные подходы к решению проблем - ты выглядишь довольно круто +1. @ialocin
birgire
1
То же самое и здесь, но мы уже знали, кто проголосовал :) Изучение возможностей - это весело! Остальное часто похоже на работу на конвейере, которая ... скажем так, не весело.
Николай
Спасибо, я надеялся, что для этого будет «родная» функция WP. Я подожду еще немного, чтобы увидеть, если кто-то опубликует какие-либо другие решения, иначе это будет выбранный ответ
YemSalat
Я обновил ответ с другим подходом @YemSalat
birgire
Вау! Это заставляет мой разум кружиться. Я никогда раньше не имел дело с классом Уокера (хотя я знаю, что он существует). Я надеялся, что будет более эффективный способ сделать это с парой SQL-запросов, но я действительно не хочу входить в структуру WP db. На данный момент я бы предпочел ваш первый подход, где его циклически wp_get_nav_menu_itemsповторяется.
YemSalat
3

Вкратце, функция ниже создает дерево объектов, помещая потомки в новое свойство children внутри родительского объекта.

Код:

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;
}

Применение:

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

Вывод:

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
                    (
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)
Nicolai
источник
Древовидная структура в WordPress не является многомерным массивом. Это массив объектов с информацией о происхождении.
Мэтт ван Андел
Пробовал около 10 разных решений по этому вопросу. Спасибо за это отличное решение, оно сохраняет его в хорошей структуре объектов WP. Это должно быть принято на самом деле!
Drmzindec
@JohanPretorius Спасибо и мое удовольствие. Ну, люди ищут разные вещи. Я предполагаю, что ОП нашел другой ответ более полезным. Все хорошо.
Николай
1

Модифицированная версия принятого ответа, в которой учитывается menu_orderсвойство пунктов меню для сохранения правильного порядка исходного плоского массива. menu_orderустанавливается автоматически WordPress, поэтому нет необходимости проверять что-либо:

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;
}

Применение:

// get navs
$locations = get_nav_menu_locations();

// get menu items by menu name
$flatMainNav = wp_get_nav_menu_items($locations['main']);
$mainNav = buildTree($flatMainNav);
eballeste
источник
-2

Здесь может быть неправильное понимание элементов навигации WordPress как древовидных структур.

Древовидные структуры в WordPress не являются многомерными массивами!

Обратите внимание, что хотя возвращенный массив пунктов меню является плоским, он все еще является древовидной структурой, поскольку каждый элемент содержит информацию о своем происхождении (родительское значение равно 0, если у элемента нет родителя, или идентификатор родительского элемента, если он делает).

Когда вы передаете такой массив в Walkerкласс, он просматривает результаты и создает два массива - один, содержащий элементы верхнего уровня, и другой, содержащий дочерние элементы в формате$parent_id => array() где массив содержит элементы меню, которые являются прямыми этого элемента.

Затем обходчик просматривает массив элементов верхнего уровня, обрабатывает этот элемент и затем проверяет массив дочерних элементов, чтобы узнать, есть ли какие-либо дочерние элементы для текущего элемента, и обрабатывает каждый из них одинаково, рекурсивно.

Если вы хотите узнать, как преобразовать древовидную структуру WordPress в многомерный массив, это совершенно другой вопрос (а не вопрос WordPress). Но информация, возвращаемаяwp_get_nav_menu_items() это древовидная структура ... и вы можете использоватьWalker как есть , чтобы справиться с этим.

Если вы хотите увидеть точный код, который выполняет класс Walker WordPress для обхода массива, взгляните на Walker-> walk () на WordPress Trac из строк 213-258. . Вы можете использовать этот код «как есть» для построения многомерного массива, если это то, что вам нужно.

Ходоки

WordPress предназначен для использования Walkerкласса для обработки его древовидных структур. Если вы просто визуализируете меню или действительно нуждаетесь в конечном результате, вы можете рассмотреть возможность использованияwp_nav_menu() для вывода своего меню…

Пример:

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(),
));

Вы бы расширили класс Walker (например Your_Walker()), чтобы получить любой вывод, который вам нужен. Для примера, смотрите эту запись в Кодексе .

Мэтт ван Андел
источник
2
В варианте A $sorted_menu_itemsэто все еще «плоский» массив, а выводом опции B является строка.
Birgire
Я думаю, что есть недоразумение о том, как WordPress определяет «древовидные структуры». wp_get_nav_menu_items()возвращает древовидную структуру, то есть массив, в котором каждый элемент содержит данные о происхождении. Эти структуры предназначены для визуализации с помощью Walkerкласса. Если в данном случае просто используется преобразование «плоского» массива в многомерный массив на основе данных о происхождении (например 'post_parent' => 123), этот вопрос технически не относится к WordPress и должен быть перенесен в переполнение стека.
Мэтт ван Андел
1
Послушайте, мне все равно, что WordPress определяет "древовидные структуры" (я не думаю, что это предложение даже имеет смысл) Все, что меня волнует, - это наличие многомерного массива, с которым я могу что-то делать самостоятельно.
YemSalat
Вы НЕ собираетесь получить это как поведение WordPress по умолчанию. Как уже говорили другие, у вас есть вся информация, необходимая для реструктуризации массива, как вы хотите, и я связал вас с конкретными областями в ядре WordPress для использования в качестве справочного материала. Это не вопрос WordPress, а вопрос PHP. Вы можете использовать класс Walker как есть или скопировать соответствующие строки из Walker :: walk (), как я уже говорил, для построения массива.
Мэтт ван Андел