Сортировка меню 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 2

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