В практике разработки на платформе 1С:Предприятие часто возникает архитектурная задача: необходимо изменить поведение кода в модуле объекта или общем модуле в зависимости от того, какая именно команда на форме инициировала процесс. Ситуация осложняется, когда между нажатием кнопки («Заполнить» или «Подбор») и конечной процедурой находится «слоеный пирог» из 10–15 промежуточных вызовов. В этой статье мы подробно разберем, как пробросить информацию об источнике вызова, не нарушая логику системы.
Это самый «честный» и прозрачный с точки зрения архитектуры способ. Мы добавим в сигнатуру каждой процедуры в цепочке дополнительный необязательный параметр. Рассмотрим, как это реализовать, чтобы не сломать существующие вызовы в других частях системы.
Начнем с начальной точки — команды на форме. Допустим, у нас есть процедура в общем модуле, которая вызывается из обеих команд:
// В модуле формы
&НаСервере
Процедура КомандаПодборНаСервере()
// Передаем признак источника в первую процедуру цепочки
ОбщийМодульСервер.ВыполнитьОбработкуДанных(Объект, "Подбор");
КонецПроцедуры
&НаСервере
Процедура КомандаЗаполнитьНаСервере()
ОбщийМодульСервер.ВыполнитьОбработкуДанных(Объект, "Заполнить");
КонецПроцедуры
Теперь разберем, как должны выглядеть промежуточные процедуры. Чтобы не переписывать сотни вызовов по всей конфигурации, используем значение параметра по умолчанию:
// В общем модуле
Процедура ВыполнитьОбработкуДанных(ДокументОбъект, Источник = "") Экспорт
// ... какой-то код ...
ПромежуточнаяПроцедура_1(ДокументОбъект, Источник);
КонецПроцедуры
Процедура ПромежуточнаяПроцедура_1(ДокументОбъект, Источник = "")
// ... цепочка продолжается ...
ДокументОбъект.ВыполнитьДействиеВМодулеОбъекта(Источник);
КонецПроцедуры
Преимущества: Полная типизация, отсутствие скрытых зависимостей, легкая отладка.
Недостатки: Если процедур действительно 15, придется изменить заголовок каждой из них.
Объект 1С (например, ДокументОбъект) имеет коллекцию ДополнительныеСвойства, которая представляет собой структуру. Это «карман», в который можно положить любые данные, и они будут путешествовать вместе с экземпляром объекта в пределах серверного вызова.
Проанализируем ситуацию, почему у автора вопроса свойства «очищались». Важно понимать: ДополнительныеСвойства живут только в конкретном экземпляре объекта в оперативной памяти. Если где-то в цепочке вызовов происходит получение объекта из ссылки заново (например, Ссылка.ПолучитьОбъект()), создается новый экземпляр с пустой структурой свойств.
Правильный алгоритм работы выглядит так:
// В начале цепочки (на сервере)
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ДополнительныеСвойства.Вставить("ИмяКоманды", "Подбор");
// Вызываем цепочку, передавая именно этот ДокументОбъект
ОбщийМодуль.НачатьЦепочку(ДокументОбъект);
В модуле объекта мы просто проверяем наличие этого свойства:
Процедура ВыполнитьДействиеВМодулеОбъекта(Параметры) Экспорт
ИмяКоманды = "";
Если ДополнительныеСвойства.Свойство("ИмяКоманды", ИмяКоманды) Тогда
Если ИмяКоманды = "Подбор" Тогда
// Логика специально для подбора
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Бывают случаи, когда менять 15 процедур категорически нельзя (например, это типовой код, который нельзя трогать). В 1С нет функции ПолучитьСтекВызовов(), но есть технический прием через генерацию исключения. Выясним, как это работает.
Мы можем вызвать ошибку «тихо», перехватить её и проанализировать текст ошибки, в котором платформа сохраняет весь путь вызова процедур.
Процедура ОпределитьИсточникВМодулеОбъекта()
ТекстСтека = "";
Попытка
// Вызываем исключение искусственно (например, деление на ноль)
А = 1/0;
Исключение
// Получаем подробное описание ошибки, содержащее стек
ТекстСтека = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Если Найти(ТекстСтека, "КомандаПодбор") > 0 Тогда
// Мы пришли сюда из команды Подбор
ИначеЕсли Найти(ТекстСтека, "КомандаЗаполнить") > 0 Тогда
// Мы пришли сюда из команды Заполнить
КонецЕсли;
КонецПроцедуры
Важно: Этот метод считается «грязным» и медленным (генерация исключения — ресурсоемкая операция). Используйте его только в крайних случаях для отладки или если другие методы недоступны.
В профессиональной разработке (например, в Библиотеке Стандартных Подсистем) вместо передачи десяти отдельных параметров принято передавать одну структуру ПараметрыВыполнения. Рассмотрим этот подход подробнее.
Если ваши процедуры уже спроектированы так, что они принимают структуру настроек, вам не нужно менять заголовки 15 функций. Вы просто добавляете новое поле в структуру в самом начале цепочки.
// Начало
ПараметрыАлгоритма = Новый Структура;
ПараметрыАлгоритма.Вставить("РежимРаботы", "Быстрый");
ПараметрыАлгоритма.Вставить("ИсточникВызова", "Подбор"); // Добавляем контекст
// Вся цепочка просто передает эту структуру дальше
Процедура Шаг15(ПараметрыАлгоритма)
Если ПараметрыАлгоритма.ИсточникВызова = "Подбор" Тогда
// ...
КонецЕсли;
КонецПроцедуры
Этот подход делает код расширяемым: если завтра вам понадобится передать еще и ID пользователя или время нажатия кнопки, вы просто добавите их в структуру, не меняя промежуточные модули.
Если цепочка вызовов прерывается (например, выполнение уходит в фоновое задание или происходит возврат на клиент и снова на сервер), ДополнительныеСвойства будут потеряны. В этом случае на помощь приходит ВременноеХранилище.
Разберем алгоритм по шагам:
УникальныйИдентификатор формы как ключ.
// В модуле формы
АдресХранилища = ПоместитьВоВременноеХранилище("Подбор", ЭтаФорма.УникальныйИдентификатор);
ОбщийМодуль.ВыполнитьЦепочку(Объект, АдресХранилища);
// В модуле объекта
Процедура ВыполнитьДействие(АдресХранилища = "")
Если ЗначениеЗаполнено(АдресХранилища) Тогда
ИмяКоманды = ПолучитьИзВременногоХранилища(АдресХранилища);
// ...
КонецЕсли;
КонецПроцедуры
Проанализировав все варианты, можно сделать следующие выводы:
ДополнительныеСвойства.Выбор метода зависит от сложности вашей цепочки вызовов и того, насколько жестко вы ограничены рамками типовой конфигурации. Мы рекомендуем всегда стремиться к явной передаче данных, так как это упрощает понимание кода другими разработчиками.