Есть свойство объекта 1С или нет — вопрос, который встаёт, как только код должен работать с разными данными: универсальная внешняя обработка для УТ и БП, общий модуль для нескольких видов документов, расширение, которое ставят на разные релизы типовой. Прямое обращение Объект.Склад к полю, которого нет, останавливает код ошибкой «Поле объекта не обнаружено (Склад)». Ниже — как проверить реквизит и свойство заранее: по метаданным, функциями БСП и через ЗаполнитьЗначенияСвойств, как быть с методами и чем каждый способ платит за удобство.
| Что проверяем | Способ | Где работает |
|---|---|---|
| Реквизит справочника или документа | Метаданные().Реквизиты.Найти(Имя), в БСП — ОбщегоНазначения.ЕстьРеквизитОбъекта | Сервер |
| Свойство любого значения: объект, данные формы, форма, строка таблицы, выборка | ЗаполнитьЗначенияСвойств в структуру с маркером, в БСП — ЕстьРеквизитИлиСвойствоОбъекта | Клиент и сервер |
| Ключ структуры, параметры формы | Структура.Свойство(Ключ) | Клиент и сервер |
| Колонка таблицы значений или результата запроса | Колонки.Найти(Имя) | Сервер |
| Элемент формы | Элементы.Найти(Имя) | Клиент и сервер |
| Общий модуль, подсистема | Метаданные.ОбщиеМодули.Найти(Имя), в БСП — ОбщегоНазначения.ПодсистемаСуществует | Сервер |
| Процедура или функция | Попытка + Выполнить | Сервер |
Самый прямой путь — спросить у метаданных. Данные из базы при этом не читаются, и результат не зависит от того, что лежит в поле. Нужен объект метаданных: у ссылки и у объекта его возвращает метод Метаданные(), по типу значения — Метаданные.НайтиПоТипу(ТипЗнч(Значение)). На тонком клиенте глобального свойства Метаданные нет, так что проверка выполняется на сервере.
Подвох в том, что коллекция Реквизиты содержит только реквизиты, которые добавил разработчик. «Наименование», «Код», «Дата», «Номер», «Владелец» лежат в СтандартныеРеквизиты, табличные части — в ТабличныеЧасти. Функция ниже смотрит все три коллекции:
// Общий модуль с флагом «Сервер».
// Есть ли у справочника или документа реквизит, табличная часть
// или стандартный реквизит с именем ИмяПоля.
Функция ЕстьПолеВМетаданных(МетаданныеОбъекта, ИмяПоля) Экспорт
Если МетаданныеОбъекта.Реквизиты.Найти(ИмяПоля) <> Неопределено Тогда
Возврат Истина;
КонецЕсли;
Если МетаданныеОбъекта.ТабличныеЧасти.Найти(ИмяПоля) <> Неопределено Тогда
Возврат Истина;
КонецЕсли;
ИмяВРег = ВРег(ИмяПоля);
Для Каждого СтандартныйРеквизит Из МетаданныеОбъекта.СтандартныеРеквизиты Цикл
Если ВРег(СтандартныйРеквизит.Имя) = ИмяВРег Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Она рассчитана на справочники и документы. У регистров сведений табличных частей нет, зато есть Измерения и Ресурсы — для них функцию придётся дополнить, иначе обращение к ТабличныеЧасти само вызовет «Поле объекта не обнаружено».
Типичное применение — собрать запрос под конкретную конфигурацию:
// Общий модуль с флагом «Сервер».
// Артикул попадает в запрос, только если такой реквизит есть в этой конфигурации.
Функция ТоварыДокумента(Ссылка) Экспорт
МетаНоменклатура = Метаданные.Справочники.Найти("Номенклатура");
Если МетаНоменклатура <> Неопределено
И ЕстьПолеВМетаданных(МетаНоменклатура, "Артикул") Тогда
ПолеАртикула = "Товары.Номенклатура.Артикул";
Иначе
ПолеАртикула = """"""; // в тексте запроса окажется пустая строка ""
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрЗаменить(
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| &ПолеАртикула КАК Артикул,
| Товары.Количество КАК Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка", "&ПолеАртикула", ПолеАртикула);
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
В БСП похожую проверку делает ОбщегоНазначения.ЕстьРеквизитОбъекта(ИмяРеквизита, МетаданныеОбъекта) — функция описана в документации БСП 3.1 и 3.2. Порядок параметров непривычный: имя первым, метаданные вторым. Перед использованием откройте код функции в своей версии и посмотрите, учитывает ли она стандартные реквизиты.
Метаданные не помогут, если на руках не объект базы, а Объект на управляемой форме, сама форма, строка табличной части или выборка из запроса. Для них работает приём, на котором построена функция БСП ЕстьРеквизитИлиСвойствоОбъекта («есть реквизит или свойство объекта»): создаём структуру с нужным ключом, кладём в неё уникальное значение-маркер и копируем туда свойства проверяемого значения через ЗаполнитьЗначенияСвойств. Если свойство есть, маркер затрётся его значением. Если нет — останется на месте.
// Общий модуль СвойстваОбъектовКлиентСервер (флаги «Клиент» и «Сервер»).
// Возвращает Истина, если у Источника есть реквизит или свойство ИмяСвойства.
// Найденное значение возвращается в третьем параметре.
Функция ЕстьСвойство(Источник, Знач ИмяСвойства, ЗначениеСвойства = Неопределено) Экспорт
ЗначениеСвойства = Неопределено;
Если Источник = Неопределено Или ПустаяСтрока(ИмяСвойства) Тогда
Возврат Ложь;
КонецЕсли;
ИмяСвойства = СокрЛП(ИмяСвойства);
Маркер = Новый УникальныйИдентификатор;
Приемник = Новый Структура;
Попытка
Приемник.Вставить(ИмяСвойства, Маркер);
Исключение
// Имя с пробелом, точкой или запятой не может быть именем свойства.
Возврат Ложь;
КонецПопытки;
ЗаполнитьЗначенияСвойств(Приемник, Источник);
Если Приемник[ИмяСвойства] = Маркер Тогда
Возврат Ложь;
КонецЕсли;
ЗначениеСвойства = Приемник[ИмяСвойства];
Возврат Истина;
КонецФункции
Вызов из формы документа — проверка идёт на клиенте, без серверного вызова:
// Модуль формы документа.
&НаКлиенте
Процедура ПоказатьКомментарий(Команда)
Комментарий = "";
Если СвойстваОбъектовКлиентСервер.ЕстьСвойство(Объект, "Комментарий", Комментарий)
И ЗначениеЗаполнено(Комментарий) Тогда
ПоказатьПредупреждение(, Комментарий);
Иначе
ПоказатьПредупреждение(, "Комментария у этого документа нет.");
КонецЕсли;
КонецПроцедуры
Новый УникальныйИдентификатор — маркер, который гарантированно не совпадёт ни с одним значением источника. Если положить в структуру Неопределено, функция ошибётся на свойствах, значение которых тоже Неопределено: свойство есть, а проверка скажет «нет».Приемник.Вставить внутри Попытки — ключ структуры обязан быть корректным идентификатором. Имена вроде «Контрагент.ИНН» или «Сумма НДС» вызовут исключение; здесь оно превращается в ответ «свойства нет».ЗаполнитьЗначенияСвойств(Приемник, Источник) без списка свойств — платформа идёт по ключам приёмника и ищет одноимённые свойства у источника. Отсутствующие она пропускает молча, поэтому ошибки не будет.Источником может быть ссылка, но на сервере копирование её реквизитов означает чтение из базы. Для одной проверки это неважно, в цикле по тысячам ссылок — лишние обращения. Для ссылок проверяйте реквизит по метаданным один раз, а значения получайте запросом. На клиенте реквизиты через ссылку не читаются, и проверка вернёт «нет» даже для существующего реквизита.
Ещё одна деталь: функция отвечает «есть» и для табличной части, и для любого другого свойства источника. У формы, например, найдутся Заголовок и Элементы. Если нужен именно реквизит объекта, проверяйте метаданными.
Для коллекций хитрости не нужны — у них есть свои методы. У структуры это Свойство, у таблицы значений и результата запроса — поиск по коллекции Колонки, у формы — Элементы.Найти:
// Модуль формы.
&НаСервере
Процедура НастроитьПоДанным(ДополнительныеПараметры, Запрос)
// Структура и ФиксированнаяСтруктура: метод Свойство,
// второй параметр сразу получает значение ключа.
Склад = Неопределено;
ЕстьСклад = ДополнительныеПараметры.Свойство("Склад", Склад)
И ЗначениеЗаполнено(Склад);
// Результат запроса и таблица значений: коллекция Колонки.
Результат = Запрос.Выполнить();
ЕстьАртикул = (Результат.Колонки.Найти("Артикул") <> Неопределено);
// Элементы формы.
Если Элементы.Найти("ТоварыАртикул") <> Неопределено Тогда
Элементы.ТоварыАртикул.Видимость = ЕстьАртикул;
КонецЕсли;
Если Элементы.Найти("ГруппаДоставка") <> Неопределено Тогда
Элементы.ГруппаДоставка.Видимость = ЕстьСклад;
КонецЕсли;
КонецПроцедуры
Тем же методом проверяют параметры формы в ПриСозданииНаСервере: Параметры.Свойство("ЗначенияЗаполнения").
Иногда нужно не проверить одно имя, а вывести все поля — например, чтобы пользователь выбрал колонку для выгрузки. Список собирается обходом тех же коллекций метаданных:
// Общий модуль с флагом «Сервер».
// Имена стандартных реквизитов, реквизитов и табличных частей
// справочника или документа по ссылке.
Функция ИменаПолейОбъекта(Ссылка) Экспорт
МетаданныеОбъекта = Ссылка.Метаданные();
Имена = Новый Массив;
Для Каждого Реквизит Из МетаданныеОбъекта.СтандартныеРеквизиты Цикл
Имена.Добавить(Реквизит.Имя);
КонецЦикла;
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
Имена.Добавить(Реквизит.Имя);
КонецЦикла;
Для Каждого ТабличнаяЧасть Из МетаданныеОбъекта.ТабличныеЧасти Цикл
Имена.Добавить(ТабличнаяЧасть.Имя);
КонецЦикла;
Возврат Имена;
КонецФункции
Структуру обходят циклом Для Каждого КлючИЗначение Из Структура. Реквизиты управляемой формы на сервере возвращает метод формы ПолучитьРеквизиты().
В типовых на БСП словом «свойства» называют ещё и дополнительные реквизиты и сведения из подсистемы «Свойства». Реквизитами метаданных они не являются: дополнительные реквизиты хранятся строками табличной части ДополнительныеРеквизиты, дополнительные сведения — в регистре сведений ДополнительныеСведения. Ни проверка по метаданным, ни ЗаполнитьЗначенияСвойств их по имени не найдут; для работы с ними есть программный интерфейс модуля УправлениеСвойствами.
С методами сложнее. Встроенный язык не отдаёт список процедур модуля, поэтому метод проверяют вызовом: выполняют его через Выполнить внутри Попытки и смотрят, было ли исключение. По сути это уже не проверка, а схема «вызвать, если есть» — если метод найдётся, он выполнится.
// Общий модуль с флагом «Сервер».
// Вызывает ИмяМодуля.ИмяПроцедуры(Параметр), если обычный (не глобальный)
// общий модуль есть и компилируется на сервере.
// Истина — вызов прошёл без исключения. Платформа 8.3.17 и новее.
Функция ВызватьПроцедуруЕслиЕсть(ИмяМодуля, ИмяПроцедуры, Параметр) Экспорт
Если Не ЭтоКорректноеИмя(ИмяМодуля) Или Не ЭтоКорректноеИмя(ИмяПроцедуры) Тогда
ВызватьИсключение "Недопустимое имя модуля или процедуры";
КонецЕсли;
МетаМодуль = Метаданные.ОбщиеМодули.Найти(ИмяМодуля);
Если МетаМодуль = Неопределено Или Не МетаМодуль.Сервер Или МетаМодуль.Глобальный Тогда
// Глобальный модуль по имени не адресуется: его методы входят в глобальный контекст.
Возврат Ложь;
КонецЕсли;
Попытка
Выполнить(ИмяМодуля + "." + ИмяПроцедуры + "(Параметр)");
Исключение
ЗаписьЖурналаРегистрации("ВызовНеобязательнойПроцедуры",
УровеньЖурналаРегистрации.Предупреждение, , ,
ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Ложь;
КонецПопытки;
Возврат Истина;
КонецФункции
// Имя допустимо, если из него получается ключ структуры:
// буквы, цифры и подчёркивание, первый символ не цифра.
Функция ЭтоКорректноеИмя(Имя)
Если ТипЗнч(Имя) <> Тип("Строка") Или ПустаяСтрока(Имя) Тогда
Возврат Ложь;
КонецЕсли;
Попытка
Проверка = Новый Структура;
Проверка.Вставить(Имя);
Исключение
Возврат Ложь;
КонецПопытки;
Возврат Истина;
КонецФункции
Что здесь важно:
Выполнить. Строка собирается в код, и если имя пришло из настроек, файла или от пользователя, без проверки в неё можно подсунуть произвольный код. Приём со структурой пропускает только буквы, цифры и подчёркивание.Сервер отсекает клиентские модули, а Глобальный — глобальные: их методы вызываются без имени модуля, и строка «Модуль.Процедура» для них упадёт, хотя метод есть.ОбработкаОшибок появился в платформе 8.3.17, и с этой версии глобальная функция ПодробноеПредставлениеОшибки() считается устаревшей. На более старых платформах пишите именно её, а не ОписаниеОшибки(): в подробном представлении есть стек вызовов.В БСП 3.x для этого есть готовые ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяМетода, Параметры) и ОбщегоНазначения.ВыполнитьМетодОбъекта(Объект, ИмяМетода, Параметры): они сами проверяют имя, а параметры принимают массивом. Если же нужно вызвать модуль необязательной подсистемы, в типовых принято обходиться без Выполнить:
// Серверный общий модуль в конфигурации на БСП 3.x.
Процедура ОбработатьСвойстваЕслиЕсть() Экспорт
Если Не ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.Свойства") Тогда
Возврат;
КонецЕсли;
МодульУправлениеСвойствами = ОбщегоНазначения.ОбщийМодуль("УправлениеСвойствами");
// Дальше обычный вызов экспортного метода:
// МодульУправлениеСвойствами.ИмяМетода(...)
КонецПроцедуры
| Способ | Скорость | Безопасность |
|---|---|---|
| Метаданные | Быстро, данные не читаются. В цикле сохраните результат проверки в переменную | Код не выполняется |
ЗаполнитьЗначенияСвойств | Быстро для значений в памяти; для ссылки на сервере — чтение из базы | Код не выполняется |
Попытка вокруг прямого обращения | Дёшево, пока нет исключения; каждое исключение заметно дороже | Прячет посторонние ошибки |
Выполнить | Строка компилируется при каждом вызове — в цикле это заметно | Риск внедрения кода, если имя пришло извне |
Общее правило: проверяйте один раз до цикла, а не на каждой строке. Если реквизита «Артикул» нет в конфигурации, его не появится и на сотой итерации.
Реквизиты конкретного объекта не виден. Его ищут в Метаданные.ОбщиеРеквизиты с учётом состава или проверяют приёмом с ЗаполнитьЗначенияСвойств.Исключение ничего не пишет, ошибка внутри существующего метода маскируется под его отсутствие.НачатьТранзакцию().ЕстьРеквизитИлиСвойствоОбъекта(Объект, ИмяРеквизита) находится в модуле ОбщегоНазначенияКлиентСервер и работает на клиенте и на сервере. Проверку по метаданным делает серверная ОбщегоНазначения.ЕстьРеквизитОбъекта(ИмяРеквизита, МетаданныеОбъекта). Если БСП в конфигурации нет или редакция старая, возьмите функцию ЕстьСвойство из статьи — зависимостей у неё нет.
Для данных формы — да: Объект, реквизиты формы и строки табличных частей проверяются приёмом с ЗаполнитьЗначенияСвойств. Метаданные на тонком клиенте недоступны, поэтому для ссылки нужен серверный вызов.
На сервере получите Ссылка.Метаданные() и проверьте коллекции, как в функции ЕстьПолеВМетаданных. Значение потом читайте запросом или функцией БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта — так не придётся загружать объект целиком.
Штатного способа нет. На практике договариваются об интерфейсе: проверяют модуль по метаданным и вызывают метод, который обязан в нём быть. Иногда в модуль добавляют функцию, возвращающую его версию, и по ней решают, какие методы можно звать.
Если расширение подключено в сеансе, его реквизиты есть и у объекта, и в метаданных, так что оба способа их найдут. Когда расширение отключено, проверка ответит «нет» — ради этого её и пишут.
Если универсальную обработку нужно подружить с несколькими конфигурациями или после обновления типовой доработки посыпались ошибками «Поле объекта не обнаружено», задачу можно отдать исполнителю. Опишите её на странице разместить задачу — оценка на площадке бесплатная, ответ в течение рабочего дня. Направления работ — на странице услуг по доработке 1С.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.