При разработке сложных инструментов администрирования, генераторов документации или систем управления правами доступа часто возникает необходимость программно получить полный список объектов, входящих в ту или иную подсистему. В 1С:Предприятие подсистемы имеют иерархическую структуру, что требует особого подхода к обходу их состава. В этой статье мы подробно разберем, как реализовать рекурсивный обход метаданных, определить тип каждого объекта и корректно вывести результат в дерево значений.
Для начала проанализируем, как информация о подсистемах хранится в платформе. Каждый объект метаданных (справочник, документ, регистр) имеет свойство Подсистемы, которое содержит коллекцию подсистем, к которым он привязан. С другой стороны, сам объект Подсистема имеет свойство Состав, где перечислены все входящие в него объекты.
Важно учитывать разницу между версиями платформы. В старых версиях (8.1) и некоторых режимах совместимости работа с этими коллекциями могла отличаться, но базовый принцип остается неизменным: подсистема может содержать как объекты конфигурации, так и другие (подчиненные) подсистемы. Именно это диктует нам использование рекурсивного алгоритма для получения полной картины.
Это наиболее прямой метод. Мы берем конкретную подсистему, читаем её состав, а затем переходим к её дочерним подсистемам. Рассмотрим по шагам, как это реализовать. Сначала подготовим дерево значений для вывода данных.
// Подготовка дерева значений для вывода
ТаблицаВыводаДерева = Новый ДеревоЗначений;
ТаблицаВыводаДерева.Колонки.Добавить("ИмяОбъекта");
ТаблицаВыводаДерева.Колонки.Добавить("ТипОбъекта");
ТаблицаВыводаДерева.Колонки.Добавить("СсылкаНаМетаданные");
Теперь разберем основной алгоритм обхода. Создадим процедуру, которая будет принимать объект подсистемы и строку дерева, в которую нужно добавлять данные. Выясним причину использования рекурсии: подсистемы могут иметь неограниченную вложенность, и простой цикл не позволит нам добраться до самого нижнего уровня.
Процедура ПолучитьСоставПодсистемыРекурсивно(ТекущаяПодсистема, СтрокаРодитель)
// 1. Обрабатываем объекты, входящие непосредственно в текущую подсистему
Для Каждого ОбъектМетаданных Из ТекущаяПодсистема.Состав Цикл
НоваяСтрока = СтрокаРодитель.Строки.Добавить();
НоваяСтрока.ИмяОбъекта = ОбъектМетаданных.Имя;
НоваяСтрока.СсылкаНаМетаданные = ОбъектМетаданных;
// Определяем тип объекта для наглядности
НоваяСтрока.ТипОбъекта = ПолучитьТипОбъектаМетаданных(ОбъектМетаданных);
КонецЦикла;
// 2. Рекурсивно заходим во вложенные подсистемы
Для Каждого ВложеннаяПодсистема Из ТекущаяПодсистема.Подсистемы Цикл
СтрокаПодсистемы = СтрокаРодитель.Строки.Добавить();
СтрокаПодсистемы.ИмяОбъекта = "Подсистема: " + ВложеннаяПодсистема.Имя;
// Вызываем эту же процедуру для вложенного узла
ПолучитьСоставПодсистемыРекурсивно(ВложеннаяПодсистема, СтрокаПодсистемы);
КонецЦикла;
КонецПроцедуры
В процессе обхода коллекции Состав мы получаем объекты типа ОбъектМетаданных. Нам нужно понять, является ли этот объект справочником, документом или, например, обработкой. Посмотрим на пример функции, которая помогает идентифицировать тип:
Функция ПолучитьТипОбъектаМетаданных(ОбъектМетаданных)
// Самый надежный способ в 8.2/8.3 - использование ПолноеИмя()
ПолноеИмя = ОбъектМетаданных.ПолноеИмя();
ПерваяТочка = Найти(ПолноеИмя, ".");
Если ПерваяТочка > 0 Тогда
Возврат Лев(ПолноеИмя, ПерваяТочка - 1);
КонецЕсли;
Возврат "Не определено";
КонецФункции
Рассмотрим подробнее альтернативный вариант, который упоминался на форумах. Можно использовать метод Содержит() у коллекций метаданных. Однако этот метод менее производителен, если вам нужно проверять объект на соответствие всем возможным типам (справочники, документы, отчеты и т.д.), так как придется перебирать все коллекции Метаданные.Справочники.Содержит(Объект), Метаданные.Документы.Содержит(Объект) и так далее.
Иногда задача стоит иначе: нужно вывести дерево всех метаданных конфигурации, распределенных по подсистемам. В этом случае мы можем пойти «от противного» — перебирать все объекты конфигурации и смотреть, в какие подсистемы они входят. Проанализируем ситуацию: этот метод хорош, когда один объект может входить в несколько подсистем, и нам важно это отразить.
Процедура ЗаполнитьДеревоПоВсемДокументам()
Для Каждого Док Из Метаданные.Документы Цикл
Для Каждого ПодсистемаОбъекта Из Док.Подсистемы Цикл
// Ищем строку подсистемы в нашем дереве
СтрокаДерева = НайтиСтрокуПодсистемыВДереве(ПодсистемаОбъекта);
Если СтрокаДерева <> Неопределено Тогда
НовСтрока = СтрокаДерева.Строки.Добавить();
НовСтрока.ИмяОбъекта = Док.Имя;
НовСтрока.ТипОбъекта = "Документ";
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Важный нюанс: метод Найти() в дереве значений по колонке, содержащей объект метаданных, может работать нестабильно в некоторых версиях платформы, если сравнивать объекты напрямую. Рекомендуется использовать уникальный идентификатор или полное имя объекта метаданных для поиска узла в дереве.
При выводе в дерево значений часто возникает путаница с родительскими узлами. Чтобы дерево выглядело логично, сначала нужно полностью построить структуру самих подсистем, а уже потом наполнять их объектами. Разберем по шагам правильный порядок действий:
Метаданные.Подсистемы, создавая пустые ветки в дереве.Состав.СоздатьКолонки() для элемента формы ТаблицаЦикла, чтобы визуализировать результат.Проанализируем типичную ошибку начинающих разработчиков: попытку получить состав через Метаданные.Подсистемы.ИмяПодсистемы.Подсистемы.Состав. Помните, что Состав — это коллекция, которую нужно обходить циклом Для Каждого, а не пытаться получить её элементы по индексу без проверки их наличия.
Мы рассмотрели основные механизмы работы с составом подсистем. Главный инструмент программиста здесь — рекурсия и объектная модель Метаданные. Правильное использование свойства ПолноеИмя() позволяет быстро определять типы объектов, а иерархический обход коллекций Состав и Подсистемы дает возможность построить полную карту конфигурации. Эти знания помогут вам в создании мощных инструментов для анализа и модификации метаданных в любой конфигурации на платформе 1С.