Сортировка меню PHP MySQL
Итак, вот моя структура таблицы:
+--------------------------+ +----------------+ +-------------------------------+
| pages | | menus | | menu_pages |
+--------------------------+ +----+-----------+ +-------------------------------+
| id | title | slug | | id | name | | menu_id | page_id | parent_id |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 1 | Home | index | | 1 | default | | 1 | 1 | 0 |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 2 | About | about | | 2 | footer | | 1 | 2 | 0 |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 3 | Test 1 | test-1 | | 1 | 3 | 2 |
+-------+---------+--------+ +---------+---------+-----------+
| 4 | Test 2 | test-2 | | 1 | 4 | 2 |
+-------+---------+--------+ +---------+---------+-----------+
| 5 | Test 3 | test-3 | | 1 | 5 | 4 |
+-------+---------+--------+ +---------+---------+-----------+
Таким образом, в основном, у нас есть страницы, меню и таблица ссылок menu_pages, которая определяет меню, страницу и родительский элемент каждого элемента меню.
Вот мой запрос:
$query = "SELECT pages.id, pages.title, pages.slug, menu_pages.parent_id
FROM menus, pages, menu_pages WHERE menus.name = '$menu'
AND menus.id = menu_pages.menu_id
AND pages.id = menu_pages.page_id";
$results = $db->Query($query);
Вот вопрос: как мне правильно вложить пункты меню под их соответствующими родителями в массив? Я уже пробовал довольно много вещей, но ни одна из них не работала дальше просто 2 уровней, поэтому я не буду загромождать вопрос этим. Очевидно, Я нужна какая-то рекурсия в PHP, или изменить мой запрос может быть таким образом, что я могу получить SQL, чтобы вернуть их должным образом?
Это должно выглядеть примерно так в выходных данных:
[0] => array(
'id' => 1,
'title' => 'Home',
'slug' => '/',
'parent_id' => '0'
)
[1] => array(
'id' => 2,
'title' => 'About',
'slug' => 'about',
'parent_id' => 0,
'sub_menu' => array(
[0] => array(
'id' => 3,
'title' => 'Test 1',
'slug' => 'test-1',
'parent_id' => 2
)
[1] => array(
'id' => 4,
'title' => 'Test 2',
'slug' => 'test-2',
'parent_id' => '2',
'sub_menu' => array(
[0] => array(
'id' => 5,
'title' => 'Test 3',
'slug' => 'test-3',
'parent_id' => 4
)
)
)
)
)
Спасибо за помощь!
1 ответ:
Это не так просто, как кажется на первый взгляд - если вы хотите разобраться, как это сделать с SQL, вы ищете рекурсивный оператор sql.
К сожалению, mysql не поддерживает это напрямую, и вам нужно было бы написать тело кода, чтобы заставить его работать. Вот пример того, как это сделать. Непростой.
На случай, если вам интересно, как это разобрать, вы бы реализовали его в Oracle следующим образом:
SELECT p.id, p.title, p.slug, mp.parent_id, level FROM menu_pages mp JOIN pages p ON ( p.id = mp.page_id ) JOIN menus m ON ( m.id = mp.menu_id ) CONNECT BY PRIOR mp.page_id = mp.parent_id START WITH ( m.name = 'default' AND mp.parent_id = 0 )
Вы в основном говорите:
- начните с запроса верхнего уровня меню
- соедините это обратно с результирующим набором, присоединив родителя к ребенку
В итоге получается такой набор результатов:
id title slug parent level ------------------------------------ 1 Home index 0 1 2 About about 0 1 3 Test 1 test-1 2 2 4 Test 2 test-2 2 2 5 Test 3 test-3 4 3
Все это на самом деле дает вам в дополнение к тому, что у вас уже есть:
- "Уровень" каждой точки в меню.
- Если подменю появилось несколько раз в вашем структура его повторялась бы правильно.
- разделы меню, которые не подключены должным образом, не будут возвращены.
Итак, для небольших, простых и последовательных меню это, вероятно, перебор в любом случае.
Плюс, даже в этом случае вам нужно будет обработать это в PHP, чтобы получить структуру, которую вы ищете.
Итак, используя это как вдохновение, вы можете увидеть, как вы могли бы реализовать его в mysql, просто выполнив постобработку.
Вы начинаете с вашего исходный запрос:
SELECT pages.id , pages.title , pages.slug , menu_pages.parent_id FROM menus , pages , menu_pages WHERE menus.name = 'default' AND menus.id = menu_pages.menu_id AND pages.id = menu_pages.page_id
Затем вы можете выполнить цикл над этим результатом и построить структуру массива самостоятельно вручную.
Чтобы избежать проблемы рекурсии, мы вместо этого воспользуемся тем, что у нас могут быть две переменные, указывающие на одну и ту же структуру данных - мы будем использовать ссылки, чтобы изменение значения переменной в одной ссылке изменяло значение переменной в другой.
Т. е. трудность, которую вы получаете, заключается в поиске правильного точка в иерархии, чтобы добавить каждого ребенка. С рекомендациями вы не должны.
- создайте пустой массив меню
- цикл над результатами из вашего оператора SQL
- создайте копию каждого пункта меню и поместите его в простое индексированное хранилище (по идентификатору элемента)
- Если у вас есть корневой пункт меню, добавьте его в массив меню (в качестве ссылки)
- Если у вас нет пункта корневого меню, найдите родительский элемент в своем простом магазине и добавьте новый элемент в оно.
В конце у вас должна быть хорошая вложенная структура, которую вы ищете.
Вот так:
<?php // As if it came back from mysql... // Assumed that it's ordered so that every possible parent appears before all its childern $aResults = array( array( 'id' => 1, 'title' => 'Home', 'slug' => 'index', 'parent_id' => 0 ) , array( 'id' => 2, 'title' => 'About', 'slug' => 'about', 'parent_id' => 0 ) , array( 'id' => 3, 'title' => 'Test 1', 'slug' => 'test-1', 'parent_id' => 2 ) , array( 'id' => 4, 'title' => 'Test 2', 'slug' => 'test-2', 'parent_id' => 2 ) , array( 'id' => 5, 'title' => 'Test 3', 'slug' => 'test-3', 'parent_id' => 4 ) ); // the menu you're creating $aMenu = array(); // the simple store of the menu items you're going to use to find the parents $aBaseMenuIndex = array(); foreach( $aResults as $aMenuItem ) { $aMenuItem['sub_menu'] = array(); // add your menu item to the simple store $aBaseMenuIndex[ $aMenuItem['id'] ] = $aMenuItem; if ( $aMenuItem['parent_id'] == 0 ) { // if it's a base menu item, add it to the menu $aMenu[] =& $aBaseMenuIndex[ $aMenuItem['id'] ]; } else { // if it's not a base item, add it to the sub menu, using the simply indexed store to find it // adding it here will also add it to $aMenu, as $aMenu contains a reference to this $aBaseMenuIndex[ $aMenuItem['parent_id'] ]['sub_menu'][] =& $aBaseMenuIndex[ $aMenuItem['id'] ]; } } var_dump( $aMenu );