массив с иерархической вложенностью элементов Как создать свой сайт > Вебмастеру > Создание своего сайта > Формирование массива

Формирование массива с иерархической вложенностью элементов

Настроение людей, пришедших на зрелище, переломить трудно.
«Экспансия 1», Юлиан Семёнов.
2 апреля 2009

    Потребность вывести многоуровневое меню — частая задача, с которой сталкивается разработчик. Основным неудобством, как правило, является процесс формирования вложенности пунктов меню при использовании в качестве основы двумерного массива. Допустим, для хранения данных структуры сайта используется дерево каталогов Nestes Sets. А в целях реализации ЧПУ, для каждого раздела сайта хранится свой абсолютный путь:


// примерный массив с элементами меню, получаемый в результате выборки из БД 
Array
(
    [0] => Array
        (
            [lkey] => 10
            [title] => О компании
            [path] => /about
        )
    [1] => Array
        (
            [lkey] => 11
            [title] => История
            [path] => /about/history
        )
    [2] => Array
        (
            [lkey] => 12
            [title] => Контактная информация
            [path] => /about/contacts
        )
    [3] => Array
        (
            [lkey] => 13
            [title] => Пресс-центр
            [path] => /press
        )
    [4] => Array
        (
            [lkey] => 14
            [title] => Новости
            [path] => /press/news
        )
    [5] => Array
        (
            [lkey] => 15
            [title] => Новости компании
            [path] => /press/news/company
        )
    [6] => Array
        (
            [lkey] => 16
            [title] => Новости отрасли
            [path] => /press/news/branch
        )
)

    Назовём этот массив $menuArray. А теперь представим, что из него нужно сформировать следующий код:

<ul>
  <li>
    <a href="/about">О компании</a>
    <ul>
      <li>
        <a href="/about/history">История</a>
      </li>
      <li>
        <a href="/about/contacts">Контактная информация</a>
      </li>
    </ul>
  </li>
  <li>
    <a href="/press">Пресс-центр</a>
    <ul>
      <li>
        <a href="/press/news">Новости</a>
        <ul>
          <li>
            <a href="/press/news/company">Новости компании</a>
          </li>
          <li>
            <a href="/press/news/branch">Новости отрасли</a>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

    Думаю, многие согласятся с утверждением, что выводить многоуровневое меню с помощью рекурсии гораздо удобнее, чем напрямую обрабатывать двумерный массив. Ведь во втором случае приходится ломать голову, когда нужно открыть вложенный список UL, когда его закрыть и сколько раз. Если Вам эти слова ни о чём не говорят, то Вы гений или — что более вероятно — пока что не сталкивались с такой задачей.

    Но для того, чтобы использовать рекурсионный вызов, необходимо иметь массив с иерархической вложенностью данных о пунктах меню. Примерно так должен выглядеть массив $menuTree:

// дочерние элементы каждого пункта меню размещены в ключах childNodes
Array
(
    [about] => Array
        (
            [lkey] => 10
            [title] => О компании
            [path] => /about
            [childNodes] => Array
                (
                    [history] => Array
                        (
                            [lkey] => 11
                            [title] => История
                            [path] => /about/history
                        )
                    [contacts] => Array
                        (
                            [lkey] => 12
                            [title] => Контактная информация
                            [path] => /about/contacts
                        )
                )
        )
    [press] => Array
        (
            [lkey] => 13
            [title] => Пресс-центр
            [path] => /press
            [childNodes] => Array
                (
                    [news] => Array
                        (
                            [lkey] => 14
                            [title] => Новости
                            [path] => /press/news
                            [childNodes] => Array
                                (
                                    [company] => Array
                                        (
                                            [lkey] => 15
                                            [title] => Новости компании
                                            [path] => /press/news/company
                                        )

                                    [branch] => Array
                                        (
                                            [lkey] => 16
                                            [title] => Новости отрасли
                                            [path] => /press/news/branch
                                        )
                                )
                        )
                )
        )
)

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

// функция вывода меню
function print_menu($menu)
{
  echo '<ul>';
  foreach ($menu as $key => $item)
  {
    echo '<li>';
    echo '<a href="'.$item['path'].'">'.$item['title'].'</a>';
    if (!empty($item['childNodes']))
    {
      print_menu($item['childNodes']);
    }
    echo '</li>';
  }
  echo '</ul>';
}

// чтобы отобразить меню, нужно вызвать рассмотренную функцию
print_menu($menu);

    Данный подход намного элегантнее формирования меню из двумерного массива, потому что не возникает путаницы: когда открывать и закрывать вложенные списки; а объём кода раза в два меньше (а то и в три). И речь идёт не о сэкономленных байтах — в коротком коде проще разобраться.

Внимание! Как из двумерного массива получить массив с иерархической вложенностью?

Легко. Необходимо воспользоваться функцией, которая была создана на основании статьи Convert anything to Tree Structures in PHP:

/**
 * Построение массива с иерархической вложенностью элементов.
 * 
 * @param array $array  Исходный массив
 * @param string $value_key  Элемент, который содержит строку для разбора
 * @return bool|array
 */
function buildLevelTree($array, $value_key)
{
  if (!is_array($array)) return FALSE;

  // регулярное выражение для формирования дерева
  $splitRE = "///";

  // исходное пустое дерево
  $tree = array();

  // меняем ключи у исходного массива
  $array_mod_keys = array();
  while (list($key, $val) = each($array))
  {
    $array_mod_keys[$val[$value_key]] = $val;
  }

  // заменяем исходный массив новым (с обновлёнными ключами)
  $array = $array_mod_keys;
  unset($array_mod_keys);

  // формируем дерево
  foreach ($array as $key => $val)
  {
    // определяем родительские ($parts)и текущий ($leafPart) сегменты
    $parts = preg_split($splitRE, $key, -1, PREG_SPLIT_NO_EMPTY);
    $leafPart = array_pop($parts);

    // построение родительской структуры (для очень глубоких структур может работать медленно)
    $parentArr = &$tree;
    foreach ($parts as $part)
    {
      if (!isset($parentArr[$part]))
      {
        $parentArr[$part] = array();
      }
      elseif (!is_array($parentArr[$part]))
      {
        $parentArr[$part] = array();
      }
      if (!empty($parentArr[$part]))
      {
        $parentArr = &$parentArr[$part]['childNodes'];
      }
    }

    // добавление финального сегмента в структуру
    if (empty($parentArr[$leafPart]))
    {
      $parentArr[$leafPart] = $val;
    }
  }

  // удаляем пустые элементы
  foreach ($tree as $k => $val)
  {
    if (empty($val))
      unset($tree[$k]);
  }

  // возвращаем сгенерированный результат
  return $tree;
}

Пример использования

    Итак, у нас есть исходный массив $menuArray, функция вывода меню print_menu() и функция buildLevelTree() для преобразования исходных данных в массив с иерархической вложенностью. Воспользоваться таким арсеналом легко и приятно:

// формируем массив с иерархической вложенностью;
// второй параметр функции определяет, 
// в каком ключе элемента меню содержится значение пути к нему
$menuTree = buildLevelTree($menuArray, 'path');

// выводим меню
print_menu($menuTree);

Автор: Никита Мосияш.

Комментарии:

stanis
Nested sets, Никита, nested sets. Спеллчеккеры рулят — особенно, если быстро печатаешь.

irgik
Здравствуйте. Спасибо огромное за статью. Все отлично, но есть один вопрос:
для хранения дерева я использую метод материализованых путей. Возможно ли как-то модифицировать скрипт, чтоб была возможность выводить не только все дерево, а еще и его части (к примеру начиная со второго уровня и до конца)?

stanis
Тогда проще использовать метод Джо Селко. Но он медленнее на вставках.


⇓ 

Поделись ссылкой на Seoded.ru с друзьями, знакомыми и собеседниками в соцсетях и на форумах! А сам сайт добавь в закладки! Так победим.

Поделиться ссылкой на эту страницу в:

Полезные ссылки:

Дополнительный заработок для копирайтера в Интернете Что нужно знать о Форексе новичку

Ещё материалы по этой теме:

PHP-генератор паролей CSS-свойство «!important» Масштабные тонкости Шаманство с покупкой сайтов 6 шагов для создания сайта на Drupal
основан в 2008 г. © Все права на материалы сайта Seoded.ru принадлежат Алексею Вострову.
Копирование (полное или частичное) любых материалов сайта возможно только с разрешения автора и при указании ссылки на источник.
Ослушавшихся находит и забирает Бабайка!