Битрикс D7: Запись в таблицу с множественным полем с помощью ORM

Допустим, вы прочитали инструкции тут и тут. Создали свой высоконагруженный инфоблок (по сути – почти обычную таблицу) и у вас в этом highload-блоке есть поле, в котором хранится привязка сущностей этого блока к ID элементов старых добрых инфоблоков. Поле это будет целочисленным и множественным и необязательным.

Чтобы общаться с этой таблицей, мы в каком-нибудь модуле опишем класс в папке lib. В этом классе будет метод getMap, который опишет все поля, которые мы создали в таблице.

Описание проблемы

Теперь мы попробуем создать запись в таблице вот примерно так

\ПространствоИмён\ИмяКласса::Add([
	//упустим остальные параметры
	'ИМЯ_МНОЖЕСТВЕННОГО_ПОЛЯ' => [
		123123,
		324234
	]
])

Запись может пройти все проверки (вы ведь описали Valdator для каждого поля в карте (метод getMap)?), но в интерфейсе админки битрикса, при просмотре данных в инфоблоке никаких значений во множественном поле не будет.

Вызов

ИмяКласса::getList()->fetchAll()

вернёт все наши записи. И во множественном поле тут также будет пусто. Возможна еще такая ситуация, когда мы передаём только один ID

\ПространствоИмён\ИмяКласса::Add([
	//упустим остальные параметры
	'ИМЯ_МНОЖЕСТВЕННОГО_ПОЛЯ' => 3213123
])

В этом случае в админке будет пусто, но getList вернёт нам значение этого поля. В чем же подвох?

Решение проблемы

Тут всё просто — множественные поля должны сериализовываться. Странно, что в документации этого нет. Можно сильно упростить себе жизнь, описав грамотную карту таблицы. Например, если указать в описании поля параметр ‘serialized’ => true, то битрикс сам будет сериализовывать все данные перед сохранением в таблицу и при возврате. С этим параметром и в админке и в коде всё будет отображаться нормально.

НО! Возникает проблема – если ID у нас в одном экзепляре, то хотелось бы передать только его, не засовывая в массив. Но при этом, чтобы возвращался в коде всегда массив, даже если там только одно значение, чтобы не проверят каждый раз тип данных. В этом нам поможет параметр ‘save_data_modification’, который может принудительно запихнут число в массив.

Получается, что поле в методе getMap должно быть описано примерно так.

new Entity\IntegerField('ИМЯ_МНОЖЕСТВЕННОГО_ПОЛЯ', [
	'column_name' => 'UF_ИМЯ_МНОЖЕСТВЕННОГО_ПОЛЯ',
	'serialized' => true,
	//принудительно сохраняем данные в массив
	'save_data_modification' => function(){
		return [
			function($value){
				if(!is_array($value)){
					return [$value];
				}else{
					return $value;
				}
			}
		];
	},
	'validation' => function() {
		return [
			function($value){
				if(is_array($value)){
					$result = true;
					foreach($value as $item){
						if(!(is_numeric($item) and intval($item) > 0)){
							$result = false;
						}
					}
					
					if($result){
						return $result;
					}else{
						return 'Некорректный массив с ID элементов';
					}
					
				}elseif(is_numeric($value) and intval($value) > 0){
					return true;
					
				}else{
					return 'Некорректный ID элемента, ожидается целое число или
 массив целых чисел.';
				}
			}
		];
	}
]),

И тогда всё будет удобно и красиво =)

5 комментариев

CharlesMuh

Небольшая особенность в setSelect нужно обязательно указывать runtime-поле, что добавляет в select все поля связанной таблицы.

Ответить

Марк

> Чтобы общаться с этой таблицей
На сколько мне известно, такой подход следует использовать при создании в БД собственных таблиц, минуя битрикс. Тогда действительно можно описать класс-модель в стиле D7, чтобы иметь возможность работать со своей таблицей по правилам битрикса (ORM D7).

Однако в вашем примере речь идет про работу с HL-иблоками, и для этого достаточно создать динамическую сущность с указанием параметров HL-иблока:
$HLEntityModelClass = \Bitrix\Highloadblock\HighloadBlockTable::compileEntity(..)

Дальше можно будет делать выборки и добавлять/изменять данные.
При записи значений множ. св-в битрикс сам будет их сериализовать и(!) дублировать в отдельные поисковые таблицы для множественных св-в данного HL-иблока, чего в вашем случае не будет происходить.

Вся суть HL-иблоков:
— хранить только нужные данные (именно поэтому нужно создать каждое поле руками, и нет базовых)
— быстрые поисковые запросы по св-м, и(!) множественным св-м (за счет внесения избыточности, и хранения множ св-в построчно в отдельной поисковой таблице, отдельно для каждого HL-иблока)

То есть в вашей статье перепутались две отдельные темы:
— работа со своими таблицами через битрикс ORM
— работа HL-иблоками.

Поправьте меня, если я не прав :)

Ответить

Марк

P.S.
очень жаль, что у вас вырезаются все пустые строки — без этого текст становится нечитаемым.
добавьте пожалуйста разрывы на абазацы, как в оригинальном сообщении http://clip2net.com/s/3Flt5Gb.

Ответить

Морозов Максим

Добавил отступ снизу у параграфов, стало лучше читаться.

Ответить

Морозов Максим

HL блок это лишь более менее удобный GUI для обычных таблиц, которые изредка необходимо править в админке. Он будет такой же быстрый как и любая обычная таблица (при правильных типах полей, индексах и построении запросов). Да, по сравнению с инфоблоками, выигрыш в скорости будет огромный, особенно при больших объемах данных. Но это обычная таблица, все столбцы которой описаны в пользовательских полях, за счет чего их можно вывести в админке в пригодном для редактирования виде.

HL инфоблок становится удобным, когда начинаешь использовать его в ORM. Создаёшь описание всех полей, задаёшь валидаторы, модификаторы и прочее. В этом случае огромное количество копипаста уходит в маппинг таблицы. И многие рутинные задачи начинают решаться проще. Мы уже точно знаем что метод add проверит валидность поля и приведёт его к нужному виду. Это сильно упрощает код. В этом случае наличие в админке GUI для HL инфоблоков лишь удобная вишенка на торте, основная цель была именно использовать ORM. В статье я описал некоторые нюансы этого подхода. Можно было бы просто создать обычную таблицу и не заморачиваться с HL.

Ответить

Ваш отзыв

logo