07.12.2025 20 мин чтения

Работа с разделами и элементами инфоблока через D7 ORM

Подробное руководство для разработчиков Битрикс, которые хотят перейти от устаревших методов CIBlockElement и CIBlockSection к современному объектному API. Если вы устали от ручного формирования массивов и хотите получить автокомплит, типизацию и чистый код — эта статья для вас.

Кирилл Новожилов

Кирилл Новожилов

Автор

Работа с разделами и элементами инфоблока через D7 ORM

Что вы узнаете?

  • Как подготовить инфоблок для работы с 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() выполняет несколько действий:

  1. Находит инфоблок по API_CODE
  2. Определяет версию хранения свойств
  3. Загружает все свойства инфоблока
  4. Генерирует класс с полями и связями
  5. Кеширует результат — повторная компиляция не создаёт новый класс

Формирование имени класса для элементов

Имя класса формируется по шаблону: \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
  • Автоматическое управление кешем и поисковыми индексами
  • Единообразный подход для элементов и разделов
  • Валидацию обязательных полей и свойств

Ключевые моменты:

  1. Задайте API_CODE инфоблоку и CODE всем свойствам
  2. Вызывайте compileEntity() перед работой с классом
  3. Используйте объектный подход для работы со свойствами
  4. Проверяйте Result после каждой операции

ORM не заменяет классический API полностью — некоторые специфические операции (массовое удаление, работа с workflow) по-прежнему удобнее выполнять через CIBlockElement. Однако для стандартных CRUD-операций ORM — оптимальный выбор.

Опубликовано 1 неделю назад

Похожие статьи