1c-bitrix / bitrix api / development / php · 19.01.2014

Разделы инфоблока в виде массива 1С-Битрикс

Что такое список разделов в информационном блоке, это данные структурированные в виде дерева.

300px-Binary_tree.svg

Есть несколько вариантов обхода дерева, вот что говорит wikipedia

Пошаговый перебор элементов дерева по связям между узлами-предками и узлами-потомками называется обходом дерева. Зачастую, операция может быть выполнена переходом указателя по отдельным узлам. Обход, при котором каждый узел-предок просматривается прежде его потомков называется предупорядоченным обходом или обходом в прямом порядке (pre-order walk), а когда просматриваются сначала потомки, а потом предки, то обход называется поступорядоченным обходом или обходом в обратном порядке (post-order walk). Существует также симметричный обход, при котором посещается сначала левое поддерево, затем узел, затем — правое поддерево, и обход в ширину, при котором узлы посещаются уровень за уровнем (N-й уровень дерева — множество узлов с высотой N). Каждый уровень обходится слева направо.

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

$rs_Section = CIBlockSection::GetList(array('left_margin' => 'asc'), array('IBLOCK_ID' => 8));
while ( $ar_Section = $rs_Section->Fetch() )
{
	$ar_Result[] = array(
		'ID' => $ar_Section['ID'],
		'NAME' => $ar_Section['NAME'],
		'IBLOCK_SECTION_ID' => $ar_Section['IBLOCK_SECTION_ID'],
		'IBLOCK_SECTION_ID' => $ar_Section['IBLOCK_SECTION_ID'],
		'LEFT_MARGIN' => $ar_Section['LEFT_MARGIN'],
		'RIGHT_MARGIN' => $ar_Section['RIGHT_MARGIN'],
		'DEPTH_LEVEL' => $ar_Section['DEPTH_LEVEL'],
	);
}

Возвращает массив вида

Array
(
    [0] => Array
        (
            [ID] => 5335
            [NAME] => USB провода
            [IBLOCK_SECTION_ID] => 
            [LEFT_MARGIN] => 1
            [RIGHT_MARGIN] => 2
            [DEPTH_LEVEL] => 1
        )

    [1] => Array
        (
            [ID] => 5171
            [NAME] => Аккумуляторы по моделям
            [IBLOCK_SECTION_ID] => 
            [LEFT_MARGIN] => 3
            [RIGHT_MARGIN] => 48
            [DEPTH_LEVEL] => 1
        )

    [2] => Array
        (
            [ID] => 5172
            [NAME] => Для Acer
            [IBLOCK_SECTION_ID] => 5171
            [LEFT_MARGIN] => 4
            [RIGHT_MARGIN] => 5
            [DEPTH_LEVEL] => 2
        )

    [3] => Array
        (
            [ID] => 5173
            [NAME] => Для Advent
            [IBLOCK_SECTION_ID] => 5171
            [LEFT_MARGIN] => 6
            [RIGHT_MARGIN] => 7
            [DEPTH_LEVEL] => 2
        )

    [4] => Array
        (
            [ID] => 5174
            [NAME] => Для Apple
            [IBLOCK_SECTION_ID] => 5171
            [LEFT_MARGIN] => 8
            [RIGHT_MARGIN] => 9
            [DEPTH_LEVEL] => 2
        )
...
)

Видно что у раздела Аккумуляторы по моделям есть подразделы и они находятся сразу под своим родителем, в этом и заключается особенность такой выборки, мы получаем ассоциативный массив с отсортированный структурой дерева. Для вывода на сайте это вполне удобно, в основном это применяется в выводе в тэге <select> для фильтра по категории или просто ссылками

Скриншот 2014-01-19 12.45.46

Код выводящий select

<select>
    <option> - Выбрать - </option>
    <?
    foreach( $ar_Result as $ar_Value )
    {
        $s = '';
        for($i = 1; $i <= $ar_Value['DEPTH_LEVEL']; $i++ )
            $s .= '.';
        
        $s .= ' '.$ar_Value['NAME'];
        
        ?><option value="<?=$ar_Value['ID']?>"><?=$s?></option><?
    }
    ?>
</select>

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

$rs_Section = CIBlockSection::GetList(
	array('DEPTH_LEVEL' => 'desc'),
	$ar_Filter,
	false,
	array('ID', 'NAME', 'IBLOCK_SECTION_ID', 'DEPTH_LEVEL', 'SORT')
);
$ar_SectionList = array();
$ar_DepthLavel = array();
while($ar_Section = $rs_Section->GetNext(true, false))
{
	$ar_SectionList[$ar_Section['ID']] = $ar_Section;
	$ar_DepthLavel[] = $ar_Section['DEPTH_LEVEL'];
}

$ar_DepthLavelResult = array_unique($ar_DepthLavel);
rsort($ar_DepthLavelResult);

$i_MaxDepthLevel = $ar_DepthLavelResult[0];

for( $i = $i_MaxDepthLevel; $i > 1; $i-- )
{
	foreach ( $ar_SectionList as $i_SectionID => $ar_Value )
	{
		if( $ar_Value['DEPTH_LEVEL'] == $i )
		{
			$ar_SectionList[$ar_Value['IBLOCK_SECTION_ID']]['SUB_SECTION'][] = $ar_Value;
			unset( $ar_SectionList[$i_SectionID] );
		}
	}
}

function __sectionSort($a, $b)
{
	if ($a['SORT'] == $b['SORT']) {
		return 0;
	}
	return ($a['SORT'] < $b['SORT']) ? -1 : 1;
}

usort($ar_SectionList, "__sectionSort");

Использовать будем поступорядоченный метод обхода, по этому сначала найдем максимальный  DEPTH_LEVEL, получаем его в переменную $i_MaxDepthLevel и начинаем в цикле собирать массив с самих глубоких по иерархии разделов. Раздел после добавления в массив родитель удаляем  unset( $ar_SectionList[$i_SectionID] ); 

Важный момент, сортировка списка должно идти именно в таком направлении array(‘DEPTH_LEVEL’ => ‘desc’) при этом подготовленный массив разделов перед сборкой будет иметь вид

 [5097] => Array
        (
            [ID] => 5097
            [NAME] => LENOVO
            [IBLOCK_SECTION_ID] => 5087
            [DEPTH_LEVEL] => 4
            [SORT] => 400
        )

    [5100] => Array
        (
            [ID] => 5100
            [NAME] => SONY
            [IBLOCK_SECTION_ID] => 5087
            [DEPTH_LEVEL] => 4
            [SORT] => 430
        )

    [5104] => Array
        (
            [ID] => 5104
            [NAME] => ACER
            [IBLOCK_SECTION_ID] => 5103
            [DEPTH_LEVEL] => 4
            [SORT] => 640
        )

    [5105] => Array
        (
            [ID] => 5105
            [NAME] => ASUS
            [IBLOCK_SECTION_ID] => 5103
            [DEPTH_LEVEL] => 4
            [SORT] => 650
        )

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

[6] => Array
(
	[ID] => 5086
	[NAME] => Кабели
	[IBLOCK_SECTION_ID] => 5425
	[DEPTH_LEVEL] => 2
	[SORT] => 290
	[SUB_SECTION] => Array
	(
		[0] => Array
		(
			[ID] => 5087
			[NAME] => Шлейфы для матриц
			[IBLOCK_SECTION_ID] => 5086
			[DEPTH_LEVEL] => 3
			[SORT] => 300
			[SUB_SECTION] => Array
			(
				[0] => Array
				(
					[ID] => 5097
					[NAME] => LENOVO
					[IBLOCK_SECTION_ID] => 5087
					[DEPTH_LEVEL] => 4
					[SORT] => 400
				)
...

Как видим удалось построить массив, но как теперь его выводить и вообще работать с ним? Во первых мы можем обращаться к определенным разделам по ключам, выдергивать части массива и тд, все что можно сделать с массивом данных.

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

function __recursivRenderMenu($ar_Items)
{
    foreach ($ar_Items as $ar_Value)
    {
        if( count($ar_Value['SUB_SECTION']) > 0  )
        {
            echo '<ul>';
                echo '<li>';
                    echo $ar_Value['NAME'];
                    echo '<ul>';

                        # рекурсивный вызов функции
                        echo __recursivRenderMenu($ar_Value['SUB_SECTION']);

                    echo '</ul>';
                echo '</li>';
            echo '</ul>';                
        }    
        else
        {
            echo '<li>'.$ar_Value['NAME'].'</li>';
        }    
    }    
}

echo __recursivRenderMenu($ar_SectionList);

На выходе получим

tree

Если есть способы сделать тоже самое легче, быстрее, правильнее и тд, прошу написать в комментарии :)