Свой тип свойства инфоблока - регистрация, форма, хранение
Делаем свой тип свойства инфоблока: регистрация, хранение значения, поле в админке, вывод на витрине и настройки типа.
Механика
Свой тип свойства - это не новая таблица, а надстройка над штатным. Значение по-прежнему лежит там, где лежат обычные свойства, а тип решает только, как его показывать, проверять и преобразовывать.
Тип объявляют обработчиком события, а не записью в базе. Платформа спрашивает у модулей список доступных типов при открытии настроек свойства, и ваш тип появляется в выпадающем списке рядом со штатными.
Базовый тип свойства выбирают до всего остального, ещё до первой строки кода. Строка, число, файл или привязка к элементу определяют, в какой колонке окажется значение, и поменять этот выбор после наполнения каталога почти невозможно.
Описание типа - это в первую очередь набор функций обратного вызова. Одна рисует поле в админке, другая преобразует значение перед записью, третья возвращает его обратно, четвёртая показывает значение на витрине.
Проверка значения живёт в своей функции и работает раньше сохранения. Она возвращает список ошибок, и пока он непуст, платформа не даёт сохранить элемент - это дешевле любой чистки данных потом.
У своего типа свойства бывают и собственные настройки, задаваемые при создании. Они задаются при создании свойства в инфоблоке и хранятся вместе с ним, поэтому один тип обслуживает и «цвет из палитры бренда», и «цвет из произвольного списка».
Фильтрация и поиск работают по базовому типу, а не по вашему представлению. Свой тип не добавляет новых правил отбора, и если по свойству нужен умный фильтр, значение должно оставаться пригодным для сравнения в базе.
Шаги
- Выбрать базовый тип: от него зависит хранение и возможности отбора.
- Зарегистрировать тип обработчиком события списка типов свойств.
- Описать функции: форма в админке, преобразование значения, вывод.
- Добавить проверку значения, чтобы мусор не попадал в базу.
- Описать настройки типа, если он должен работать по-разному в разных инфоблоках.
- Проверить тип на реальном инфоблоке: админка, витрина, обмен, фильтр.
Код
Регистрируем тип свойства:
\Bitrix\Main\EventManager::getInstance()->addEventHandler( 'iblock', 'OnIBlockPropertyBuildList', ['CVendorColorProperty', 'GetUserTypeDescription']);// событие поднимается при открытии настроек свойства и при выводе значенийСобытие поднимается чаще, чем кажется на первый взгляд. Оно нужно и при выводе значений на витрине, поэтому регистрацию держат в файле обработчиков проекта, а не включают её по условию на отдельных страницах админки.
Описываем сам тип:
class CVendorColorProperty{ public static function GetUserTypeDescription() { return [ 'PROPERTY_TYPE' => 'S', // базовый тип: строка 'USER_TYPE' => 'vendor_color', // код типа, он же ключ в базе 'DESCRIPTION' => 'Цвет из палитры', 'GetPropertyFieldHtml' => [__CLASS__, 'GetPropertyFieldHtml'], 'ConvertToDB' => [__CLASS__, 'ConvertToDB'], 'ConvertFromDB' => [__CLASS__, 'ConvertFromDB'], 'GetPublicViewHTML' => [__CLASS__, 'GetPublicViewHTML'], 'CheckFields' => [__CLASS__, 'CheckFields'], ]; }}Базовый тип определяет колонку хранения. Строка ложится в текстовое поле, число - в числовое, а привязка к элементу - в поле идентификатора; от этого зависит и то, как свойство поведёт себя в фильтре.
Рисуем поле в админке:
public static function GetPropertyFieldHtml($property, $value, $control){ $selected = htmlspecialcharsbx($value['VALUE']); return '<input type="color" name="' . $control['VALUE'] . '" value="' . $selected . '">'; // имя поля берут из массива управления: платформа сама разберёт его при сохранении}Имя поля приходит готовым и своё придумывать нельзя. Платформа собирает имена с учётом множественности свойства и номера значения, и самодельное имя ломает сохранение именно у множественных свойств.
Пара функций преобразования отвечает за то, в каком виде значение живёт в базе. Одна приводит введённое к хранимому виду, вторая возвращает его обратно, и вместе они позволяют хранить значение удобно для отбора, а показывать - удобно человеку.
Преобразуем значение при записи и чтении:
public static function ConvertToDB($property, $value){ $value['VALUE'] = mb_strtoupper(trim((string)$value['VALUE'])); // #ff00aa в верхний регистр return $value;}
public static function ConvertFromDB($property, $value){ return $value; // обратное преобразование: здесь оно не требуется}Проверяем значение перед сохранением:
public static function CheckFields($property, $value){ $errors = []; if ($value['VALUE'] !== '' && !preg_match('/^#[0-9A-F]{6}$/i', $value['VALUE'])) { $errors[] = 'Цвет задают шестизначным кодом со знаком решётки'; } return $errors; // непустой список не даёт сохранить элемент}Проверка окупается на первой же массовой загрузке. Обмен с учётной системой и импорт из файла пишут значения тем же путём, и без проверки в свойстве оказывается всё подряд - от пустых строк до названий цветов словами.
Выводим значение на витрине:
public static function GetPublicViewHTML($property, $value, $control){ $color = htmlspecialcharsbx($value['VALUE']); return '<span class="swatch" style="background:' . $color . '"></span>'; // тот же вывод используют компоненты каталога, если им не задан свой шаблон}Добавляем настройки типа:
public static function GetSettingsHTML($property, $control, &$settings){ return '<tr><td>Палитра:</td><td><input name="' . $control['NAME'] . '[PALETTE]" value="' . htmlspecialcharsbx($property['USER_TYPE_SETTINGS']['PALETTE'] ?? '') . '"></td></tr>';}// разбор значений настроек делает PrepareSettings, он же чистит лишнееНастройки хранятся вместе со свойством инфоблока. Благодаря им один тип обслуживает разные инфоблоки по-разному, и заводить второй тип ради другой палитры не приходится.
Ограничения
Базовый тип меняют только до наполнения. После записи значений смена базового типа означает перенос данных из одной колонки в другую, а это отдельная задача с миграцией и проверкой.
Свой тип свойства не добавляет платформе никаких новых правил отбора значений. Умный фильтр и выборки работают по базовому типу, поэтому значение должно оставаться сравнимым: цвет как строка фильтруется как строка, и никак иначе.
Обмен с учётной системой о вашем типе не знает. Он пишет значения как обычные строки, и проверка значения - единственное, что защищает свойство от мусора при ночной выгрузке.
Множественные свойства требуют особого внимания к именам полей в разметке формы. Их платформа собирает сама, и любая самодеятельность в разметке ломает сохранение второго и последующих значений.
Тип регистрируется на каждом запросе. Обработчик события живёт в файле обработчиков проекта или в своём модуле; забытая регистрация превращает свойства этого типа в нечитаемые строки.
Вывод значения по умолчанию годится далеко не всегда и не везде. Компоненты каталога часто печатают значение своим шаблоном, и тогда функция вывода на витрине не вызывается вовсе.
Типичные проблемы
Тип не появился в списке при создании свойства.
Обработчик события не зарегистрирован или зарегистрирован не на том событии. Регистрацию держат в общем файле обработчиков проекта, а не в отдельной странице.
У множественного свойства сохраняется только одно значение.
В разметке поля указано своё имя вместо имени из массива управления. Имена полей собирает сама платформа, с учётом номера значения множественного свойства.
После обмена в свойстве оказался мусор.
Проверка значения не описана, а обмен пишет строки напрямую. Проверку значения добавляют в описание типа, а не в шаблон формы админки.
Значения свойства перестали читаться.
Регистрация типа пропала после правки файла обработчиков. Без неё платформа не знает, как разобрать сохранённое значение.
На витрине выводится сырое значение вместо оформления.
Компонент печатает значение своим шаблоном и функцию вывода не вызывает. Оформление значения в таком случае переносят прямо в шаблон самого компонента.
Фильтр по свойству работает не так, как ожидалось.
Отбор идёт по базовому типу, а не по вашему представлению значения. Для свойств, участвующих в умном фильтре, подходящий базовый тип выбирают заранее.
Частые вопросы
Чем свой тип свойства отличается от своего типа пользовательского поля?
Это разные механизмы: свойства принадлежат инфоблокам, пользовательские поля - другим сущностям платформы. Регистрируются они разными событиями и описываются разными наборами функций.
Какой базовый тип выбрать?
Тот, который соответствует хранимому значению и нужному отбору: строка для кодов и текстов, число для сравнений и сортировки, привязка для ссылок на другие элементы. Менять его после наполнения дорого.
Где регистрировать обработчик?
В файле обработчиков проекта или в своём модуле, если тип едет вместе с решением. Главное - чтобы регистрация выполнялась на каждом запросе: без неё значения не читаются.
Можно ли обойтись без своего типа?
Часто да: строка с проверкой в обработчике события сохранения решает половину задач. Свой тип нужен, когда важна форма ввода в админке и единое поведение во всех инфоблоках.
Работает ли свой тип в умном фильтре?
Фильтрация идёт по базовому типу, поэтому свойство фильтруется как строка или число. Если по свойству нужен фасетный отбор, это учитывают при выборе базового типа.
Смежное
- Свойства инфоблоков - оглавление подтемы
- Свойства инфоблока: чтение, запись и фильтрация по значению - как устроены обычные свойства
- Дата, число и HTML в свойствах: типы значений и их ловушки - штатные типы и их поведение
- Свой тип пользовательского поля: регистрация, формы, значение - соседний механизм для других сущностей
- Свой модуль: структура, установка, автозагрузка - где живёт тип в тиражном решении
- Инфоблоки - устройство хранилища целиком