Работа с разделами и элементами инфоблока через D7 ORM
Подробное руководство для разработчиков Битрикс, которые хотят перейти от устаревших методов CIBlockElement и CIBlockSection к современному объектному API. Если вы устали от ручного формирования массивов и хотите получить автокомплит, типизацию и чистый код — эта статья для вас.
Кирилл Новожилов
Автор
Содержание
Что вы узнаете?
- Как подготовить инфоблок для работы с ORM.
- Принцип компиляции entity и зачем она нужна.
- Как работать с разделами инфоблока через ORM.
- Полный цикл CRUD-операций для элементов: создание, чтение, обновление, удаление.
- Работу с обычными и множественными свойствами, включая свойства с описанием.
- Типичные ошибки и способы их избежать.
Часть 1: Подготовка инфоблока
Прежде чем использовать ORM API, необходимо настроить инфоблок. Без этих настроек ORM работать не будет.
Обязательные требования
1.API_CODE инфоблока — символьный код API, который задаётся в настройках инфоблока. Именно по нему происходит компиляция entity. Без API_CODE метод compileEntity() вернёт false.
2.Символьные коды свойств (CODE) — каждое свойство, с которым вы хотите работать через ORM, должно иметь заполненный символьный код. Свойства без CODE будут недоступны.
Где задать API_CODE
Административная панель → Контент → Инфоблоки → [Ваш инфоблок] → вкладка «Основные» → поле «Символьный код API».
Рекомендуется использовать CamelCase для API_CODE, например:
Products,NewsItems,Catalog. Это обеспечит читаемые имена сгенерированных классов.
Версия хранения свойств
ORM поддерживает обе версии инфоблоков:
- Версия 1 — свойства хранятся в общей таблице
b_iblock_element_property - Версия 2 — свойства хранятся в отдельной таблице
b_iblock_element_prop_s{IBLOCK_ID}
Выбор версии влияет только на внутреннюю реализацию — API остаётся одинаковым.
Часть 2: Компиляция Entity
Компиляция entity — это процесс создания ORM-класса для конкретного инфоблока. Класс генерируется динамически на основе структуры инфоблока и его свойств.
Компиляция для элементов
use Bitrix\Iblock\IblockTable;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
// Компиляция по API_CODE
$entity = IblockTable::compileEntity('Products');
// После компиляции доступен класс:
// \Bitrix\Iblock\Elements\ElementProductsTable
Метод compileEntity() выполняет несколько действий:
- Находит инфоблок по
API_CODE - Определяет версию хранения свойств
- Загружает все свойства инфоблока
- Генерирует класс с полями и связями
- Кеширует результат — повторная компиляция не создаёт новый класс
Формирование имени класса для элементов
Имя класса формируется по шаблону: \Bitrix\Iblock\Elements\Element{ApiCode}Table
| API_CODE | Сгенерированный класс |
|---|---|
| Products | \Bitrix\Iblock\Elements\ElementProductsTable |
| NewsItems | \Bitrix\Iblock\Elements\ElementNewsItemsTable |
| Catalog | \Bitrix\Iblock\Elements\ElementCatalogTable |
Компиляция для разделов
Для разделов используется отдельный класс Section:
use Bitrix\Iblock\Model\Section;
use Bitrix\Iblock\IblockTable;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
// Компиляция по API_CODE инфоблока (рекомендуемый способ)
$sectionClass = Section::compileEntityByIblock('Products');
// Вернёт: \Bitrix\Iblock\Section{ID}Table, например \Bitrix\Iblock\Section5Table
// Или по объекту инфоблока
$iblock = IblockTable::query()
->where('API_CODE', 'Products')
->setLimit(1)
->fetchObject();
$sectionClass = Section::compileEntityByIblock($iblock);
// Также можно передать ID инфоблока, если он известен
// $sectionClass = Section::compileEntityByIblock($iblock->getId());
Особенность именования классов для разделов
В отличие от элементов, имя класса для разделов формируется по ID инфоблока, а не по API_CODE:
| ID инфоблока | Сгенерированный класс |
|---|---|
| 5 | \Bitrix\Iblock\Section5Table |
| 12 | \Bitrix\Iblock\Section12Table |
Генерация аннотаций для IDE
Классы элементов и разделов генерируются динамически, поэтому IDE не знает о них заранее и не может предоставить автодополнение. Чтобы получить подсказки по методам и свойствам, необходимо сгенерировать файлы аннотаций.
Для этого используется консольная команда orm:annotate:
cd /path/to/document_root/bitrix
php bitrix.php orm:annotate --modules=all
Команда сканирует все инфоблоки с заданным API_CODE и генерирует файлы аннотаций. После выполнения IDE начнёт подсказывать:
- Методы объектов элементов:
setName(),getName(),setActive()и другие - Методы для работы со свойствами:
set('PRICE', ...),get('PRICE') - Коллекции и результаты запросов
Рекомендуется запускать
orm:annotateпосле добавления новых инфоблоков или изменения их свойств.
Часть 3: Работа с разделами
После компиляции entity для разделов доступен полный набор CRUD-операций.
Создание раздела
Для создания раздела обязательно требуется указать IBLOCK_ID. Поэтому сначала получаем объект инфоблока:
use Bitrix\Iblock\Model\Section;
use Bitrix\Iblock\IblockTable;
$iblock = IblockTable::query()
->where('API_CODE', 'Products')
->setLimit(1)
->fetchObject();
$sectionClass = Section::compileEntityByIblock($iblock);
// Создание раздела
$section = $sectionClass::createObject()
->setIblockId($iblock->getId())
->setName('Смартфоны')
->setCode('smartphones')
->setSort(100)
->setActive(true)
->setDescription('Раздел со смартфонами')
->setDescriptionType('text'); // или 'html'
$result = $section->save();
if ($result->isSuccess()) {
echo 'Создан раздел с ID: ' . $section->getId();
}
Для создания вложенного раздела сначала получаем инфоблок и находим родительский раздел:
$iblock = IblockTable::query()
->where('API_CODE', 'Products')
->setLimit(1)
->fetchObject();
$sectionClass = Section::compileEntityByIblock($iblock);
// Находим родительский раздел
$parentSection = $sectionClass::query()
->setSelect(['ID'])
->where('CODE', 'smartphones')
->setLimit(1)
->fetchObject();
$section = $sectionClass::createObject()
->setIblockId($iblock->getId())
->setName('Смартфоны Apple')
->setCode('apple-smartphones');
if ($parentSection) {
$section->setIblockSectionId($parentSection->getId());
}
$section->save();
Работа с пользовательскими полями разделов (UF_*)
Разделы инфоблока поддерживают пользовательские поля. После компиляции entity они становятся доступны:
$iblock = IblockTable::query()
->where('API_CODE', 'Products')
->setLimit(1)
->fetchObject();
$sectionClass = Section::compileEntityByIblock($iblock);
// Проверка UF_ID
echo $sectionClass::getUfId(); // IBLOCK_5_SECTION
// Создание раздела с UF-полями
$section = $sectionClass::createObject()
->setIblockId($iblock->getId())
->setName('Раздел с UF-полями')
->set('UF_ICON', '/images/icon.png') // Строка
->set('UF_SHOW_ON_MAIN', true); // Да/Нет
$section->save();
Чтение разделов
$sectionClass = Section::compileEntityByIblock('Products');
// Один раздел по символьному коду
$section = $sectionClass::query()
->where('CODE', 'smartphones')
->setLimit(1)
->fetchObject();
if ($section) {
echo $section->getName();
}
// Выборка с фильтрацией
$sections = $sectionClass::query()
->setSelect(['ID', 'NAME', 'CODE', 'DEPTH_LEVEL'])
->where('ACTIVE', 'Y')
->where('DEPTH_LEVEL', 1) // Только разделы первого уровня
->setOrder(['SORT' => 'ASC'])
->fetchCollection();
foreach ($sections as $section) {
echo $section->getName() . "\n";
}
Чтение UF-полей раздела
Если у разделов есть пользовательские поля, они доступны через метод get():
$sectionClass = Section::compileEntityByIblock('Products');
// Получаем раздел со всеми UF-полями
$section = $sectionClass::query()
->setSelect(['*', 'UF_*']) // Все поля + все UF-поля
->where('CODE', 'smartphones')
->setLimit(1)
->fetchObject();
if ($section) {
// Строковое поле
$icon = $section->get('UF_ICON');
// Чекбокс (Да/Нет) — возвращает 1 или 0
$showOnMain = $section->get('UF_SHOW_ON_MAIN');
// Файл — возвращает ID файла
$imageId = $section->get('UF_IMAGE');
if ($imageId) {
$imagePath = \CFile::GetPath($imageId);
}
// Множественный файл — возвращает массив ID
$galleryIds = $section->get('UF_GALLERY');
if ($galleryIds) {
foreach ($galleryIds as $fileId) {
$filePath = \CFile::GetPath($fileId);
echo $filePath . "\n";
}
}
}
Получение родительского раздела
// Находим вложенный раздел с подгрузкой родителя
$section = $sectionClass::query()
->setSelect(['*', 'PARENT_SECTION'])
->where('CODE', 'apple-smartphones')
->setLimit(1)
->fetchObject();
if ($section) {
$parent = $section->getParentSection();
if ($parent) {
echo 'Родитель: ' . $parent->getName();
}
}
Обновление раздела
// Находим раздел по символьному коду
$section = $sectionClass::query()
->where('CODE', 'smartphones')
->setLimit(1)
->fetchObject();
if ($section) {
$section
->setName('Новое название раздела')
->setSort(50)
->set('UF_ICON', '/images/new-icon.png');
$result = $section->save();
}
Статический метод update() можно использовать, когда ID известен:
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$sectionId = (int)$request->get('SECTION_ID');
if ($sectionId > 0) {
// Обновляем только стандартные поля
$result = $sectionClass::update($sectionId, [
'NAME' => 'Обновлённый раздел',
'SORT' => 200,
]);
}
Удаление раздела
// Через объект — находим раздел по символьному коду
$section = $sectionClass::query()
->where('CODE', 'old-section')
->setLimit(1)
->fetchObject();
if ($section) {
$result = $section->delete();
}
// Или напрямую по ID, если он известен
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$sectionId = (int)$request->get('DELETE_SECTION_ID');
if ($sectionId > 0) {
$result = $sectionClass::delete($sectionId);
}
При удалении раздела автоматически пересчитывается дерево разделов (LEFT_MARGIN, RIGHT_MARGIN, DEPTH_LEVEL).
Внимание: ORM не удаляет вложенные разделы и элементы автоматически. Раздел будет удалён, но все вложенные разделы и элементы останутся в базе данных с
IBLOCK_SECTION_ID, указывающим на несуществующий раздел. Они не будут отображаться в административной панели, и никакой ошибки при этом выдано не будет — данные просто «потеряются».
Рекурсивное удаление раздела
Вариант 1: Использовать классический API
Самый простой и надёжный способ — использовать CIBlockSection::Delete(), который обрабатывает рекурсивное удаление:
// Удаление раздела со всем содержимым
\CIBlockSection::Delete($sectionId);
Метод автоматически удалит все вложенные разделы и элементы.
Вариант 2: Рекурсивное удаление через ORM
Если необходимо использовать ORM, реализуйте рекурсивное удаление вручную:
use Bitrix\Iblock\Model\Section;
use Bitrix\Iblock\IblockTable;
/**
* Рекурсивно удаляет раздел со всеми вложенными разделами и элементами
*/
function deleteSectionRecursive(int $sectionId, string $sectionClassName, string $elementClassName): void
{
// 1. Удаляем все элементы раздела
$elements = $elementClassName::query()
->setSelect(['ID'])
->where('IBLOCK_SECTION_ID', $sectionId)
->fetchCollection();
foreach ($elements as $element) {
$element->delete();
}
// 2. Находим и рекурсивно удаляем вложенные разделы
$childSections = $sectionClassName::query()
->setSelect(['ID'])
->where('IBLOCK_SECTION_ID', $sectionId)
->fetchCollection();
foreach ($childSections as $childSection) {
deleteSectionRecursive($childSection->getId(), $sectionClassName, $elementClassName);
}
// 3. Удаляем сам раздел
$sectionClassName::delete($sectionId);
}
// Использование
$iblock = IblockTable::query()
->where('API_CODE', 'Products')
->setLimit(1)
->fetchObject();
IblockTable::compileEntity('Products');
$sectionClass = Section::compileEntityByIblock($iblock);
$elementClass = \Bitrix\Iblock\Elements\ElementProductsTable::class;
deleteSectionRecursive($sectionId, $sectionClass, $elementClass);
Для большинства случаев рекомендуется использовать
CIBlockSection::Delete()— он проверен временем и гарантирует корректное удаление всех связанных данных, включая SEO-настройки, права доступа и поисковые индексы.
Часть 4: CRUD-операции с элементами
После компиляции entity доступен полный набор операций: создание, чтение, обновление и удаление.
Создание элемента
use Bitrix\Iblock\IblockTable;
use Bitrix\Iblock\Model\Section;
IblockTable::compileEntity('Products');
$sectionClass = Section::compileEntityByIblock('Products');
// Находим раздел, в который будем добавлять элемент
$section = $sectionClass::query()
->setSelect(['ID'])
->where('CODE', 'smartphones')
->setLimit(1)
->fetchObject();
// Создание объекта элемента
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Смартфон iPhone 15')
->setCode('iphone-15')
->setActive(true)
->setSort(100)
->setPreviewText('Краткое описание товара')
->setDetailText('<p>Подробное описание</p>')
->setDetailTextType('html'); // или 'text'
if ($section) {
$element->setIblockSectionId($section->getId());
}
// Сохранение
$result = $element->save();
if ($result->isSuccess()) {
echo 'Создан элемент с ID: ' . $element->getId();
} else {
print_r($result->getErrorMessages());
}
Работа со свойствами
Свойства устанавливаются через метод set() с кодом свойства в верхнем регистре:
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Товар со свойствами')
->set('PRICE', 99900) // Число
->set('ARTICLE', 'ART-001') // Строка
->set('MANUFACTURER', 'Apple'); // Строка
$element->save();
Множественные свойства
Для множественных свойств используется метод addTo():
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Товар с множественными свойствами')
->addTo('COLORS', 'Красный')
->addTo('COLORS', 'Синий')
->addTo('COLORS', 'Зелёный');
$element->save();
Для свойств типа «Привязка к элементам» сначала получаем ID связанных элементов:
$dataClass = \Bitrix\Iblock\Elements\ElementProductsTable::class;
// Находим товары для привязки по символьным кодам
$relatedProducts = $dataClass::query()
->setSelect(['ID'])
->whereIn('CODE', ['case-iphone', 'charger-usb-c', 'screen-protector'])
->fetchCollection();
$element = $dataClass::createObject()
->setName('Смартфон с аксессуарами');
// Привязываем найденные товары
foreach ($relatedProducts as $relatedProduct) {
$element->addTo('RELATED_PRODUCTS', $relatedProduct->getId());
}
$element->save();
Свойства с описанием
Некоторые типы свойств поддерживают описание (например, файлы). Для этого используется класс PropertyValue:
use Bitrix\Iblock\ORM\PropertyValue;
// Сначала сохраняем файлы и получаем их ID
$mainPhotoId = \CFile::SaveFile(
\CFile::MakeFileArray($_SERVER['DOCUMENT_ROOT'] . '/upload/tmp/photo.jpg'),
'iblock'
);
$sideViewId = \CFile::SaveFile(
\CFile::MakeFileArray($_SERVER['DOCUMENT_ROOT'] . '/upload/tmp/side.jpg'),
'iblock'
);
$backViewId = \CFile::SaveFile(
\CFile::MakeFileArray($_SERVER['DOCUMENT_ROOT'] . '/upload/tmp/back.jpg'),
'iblock'
);
// Первый аргумент — ID файла, второй — описание
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Товар с изображениями')
->set('PHOTO', new PropertyValue($mainPhotoId, 'Фото товара спереди'))
->addTo('GALLERY', new PropertyValue($sideViewId, 'Вид сбоку'))
->addTo('GALLERY', new PropertyValue($backViewId, 'Вид сзади'));
$element->save();
Свойство типа «Справочник» (привязка к HL-блоку)
Свойство типа «Справочник» хранит XML_ID элемента Highload-блока. Сначала находим нужное значение в справочнике:
use Bitrix\Highloadblock\HighloadBlockTable;
use Bitrix\Main\Loader;
Loader::includeModule('highloadblock');
// Получаем класс HL-блока по названию таблицы
$hlblock = HighloadBlockTable::query()
->where('TABLE_NAME', 'b_hlbd_colors') // или 'NAME', 'Colors'
->setLimit(1)
->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$hlDataClass = $entity->getDataClass();
// Находим нужный цвет по названию
$color = $hlDataClass::query()
->setSelect(['UF_XML_ID'])
->where('UF_NAME', 'Синий')
->setLimit(1)
->fetch();
// Устанавливаем значение свойства — это XML_ID из справочника
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Товар с цветом из справочника');
if ($color) {
$element->set('COLOR', $color['UF_XML_ID']); // например: 'blue'
}
$element->save();
Для множественного свойства-справочника:
// Находим несколько значений
$colors = $hlDataClass::query()
->setSelect(['UF_XML_ID'])
->whereIn('UF_NAME', ['Синий', 'Красный', 'Зелёный'])
->fetchAll();
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject()
->setName('Товар с несколькими цветами');
foreach ($colors as $color) {
$element->addTo('AVAILABLE_COLORS', $color['UF_XML_ID']);
}
$element->save();
Чтение элементов
IblockTable::compileEntity('Products');
$dataClass = \Bitrix\Iblock\Elements\ElementProductsTable::class;
// Получение элемента по символьному коду
$element = $dataClass::query()
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
echo $element->getName(); // Название
echo $element->getCode(); // Символьный код
echo $element->get('PRICE'); // Значение свойства
}
// Или по ID, если он известен (например, пришёл из запроса)
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$elementId = (int)$request->get('ELEMENT_ID');
if ($elementId > 0) {
$element = $dataClass::getByPrimary($elementId)->fetchObject();
}
// Выборка с фильтрацией
$elements = $dataClass::query()
->setSelect(['ID', 'NAME', 'CODE', 'PRICE', 'ARTICLE'])
->where('ACTIVE', 'Y')
->where('PRICE.VALUE', '>=', 1000)
->setOrder(['SORT' => 'ASC', 'NAME' => 'ASC'])
->setLimit(20)
->fetchCollection();
foreach ($elements as $element) {
echo $element->getName() . ': ' . $element->get('PRICE')->getValue() . "\n";
}
Чтение свойства типа «Файл»
Свойство типа «Файл» возвращает объект PropertyValue, содержащий ID файла и описание:
// Элемент со свойством PHOTO (одиночный файл)
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'PHOTO'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$photo = $element->get('PHOTO');
if ($photo) {
// Получаем ID файла и описание
$fileId = $photo->getValue();
$description = $photo->getDescription();
// Получаем путь к файлу
$filePath = \CFile::GetPath($fileId);
// Или полные данные о файле
$fileData = \CFile::GetFileArray($fileId);
// $fileData['SRC'], $fileData['WIDTH'], $fileData['HEIGHT'], etc.
}
}
Чтение множественного свойства типа «Файл»
Множественные файловые свойства возвращают коллекцию объектов PropertyValue:
// Элемент со свойством GALLERY (множественный файл)
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'GALLERY'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$gallery = $element->get('GALLERY');
if ($gallery) {
// Это коллекция PropertyValueCollection
foreach ($gallery->getAll() as $photo) {
$fileId = $photo->getValue();
$description = $photo->getDescription();
$fileData = \CFile::GetFileArray($fileId);
echo $fileData['SRC'] . ' — ' . $description . "\n";
}
}
}
Чтение привязки к элементам инфоблока
Свойство типа «Привязка к элементам» хранит ID связанных элементов:
// Элемент со свойством RELATED_PRODUCTS (привязка к элементам)
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'RELATED_PRODUCTS'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$relatedProducts = $element->get('RELATED_PRODUCTS');
if ($relatedProducts) {
// Для множественного свойства — это коллекция
foreach ($relatedProducts->getAll() as $propValue) {
$relatedId = $propValue->getValue();
echo "Связанный элемент ID: {$relatedId}\n";
}
// Чтобы получить данные связанных элементов, делаем отдельный запрос
$relatedIds = array_map(
fn($pv) => $pv->getValue(),
$relatedProducts->getAll()
);
$relatedElements = $dataClass::query()
->setSelect(['ID', 'NAME', 'CODE'])
->whereIn('ID', $relatedIds)
->fetchCollection();
foreach ($relatedElements as $related) {
echo $related->getName() . "\n";
}
}
}
Для одиночного свойства привязки:
// MAIN_PRODUCT — одиночная привязка к элементу
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'MAIN_PRODUCT'])
->where('CODE', 'accessory-case')
->setLimit(1)
->fetchObject();
if ($element) {
$mainProductProp = $element->get('MAIN_PRODUCT');
if ($mainProductProp) {
$mainProductId = $mainProductProp->getValue();
// Получаем связанный элемент
$mainProduct = $dataClass::query()
->setSelect(['ID', 'NAME', 'PRICE'])
->where('ID', $mainProductId)
->setLimit(1)
->fetchObject();
if ($mainProduct) {
echo "Основной товар: " . $mainProduct->getName();
}
}
}
Чтение привязки к разделам
Для работы с привязкой к разделам инфоблока:
// Свойство SECTIONS — привязка к разделам
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'SECTIONS'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$sectionsProp = $element->get('SECTIONS');
if ($sectionsProp) {
$sectionIds = array_map(
fn($pv) => $pv->getValue(),
$sectionsProp->getAll()
);
// Получаем данные разделов
$sectionClass = Section::compileEntityByIblock('Products');
$sections = $sectionClass::query()
->setSelect(['ID', 'NAME', 'CODE'])
->whereIn('ID', $sectionIds)
->fetchCollection();
foreach ($sections as $section) {
echo "Раздел: " . $section->getName() . "\n";
}
}
}
Чтение свойства типа «Справочник»
Свойство-справочник хранит XML_ID элемента Highload-блока. Для получения полных данных нужен дополнительный запрос:
use Bitrix\Highloadblock\HighloadBlockTable;
Loader::includeModule('highloadblock');
// Элемент со свойством COLOR (справочник)
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'COLOR'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$colorProp = $element->get('COLOR');
if ($colorProp) {
// Получаем XML_ID из справочника
$colorXmlId = $colorProp->getValue();
// Получаем данные из HL-блока
$hlblock = HighloadBlockTable::query()
->where('TABLE_NAME', 'b_hlbd_colors')
->setLimit(1)
->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$hlDataClass = $entity->getDataClass();
$color = $hlDataClass::query()
->setSelect(['UF_NAME', 'UF_HEX', 'UF_FILE'])
->where('UF_XML_ID', $colorXmlId)
->setLimit(1)
->fetch();
if ($color) {
echo "Цвет: {$color['UF_NAME']}, код: {$color['UF_HEX']}";
}
}
}
Чтение множественного свойства-справочника
// Свойство AVAILABLE_COLORS — множественный справочник
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'AVAILABLE_COLORS'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$colorsProp = $element->get('AVAILABLE_COLORS');
if ($colorsProp) {
// Собираем все XML_ID
$xmlIds = array_map(
fn($pv) => $pv->getValue(),
$colorsProp->getAll()
);
// Запрашиваем данные из HL-блока
$hlblock = HighloadBlockTable::query()
->where('TABLE_NAME', 'b_hlbd_colors')
->setLimit(1)
->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$hlDataClass = $entity->getDataClass();
$colors = $hlDataClass::query()
->setSelect(['UF_XML_ID', 'UF_NAME', 'UF_HEX'])
->whereIn('UF_XML_ID', $xmlIds)
->fetchAll();
foreach ($colors as $color) {
echo "{$color['UF_NAME']} ({$color['UF_HEX']})\n";
}
}
}
Чтение свойства типа «Список»
Свойство типа «Список» хранит ID выбранного значения из списка:
// Свойство STATUS — список
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'STATUS'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
$statusProp = $element->get('STATUS');
if ($statusProp) {
// Для списка доступны:
// - ITEM — объект с данными выбранного значения
// - VALUE — ID значения
$statusItem = $statusProp->getItem();
if ($statusItem) {
echo "Статус: " . $statusItem->getValue(); // Название
echo "XML_ID: " . $statusItem->getXmlId(); // Символьный код
}
}
}
Чтение изображений анонса и детальной картинки
Стандартные поля PREVIEW_PICTURE и DETAIL_PICTURE хранят ID файла:
$element = $dataClass::query()
->setSelect(['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PICTURE'])
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
// Анонсовая картинка
$previewPictureId = $element->getPreviewPicture();
if ($previewPictureId) {
$previewPicture = \CFile::GetFileArray($previewPictureId);
echo "Анонс: " . $previewPicture['SRC'] . "\n";
}
// Детальная картинка
$detailPictureId = $element->getDetailPicture();
if ($detailPictureId) {
$detailPicture = \CFile::GetFileArray($detailPictureId);
echo "Детальная: " . $detailPicture['SRC'] . "\n";
}
}
Выборка с JOIN на разделы
$elements = $dataClass::query()
->setSelect([
'ID',
'NAME',
'SECTION_' => 'IBLOCK_SECTION', // Префикс для полей раздела
])
->where('ACTIVE', 'Y')
->fetchCollection();
foreach ($elements as $element) {
$section = $element->getIblockSection();
if ($section) {
echo $element->getName() . ' → Раздел: ' . $section->getName();
}
}
Обновление элемента
Способ 1: через объект
// Находим элемент по символьному коду
$element = $dataClass::query()
->where('CODE', 'iphone-15')
->setLimit(1)
->fetchObject();
if ($element) {
// Изменение стандартных полей
$element
->setName('Новое название товара')
->setSort(50)
->set('PRICE', 109900)
->set('ARTICLE', 'ART-002');
// Для множественных свойств: очистка и добавление новых значений
$element
->removeAll('COLORS')
->addTo('COLORS', 'Белый')
->addTo('COLORS', 'Чёрный');
$result = $element->save();
}
Способ 2: статический метод update()
Когда ID известен (например, из запроса) и не нужно загружать объект целиком:
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$elementId = (int)$request->get('ELEMENT_ID');
if ($elementId > 0) {
$result = $dataClass::update($elementId, [
'NAME' => 'Обновлённое название',
'SORT' => 200,
]);
if (!$result->isSuccess()) {
print_r($result->getErrorMessages());
}
}
Статический
update()не позволяет обновлять свойства — только стандартные поля элемента. Для обновления свойств используйте объектный подход.
Удаление элемента
Способ 1: через объект
// Находим элемент, который нужно удалить
$element = $dataClass::query()
->where('CODE', 'discontinued-product')
->setLimit(1)
->fetchObject();
if ($element) {
$result = $element->delete();
if ($result->isSuccess()) {
echo 'Элемент удалён';
}
}
Способ 2: статический метод
Когда ID известен (например, пришёл из формы):
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
$elementId = (int)$request->get('DELETE_ID');
if ($elementId > 0) {
$result = $dataClass::delete($elementId);
if (!$result->isSuccess()) {
print_r($result->getErrorMessages());
}
}
При удалении автоматически:
- Удаляются значения свойств из соответствующих таблиц
- Очищается тегированный кеш инфоблока
- Удаляются фасетные индексы
Часть 5: Обработка ошибок
Все методы save(), update(), delete() возвращают объект Result, который позволяет проверить успешность операции.
Базовая обработка
$result = $element->save();
if ($result->isSuccess()) {
// Операция выполнена успешно
$id = $element->getId();
} else {
// Есть ошибки
foreach ($result->getErrors() as $error) {
echo $error->getMessage() . "\n";
echo $error->getCode() . "\n"; // Код ошибки, если есть
}
// Или просто массив сообщений
$messages = $result->getErrorMessages();
print_r($messages);
}
Проверка обязательных свойств
ORM автоматически проверяет обязательные свойства при добавлении:
$element = $dataClass::createObject();
$element->setName('Товар');
// Забыли установить обязательное свойство ARTICLE
$result = $element->save();
if (!$result->isSuccess()) {
// Получим ошибку: "Не заполнено обязательное свойство «Артикул»"
print_r($result->getErrorMessages());
}
Часть 6: Что происходит «под капотом»
При сохранении элемента через ORM выполняется ряд автоматических действий:
События ORM
// При добавлении
CommonElementTable::onBeforeAdd() // Валидация обязательных свойств
CommonElementTable::onAfterAdd() // Очистка кеша, обновление индексов
// При обновлении
CommonElementTable::onAfterUpdate() // Очистка кеша, обновление индексов
// При удалении
CommonElementTable::onAfterDelete() // Удаление свойств, очистка кеша, удаление индексов
Очистка кеша
После любой модификации вызывается CIBlock::clearIblockTagCache($iblockId), что инвалидирует тегированный кеш компонентов.
Фасетные индексы
При добавлении и обновлении вызывается Manager::updateElementIndex(), который обновляет фасетные индексы для умного фильтра.
⚠️ Индексация поиска при работе через D7 ORM
Важно! При добавлении и обновлении элементов и разделов через D7 ORM не происходит автоматическая индексация для полнотекстового поиска Bitrix (модуль
search).
Что происходит при работе через ORM
При добавлении/обновлении элементов через ORM ($element->save()) выполняется:
- ✅ Очистка тег-кеша инфоблока
- ✅ Обновление индекса фасетного поиска (
PropertyIndex\Manager::updateElementIndex) — используется для фильтрации в каталоге - ❌ НЕ вызывается
CIBlockElement::UpdateSearch()→ элемент не попадёт в полнотекстовый поиск
При добавлении/обновлении разделов через ORM (SectionTable::add/update):
- ✅ Очистка тег-кеша инфоблока
- ✅ Пересчёт дерева разделов
- ❌ НЕ обновляется фасетный индекс
- ❌ НЕ вызывается индексация для поиска
Сравнение: что вызывается при добавлении
| Метод | Тег-кеш | PropertyIndex | Полнотекстовый поиск |
|---|---|---|---|
CIBlockElement::Add() |
✅ | ✅ | ✅ CSearch::Index() |
CIBlockSection::Add() |
✅ | — | ✅ CSearch::Index() |
ORM $element->save() |
✅ | ✅ | ❌ |
ORM SectionTable::add() |
✅ | ❌ | ❌ |
Решение: ручная индексация
Если вам нужна поддержка полнотекстового поиска, вызывайте индексацию вручную после сохранения:
use Bitrix\Iblock\Iblock;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
Loader::includeModule('search');
// Добавляем элемент через ORM
$iblock = Iblock::wakeUp(IBLOCK_ID);
$dataClass = $iblock->getEntityDataClass();
$element = $dataClass::createObject();
$element->setName('Новый товар');
$element->setPrice(1000);
$result = $element->save();
if ($result->isSuccess()) {
$elementId = $result->getId();
// Вручную добавляем в поисковый индекс
\CIBlockElement::UpdateSearch($elementId);
}
Для разделов:
use Bitrix\Iblock\SectionTable;
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
Loader::includeModule('search');
$result = SectionTable::add([
'IBLOCK_ID' => IBLOCK_ID,
'NAME' => 'Новый раздел',
]);
if ($result->isSuccess()) {
$sectionId = $result->getId();
// Вручную добавляем в поисковый индекс
\CIBlockSection::UpdateSearch($sectionId);
}
Когда это критично
Индексация для поиска важна, если:
- На сайте используется встроенный поиск Bitrix (
bitrix:search.page) - Элементы/разделы должны находиться через компонент
bitrix:search.title - Вы используете API
CSearch::GetList()для поиска по контенту
Если поиск по инфоблокам не используется, дополнительная индексация не требуется.
Антипаттерны и частые ошибки
❌ Забыли скомпилировать entity
// ОШИБКА: класс не существует
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject();
Решение: всегда вызывайте compileEntity() перед использованием:
IblockTable::compileEntity('Products');
$element = \Bitrix\Iblock\Elements\ElementProductsTable::createObject();
❌ Свойство без CODE
Если у свойства не задан символьный код, оно не попадёт в скомпилированный класс:
// Свойство с ID=15, но без CODE — недоступно через ORM
$element->set('15', 'value'); // Не сработает
Решение: задайте CODE для всех свойств в настройках инфоблока.
❌ Обновление свойств через статический update()
// Это НЕ обновит свойства
$dataClass::update($id, [
'NAME' => 'Новое имя',
'PRICE' => 5000, // Игнорируется
]);
Решение: для обновления свойств используйте объектный подход:
$element = $dataClass::getByPrimary($id)->fetchObject();
$element->set('PRICE', 5000)->save();
❌ Неправильный регистр кода свойства
$element->set('price', 1000); // Не сработает
$element->set('Price', 1000); // Не сработает
$element->set('PRICE', 1000); // Правильно
Коды свойств в методе set() всегда указываются в верхнем регистре.
Сравнение с классическим API
| Операция | Классический API | ORM API |
|---|---|---|
| Создание элемента | CIBlockElement::Add($arFields) |
$element->save() |
| Обновление элемента | CIBlockElement::Update($id, $arFields) |
$element->save() |
| Удаление элемента | CIBlockElement::Delete($id) |
$element->delete() |
| Создание раздела | CIBlockSection::Add($arFields) |
$section->save() |
| Обновление раздела | CIBlockSection::Update($id, $arFields) |
$section->save() |
| Удаление раздела | CIBlockSection::Delete($id) |
$section->delete() |
| Автокомплит | ❌ | ✅ |
| Типизация | ❌ | ✅ |
| Автоочистка кеша | ✅ | ✅ |
| Индекс фасетного поиска | ✅ | ✅ (только элементы) |
| Индекс полнотекстового поиска | ✅ (автоматически) | ⚠️ Не поддерживается |
Заключение
ORM API инфоблоков — это современный способ работы с данными в Битрикс. Он предоставляет:
- Типизированный интерфейс с автокомплитом в IDE
- Автоматическое управление кешем и поисковыми индексами
- Единообразный подход для элементов и разделов
- Валидацию обязательных полей и свойств
Ключевые моменты:
- Задайте
API_CODEинфоблоку иCODEвсем свойствам - Вызывайте
compileEntity()перед работой с классом - Используйте объектный подход для работы со свойствами
- Проверяйте
Resultпосле каждой операции
ORM не заменяет классический API полностью — некоторые специфические операции (массовое удаление, работа с workflow) по-прежнему удобнее выполнять через CIBlockElement. Однако для стандартных CRUD-операций ORM — оптимальный выбор.
Теги:
Похожие статьи