PHP-создание вложенной структуры меню дерева из плоского массива

я делаю вложенный массив меню из ответа, который я получаю из базы данных WP. Я получаю данные от WP в контроллере в Laravel с помощью пакета corcel, а затем создание массива с данными меню, который теперь находится на одном уровне. Итак, когда ссылка меню имеет ссылки подменю, массив выглядит следующим образом:

{
    "Hjem": {
        "ID": 112,
        "title": "Hjem",
        "slug": "hjem",
        "url": "http://hivnorge.app/?p=112",
        "status": "publish",
        "main_category": "Hovedmeny",
        "submenus": [
            {
                "ID": 129,
                "title": "Lorem ipsum",
                "slug": "lorem-ipsum",
                "url": "http://hivnorge.app/?p=129",
                "status": "publish",
                "main_category": "Nyheter"
            }
        ]
    },
    "Nytt test innlegg": {
        "ID": 127,
        "title": "Nytt test innlegg",
        "slug": "nytt-test-innlegg",
        "url": "http://hivnorge.app/?p=127",
        "status": "private",
        "main_category": "Nyheter",
        "submenus": [
            {
                "ID": 125,
                "title": "Test innlegg",
                "slug": "test-innlegg",
                "url": "http://hivnorge.app/?p=125",
                "status": "publish",
                "main_category": "Nyheter"
            },
            {
                "ID": 129,
                "title": "Lorem ipsum",
                "slug": "lorem-ipsum",
                "url": "http://hivnorge.app/?p=129",
                "status": "publish",
                "main_category": "Nyheter"
            }
        ]
    },
    "Prosjektsamarbeidets verdi": {
        "ID": 106,
        "title": "Prosjektsamarbeidets verdi",
        "slug": "prosjektsamarbeidets-verdi",
        "url": "http://hivnorge.no.wordpress.seven.fredrikst/?p=106",
        "status": "publish",
        "main_category": "Prevensjon"
    }
}

вот как я создаю этот ответ:

        $menu = Menu::slug('hovedmeny')->first();
        $res = [];

        foreach ($menu->nav_items as $item) {
            $item->makeHidden($hiddenAttributes)->toArray();
            $parent_id = $item->meta->_menu_item_menu_item_parent;

            if ($parent_id == '0') {
              if ($item->title == '') {
                  $item = $this->findPost($item);
              }
              $parentItem = $item;
              $res[$parentItem->title] = $parentItem->makeHidden($hiddenAttributes)->toArray();
            }
            else {
              $childItem = $this->findPost($item);
              $res[$parentItem->title]['submenus'][] = $childItem->makeHidden($hiddenAttributes)->toArray();
            }
        }

        return $res;

проблема в том, что ответ только от WP возвращает parent_id для каждого $item и нет данных о том, есть ли у элемента дети, так что это метаданные родительского элемента, например:

         #attributes: array:4 [
            "meta_id" => 209
            "post_id" => 112
            "meta_key" => "_menu_item_menu_item_parent"
            "meta_value" => "0"
          ]

и это метаданные дочернего элемента:

          #attributes: array:4 [
            "meta_id" => 326
            "post_id" => 135
            "meta_key" => "_menu_item_menu_item_parent"
            "meta_value" => "112"
          ]

как я могу сделать это гибким и включить более глубокое гнездование, чтобы я мог иметь подменю внутри подменю?

я попытался найти решение здесь, потому что это в значительной степени та же проблема, что и моя, но не смогла реализовать его. В моем массиве пункты меню также имеют только parent_id и parent_id что это 0 является корневым элементом. Также parent_id если menu item это post на meta id, а не идентификатор post это мне нужно, поэтому мне нужно получить это дополнительно от meta->_menu_item_object_id.

обновление

мне удалось создать дерево, подобное структуре, но проблема в том, что я не знаю, как получить title для меню элементы posts. Я сделал это в предыдущем примере, проверив, если title пусто, тогда я бы искал это post by id:

          if ($item->title == '') {
              $item = $this->findPost($item);
          }

но с новым кодом, где я делаю древовидную структуру, я не уверен, как это сделать, с тех пор я не могу сделать древовидную структуру, так как я сравниваю все с id и ids элемента меню отличается от id на post, что указывает на, тогда Я не могу сделать древовидную структуру:

    private function menuBuilder($menuItems, $parentId = 0)
    {
        $hiddenAttributes = Config::get('middleton.wp.menuHiddenAttributes');
        $res = [];

        foreach ($menuItems as $index => $item) {
            $itemParentId = $item->meta->_menu_item_menu_item_parent;

            if ($itemParentId == $parentId) {
                $children = self::menuBuilder($menuItems, $item->ID);

                if ($children) {
                    $item['submenu'] = $children;
                }

                $res[$item->ID] = $item->makeHidden($hiddenAttributes)->toArray();
                unset($menuItems[$index]);
            }
        }

        return $res;
    }

Итак, тогда данные, которые я получаю:

   {
    "112": {
        "ID": 112,
        "submenu": {
            "135": {
                "ID": 135,
                "title": "",
                "slug": "135",
                "url": "http://hivnorge.app/?p=135",
                "status": "publish",
                "main_category": "Hovedmeny"
            }
        },
        "title": "Hjem",
        "slug": "hjem",
        "url": "http://hivnorge.app/?p=112",
        "status": "publish",
        "main_category": "Hovedmeny"
    },
    "136": {
        "ID": 136,
        "submenu": {
            "137": {
                "ID": 137,
                "submenu": {
                    "138": {
                        "ID": 138,
                        "title": "",
                        "slug": "138",
                        "url": "http://hivnorge.app/?p=138",
                        "status": "publish",
                        "main_category": "Hovedmeny"
                    }
                },
                "title": "",
                "slug": "137",
                "url": "http://hivnorge.app/?p=137",
                "status": "publish",
                "main_category": "Hovedmeny"
            }
        },
        "title": "",
        "slug": "136",
        "url": "http://hivnorge.app/?p=136",
        "status": "publish",
        "main_category": "Hovedmeny"
    },
    "139": {
        "ID": 139,
        "title": "",
        "slug": "139",
        "url": "http://hivnorge.app/?p=139",
        "status": "publish",
        "main_category": "Hovedmeny"
    }
}

2 ответов


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

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

для примера я создал простой плоский массив:

# some example rows as the flat array
$rows = [
    ['id' => 3, 'parent_id' => 2, 'name' => 'Subcategory A'],
    ['id' => 1, 'parent_id' => null, 'name' => 'Home'],
    ['id' => 2, 'parent_id' => null, 'name' => 'Categories'],
    ['id' => 4, 'parent_id' => 2, 'name' => 'Subcategory B'],
];

тогда для работы есть основные переменные буксировки: сначала $menu который является иерархическим массивом для создания и второго $byId которая является таблицей подстановки:

# initialize the menu structure
$menu = []; # the menu structure
$byId = []; # menu ID-table (temporary) 

таблица поиска необходима только до тех пор, пока меню построено, оно будет потом выбросить.

следующий большой шаг-создать $menu путем обхода плоского массива. Это больший цикл foreach:

# build the menu (hierarchy) from flat $rows traversable
foreach ($rows as $row) {
    # map row to local ID variables
    $id = $row['id'];
    $parentId = $row['parent_id'];

    # build the entry
    $entry = $row;
    # init submenus for the entry
    $entry['submenus'] = &$byId[$id]['submenus']; # [1]

    # register the entry in the menu structure
    if (null === $parentId) {
        # special case that an entry has no parent
        $menu[] = &$entry;
    } else {
        # second special case that an entry has a parent
        $byId[$parentId]['submenus'][] = &$entry;
    }

    # register the entry as well in the menu ID-table
    $byId[$id] = &$entry;

    # unset foreach (loop) entry alias
    unset($entry);
}

здесь записи отображаются из плоского массива ($rows) в иерархическом $menu массив. Рекурсия не требуется благодаря стеку и таблице поиска $byId.

ключевым моментом здесь является использование псевдонимов переменных (ссылок) при добавлении новых записей в $menu структура а также при добавлении их в $byId. Это позволяет получить доступ к одному и тому же значению в памяти с двумя разными именами переменных:

        # special case that an entry has no parent
        $menu[] = &$entry;
         ...

    # register the entry as well in the menu ID-table
    $byId[$id] = &$entry;

это делается с помощью тега = & задание и это означает, что $byId[$id] дает доступ к $menu[<< new key >>].

то же самое делается в случае, если он добавляется в подменю:

    # second special case that an entry has a parent
    $byId[$parentId]['submenus'][] = &$entry;
...

# register the entry as well in the menu ID-table
$byId[$id] = &$entry;

здесь $byId[$id] указывает на $menu...[ << parent id entry in the array >>]['submenus'][ << new key >> ].

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

для решения случаев, когда подменю поставляется в плоском массиве до запись меню, к которой она принадлежит, подменю при инициализации для новых записей необходимо удалить из таблицы поиска (в [1]):

# init submenus for the entry
$entry['submenus'] = &$byId[$id]['submenus']; # [1]

это немного особый случай. На случай, если это $byId[$id]['submenus'] еще не установлен (например, в первом цикле), он неявно установлен в null из-за ссылки (& перед &$byId[$id]['submenus']). В случае установите, существующее подменю из еще не существующей записи будет использоваться для инициализации подменю записи.

делать это достаточно, чтобы не зависеть от какого-либо конкретного порядка в $rows.

это то, что делает цикл.

остальное-работа по очистке:

# unset ID aliases
unset($byId); 

он сбрасывает таблицу look ID, поскольку она больше не нужна. То есть все псевдонимы не заданы.

выполнить пример:

# visualize the menu structure
print_r($menu);

что тогда дает следующий результат:

Array
(
    [0] => Array
        (
            [id] => 1
            [parent_id] => 
            [name] => Home
            [submenus] => 
        )

    [1] => Array
        (
            [id] => 2
            [parent_id] => 
            [name] => Categories
            [submenus] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [parent_id] => 2
                            [name] => Subcategory A
                            [submenus] => 
                        )

                    [1] => Array
                        (
                            [id] => 4
                            [parent_id] => 2
                            [name] => Subcategory B
                            [submenus] => 
                        )

                )

        )

)

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

связанный материал Q&A:


поэтому вам нужно будет написать рекурсивную функцию see что такое рекурсивная функция в PHP?

что-то вроде

function menuBuilder($menuItems){
    foreach($menuItems as $key => $item)
    {
        if(!empty($item->children)){
            $output[$key] = menuBuilder($item->children);
        }
    }
    return $output;
}