Работа с элементами инфоблока

Элемент инфоблока — одна запись однотипных данных: новость, товар, статья. Элементы читают и записывают разными слоями API:

  • ORM-класс инфоблока Element{ApiCode}Table — получает элемент по ID, символьному коду или внешнему коду,

  • классический API CIBlockElement — добавляет, изменяет и удаляет элемент.

ORM-класс тоже умеет сохранять и удалять элементы, но выполняет только часть работы классического API. Например, он не привязывает элемент к разделу так, как это ожидают компоненты, и не удаляет файлы элемента. Различия собраны в разделе Выбрать API для записи.

Значения свойств записывают и читают способами из статьи Значения свойств элементов инфоблока. Выборка списка элементов с фильтром, сортировкой и постраничной навигацией описана в статье Выборка элементов инфоблока.

Перед работой подключите модуль iblock и задайте инфоблоку символьный код API в настройках инфоблока. Это не символьный код CODE инфоблока. Из кода API система строит имя ORM-класса Element{ApiCode}Table, подробнее — в разделе Представление инфоблоков в ORM.

Примеры используют инфоблок новостей с символьным кодом API News и его свойства AUTHOR — Автор, строка, и KEYWORDS — Ключевые слова, множественная строка. Полный набор свойств инфоблока новостей приведен в статье Значения свойств элементов инфоблока.

Переменная $iblockId содержит идентификатор инфоблока, $elementId — идентификатор элемента, $sectionId — идентификатор раздела. Замените коды свойств и значения полей на свои.

Выбрать API для записи

Добавляйте, изменяйте и удаляйте элементы методами CIBlockElement, а ORM-класс Element{ApiCode}Table используйте для чтения. Выбор API для остальных задач модуля описан в статье Как выбрать API инфоблоков, события записи — в статье События инфоблоков.

Классический API и ORM-класс записывают в базу данных одну и ту же строку элемента. Различаются действия, которые методы выполняют вместе с записью. Классические методы применяют настройки полей инфоблока, привязывают элемент к разделам, обновляют поисковый индекс и вызывают события. ORM-методы save() и delete() записывают переданные поля и значения свойств, остальное перечислено в таблице.

Таблица упоминает хранилища, которые методы записи обновляют вместе с элементом:

  • тегированный кеш — кеш компонентов с меткой инфоблока, подробнее — в статье Кеширование,

  • фасетный индекс — таблица значений свойств, по которой умный фильтр bitrix:catalog.smart.filter отбирает элементы, подробнее — в статье Фасетный индекс и умный фильтр,

  • поисковый индекс модуля search — данные, по которым работает поиск по сайту.

Таблица сравнивает результат записи одного и того же элемента. Поведение одинаково для обеих версий хранения свойств.

Что происходит при записи CIBlockElement::Add(), Update(), Delete() ORM save() и delete()
Признак привязки к разделам IN_SECTIONS Метод устанавливает Y, если элемент привязан к разделу Метод оставляет N. Классическая выборка корня инфоблока CIBlockElement::GetList() с SECTION_ID 0 и компоненты показывают такой элемент вне раздела
Внешний код XML_ID Если код не передан, метод записывает в него ID элемента Поле остается пустым
Авторы CREATED_BY и MODIFIED_BY Метод Add() записывает ID текущего пользователя, если поля не переданы. Без авторизованного пользователя, например в агенте или консольном скрипте, метод записывает 0, поэтому передайте поля сами. Метод Update() меняет MODIFIED_BY, только если поле передано Поля остаются пустыми
Дата изменения TIMESTAMP_X Метод Update() обновляет дату Метод save() у существующего элемента дату не меняет
Настройки полей инфоблока Метод проверяет обязательные поля и уникальность символьного кода Настройки не применяются, элемент сохраняется без обязательного кода и с повторяющимся кодом
Обязательные свойства Метод проверяет свойства, переданные в PROPERTY_VALUES, при добавлении и изменении Метод проверяет все обязательные свойства, но только при добавлении
Поисковый индекс модуля search Метод добавляет, обновляет и удаляет запись Индекс не меняется. После удаления запись поиска остается
Файлы картинок и файловых свойств Метод Delete() удаляет файлы элемента Метод delete() оставляет файлы в файловом хранилище
События OnBeforeIBlockElementAdd, OnAfterIBlockElementUpdate и другие Метод вызывает ORM не вызывает
Права элемента в режиме расширенных прав Метод создает и удаляет права элемента ORM не меняет права
Тегированный кеш и фасетный индекс Метод сбрасывает кеш и обновляет индекс Метод сбрасывает кеш и обновляет индекс, если изменились поля элемента

Общий ORM-класс Bitrix\Iblock\ElementTable записывать элементы не может. Его методы add(), update() и delete() возвращают ошибку «Для добавления элементов инфоблоков используйте вызов CIBlockElement::Add()».

Если код проекта уже записывает элементы через ORM, заполните недостающие поля способом из раздела Если элемент записывает ORM.

Проверить права пользователя

Методы CIBlockElement::Add(), Update(), Delete() и ORM-класс не проверяют права текущего пользователя. Если код выполняет действие пользователя, например в публичной форме, проверьте право перед чтением или записью.

Классы прав проверяют операцию для инфоблока, раздела или элемента:

  • CIBlockElementRights::UserHasRightTo() с операцией element_read — право прочитать элемент,

  • CIBlockSectionRights::UserHasRightTo() с операцией section_element_bind — право добавить элемент в раздел,

  • CIBlockElementRights::UserHasRightTo() с операцией element_edit — право изменить элемент,

  • CIBlockElementRights::UserHasRightTo() с операцией element_delete — право удалить элемент.

Методы принимают три аргумента — ID инфоблока, ID проверяемого объекта и код операции. Проверяемый объект — элемент для CIBlockElementRights, раздел для CIBlockSectionRights и сам инфоблок для CIBlockRights, поэтому у метода CIBlockRights::UserHasRightTo() первые два аргумента совпадают.

Методы работают в простом и расширенном режиме прав инфоблока и возвращают true, если текущему пользователю разрешена операция. Для администратора методы всегда возвращают true. Если в коде нет объекта текущего пользователя $USER, например в агенте, методы возвращают false для любой операции.

Пример. Проверьте, может ли текущий пользователь изменить элемент.

if (!\CIBlockElementRights::UserHasRightTo($iblockId, $elementId, 'element_edit'))
{
    throw new \RuntimeException('Недостаточно прав для изменения элемента');
}

Право добавить элемент проверяйте для раздела, в который код добавляет элемент. Если элемент добавляется в корень инфоблока, проверьте ту же операцию section_element_bind методом CIBlockRights::UserHasRightTo() для самого инфоблока.

Пример. Проверьте право добавить элемент в раздел $sectionId или в корень инфоблока, если раздел не задан.

$canAdd = $sectionId > 0
    ? \CIBlockSectionRights::UserHasRightTo($iblockId, $sectionId, 'section_element_bind')
    : \CIBlockRights::UserHasRightTo($iblockId, $iblockId, 'section_element_bind')
;

if (!$canAdd)
{
    throw new \RuntimeException('Недостаточно прав для добавления элемента');
}

Проверяйте права, когда код выполняет действие конкретного пользователя. Фоновый код, например агент или импорт, работает без пользователя, поэтому в нем права не проверяют. Чтобы получить список элементов с проверкой прав, используйте компонент или метод CIBlockElement::GetList() с фильтром 'CHECK_PERMISSIONS' => 'Y'.

Назначение прав инфоблоку описано в разделе Настроить права доступа.

Добавить элемент

Метод CIBlockElement::Add() создает элемент по массиву полей и значений свойств. Метод вызывают у объекта класса CIBlockElement, потому что текст ошибки он сохраняет в объекте. Текст ошибки возвращает метод getLastError().

Подготовить поля

Массив полей содержит поля элемента и ключ PROPERTY_VALUES со значениями свойств. Таблица перечисляет поля, которые нужны для типовой публикации. Полный список полей элемента приведен в разделе Элементы.

Обязательные поля обозначены *.

Ключ Описание
IBLOCK_ID*
int
Идентификатор инфоблока
NAME*
string
Название элемента
CODE
string
Символьный код. Метод не создает код из названия, даже если в настройках поля включена транслитерация. Код по этим настройкам возвращает метод createMnemonicCode()
XML_ID
string
Внешний код для обмена с другой системой. Если код не передан, метод записывает в него ID элемента
ACTIVE
string
Активность, Y или N. По умолчанию Y
ACTIVE_FROM, ACTIVE_TO
string
Начало и окончание активности в формате даты сайта
SORT
int
Сортировка. По умолчанию 500
IBLOCK_SECTION_ID
int
Основной раздел элемента
IBLOCK_SECTION
array
Все разделы элемента, если элемент привязан к нескольким разделам
PREVIEW_TEXT, DETAIL_TEXT
string
Описание для анонса и детальное описание
PREVIEW_TEXT_TYPE, DETAIL_TEXT_TYPE
string
Тип описания, text или html
PREVIEW_PICTURE, DETAIL_PICTURE
array
Картинка для анонса и детальная картинка — массив файла, который возвращает CFile::MakeFileArray()
PROPERTY_VALUES
array
Значения свойств. Ключ — код или ID свойства, значение — в формате из раздела Значение по типу свойства
IPROPERTY_TEMPLATES
array
SEO-шаблоны элемента, формат приведен в разделе Передать шаблоны при записи

Значение множественного свойства передайте массивом. Для свойства-списка передайте ID варианта, найденный способом из раздела Найти вариант списка.

Метод проверяет обязательные свойства только среди переданных в PROPERTY_VALUES. Если ключ не передан, метод создаст элемент без обязательных свойств. Передавайте в PROPERTY_VALUES все обязательные свойства инфоблока.

Чтобы создать символьный код из названия по настройкам поля Символьный код, вызовите метод createMnemonicCode() у объекта CIBlockElement. Метод принимает массив с ключами NAME и IBLOCK_ID и возвращает код или null, если транслитерация не настроена. Если в настройках включена проверка уникальности, метод добавляет к занятому коду суффикс, например _1.

Пример. Создайте символьный код для новости по ее названию.

$element = new \CIBlockElement();

$code = $element->createMnemonicCode([
    'NAME' => 'Обновление системы безопасности',
    'IBLOCK_ID' => $iblockId,
]);
// obnovlenie-sistemy-bezopasnosti

Создать элемент

Метод Add() возвращает ID нового элемента или false, если элемент не создан. Причину ошибки возвращает метод getLastError(). Если ошибок несколько, они разделены тегом <br>.

Пример. Создайте активную новость в разделе с автором и ключевыми словами.

use Bitrix\Main\Loader;

if (!Loader::includeModule('iblock'))
{
    throw new \RuntimeException('Не удалось подключить модуль iblock');
}

$element = new \CIBlockElement();

$elementId = $element->Add([
    'IBLOCK_ID' => $iblockId,
    'NAME' => 'Обновление системы безопасности',
    'CODE' => 'security-update',
    'ACTIVE' => 'Y',
    'IBLOCK_SECTION_ID' => $sectionId,
    'PREVIEW_TEXT' => 'Система получила обновление безопасности.',
    'PROPERTY_VALUES' => [
        'AUTHOR' => 'Пресс-служба',
        'KEYWORDS' => ['обновление', 'безопасность'],
    ],
]);

if (!$elementId)
{
    throw new \RuntimeException($element->getLastError());
}

Следующие примеры раздела используют объект $element класса CIBlockElement из этого примера.

Метод сохраняет элемент, значения свойств и привязку к разделу, затем добавляет элемент в поисковый индекс, обновляет фасетный индекс и сбрасывает тегированный кеш инфоблока. Перед сохранением метод вызывает событие OnBeforeIBlockElementAdd, после — OnAfterIBlockElementAdd. Обработчик события OnBeforeIBlockElementAdd может отменить добавление, тогда метод вернет false, а getLastError() — текст, который обработчик передал в $APPLICATION->ThrowException().

У метода Add() есть необязательные параметры после массива полей:

  • $bWorkFlow — сохранить элемент с учетом документооборота инфоблока, по умолчанию false,

  • $bUpdateSearch — добавить элемент в поисковый индекс, по умолчанию true,

  • $bResizePictures — применить к картинкам настройки полей инфоблока, например уменьшение и создание картинки анонса из детальной, по умолчанию false.

Привязать элемент к нескольким разделам

Элемент может находиться в нескольких разделах. Передайте ID всех разделов в ключе IBLOCK_SECTION, а основной раздел — в ключе IBLOCK_SECTION_ID. Основной раздел используют компоненты, например для адреса детальной страницы.

Пример. Создайте элемент в двух разделах. Основной раздел — $sectionId, дополнительный — $secondSectionId. Результат проверьте так же, как в примере из раздела Создать элемент.

$elementId = $element->Add([
    'IBLOCK_ID' => $iblockId,
    'NAME' => 'Итоги конференции',
    'IBLOCK_SECTION_ID' => $sectionId,
    'IBLOCK_SECTION' => [$sectionId, $secondSectionId],
    'PROPERTY_VALUES' => [
        'AUTHOR' => 'Пресс-служба',
    ],
]);

Раздел должен принадлежать тому же инфоблоку. Метод не создает разделы, создание раздела описано в разделе Добавить раздел.

Добавить картинки

Картинку для анонса и детальную картинку передайте массивом файла. Метод CFile::MakeFileArray() создает такой массив по пути к файлу на сервере. Переменная $picturePath содержит путь к существующему файлу, доступному текущему PHP-процессу.

Пример. Создайте элемент с детальной картинкой и картинкой анонса из нее по настройкам инфоблока.

$elementId = $element->Add(
    [
        'IBLOCK_ID' => $iblockId,
        'NAME' => 'Новость с картинкой',
        'DETAIL_PICTURE' => \CFile::MakeFileArray($picturePath),
        'PROPERTY_VALUES' => [
            'AUTHOR' => 'Пресс-служба',
        ],
    ],
    false,
    true,
    true
);

Четвертый аргумент true включает обработку картинок по настройкам полей инфоблока. Если в настройках поля Картинка для анонса включено создание из детальной картинки, метод сохранит обе картинки. Без этого аргумента метод сохранит только переданную картинку.

Получить элемент

ORM-класс Element{ApiCode}Table читает поля элемента и значения свойств в одном запросе. Система создает класс по символьному коду API инфоблока. Метод IblockTable::compileEntity() компилирует класс и возвращает его описание или false, если инфоблок не найден. Подробнее — в разделе Получить ORM-класс элементов.

Пример. Получите ORM-класс элементов инфоблока новостей.

use Bitrix\Iblock\IblockTable;

$elementEntity = IblockTable::compileEntity('News');

if (!$elementEntity)
{
    throw new \RuntimeException('Не удалось скомпилировать ORM-класс элементов');
}

$elementNewsClass = $elementEntity->getDataClass();

Примеры ниже используют переменную $elementNewsClass из этого раздела.

Получить элемент по ID

Метод getByPrimary() создает запрос по идентификатору элемента. В параметре select перечислите поля элемента и коды свойств. Метод fetchObject() возвращает объект элемента или null, если элемента нет в инфоблоке.

Пример. Получите название, раздел и автора элемента.

$news = $elementNewsClass::getByPrimary($elementId, [
    'select' => ['ID', 'NAME', 'CODE', 'ACTIVE', 'IBLOCK_SECTION_ID', 'AUTHOR'],
])
    ->fetchObject()
;

if ($news === null)
{
    throw new \RuntimeException('Элемент не найден');
}

$newsName = $news->getName();
$mainSectionId = $news->getIblockSectionId();
$newsAuthor = $news->get('AUTHOR')?->getValue();

Поля элемента объект возвращает методами вида get<ИмяПоля>() и задает методами set<ИмяПоля>(), где имя поля записано в CamelCase без подчеркиваний, например getName(), getActive(), getIblockSectionId() для IBLOCK_SECTION_ID и setXmlId() для XML_ID. Значение свойства возвращает метод get() с кодом свойства. Без кода свойства в select метод get() вернет null. Чтение свойств разных типов описано в разделе Прочитать значения.

Найти элемент по символьному или внешнему коду

Чтобы найти элемент по символьному коду CODE или внешнему коду XML_ID, постройте запрос методом query() и задайте условие методом where().

Пример. Найдите элемент по символьному коду.

$news = $elementNewsClass::query()
    ->setSelect(['ID', 'NAME', 'AUTHOR'])
    ->where('CODE', 'security-update')
    ->setLimit(1)
    ->fetchObject()
;

Символьный код уникален, только если в настройках поля Символьный код включена проверка уникальности. Без проверки под условие могут попасть несколько элементов, а setLimit(1) вернет первый из них. Внешний код XML_ID используют для связи с другой системой, поэтому обеспечьте его уникальность в коде обмена.

Получить все разделы элемента

Поле IBLOCK_SECTION_ID содержит только основной раздел. Все привязки элемента к разделам хранит ORM-класс Bitrix\Iblock\SectionElementTable. Прямую привязку отличает пустое поле ADDITIONAL_PROPERTY_ID, заполненное поле означает привязку через свойство.

Пример. Получите ID всех разделов, к которым привязан элемент.

use Bitrix\Iblock\SectionElementTable;

$sectionIds = array_column(
    SectionElementTable::getList([
        'select' => ['IBLOCK_SECTION_ID'],
        'filter' => [
            '=IBLOCK_ELEMENT_ID' => $elementId,
            '=ADDITIONAL_PROPERTY_ID' => null,
        ],
    ])
        ->fetchAll(),
    'IBLOCK_SECTION_ID'
);

Учесть, что отбирает ORM-запрос

ORM-класс автоматически ограничивает запрос своим инфоблоком. Если элемент выводится на публичной странице, добавьте в запрос условия, которые проверяют стандартные компоненты. Сам запрос их не добавляет:

  • неактивные элементы попадают в результат — добавьте where('ACTIVE', 'Y'), если нужны только активные,

  • период активности ACTIVE_FROM и ACTIVE_TO не проверяется — добавьте условия из примера ниже,

  • права текущего пользователя не проверяются — проверьте их способом из раздела Проверить права пользователя,

  • копии элементов из истории документооборота и неопубликованные элементы попадают в результат — добавьте where('WF_STATUS_ID', 1) и whereNull('WF_PARENT_ELEMENT_ID'), если в инфоблоке включен документооборот.

Пример. Найдите элемент по символьному коду, только если он активен и период активности не закончился или не задан.

use Bitrix\Main\ORM\Query\Query;
use Bitrix\Main\Type\DateTime;

$now = new DateTime();

$news = $elementNewsClass::query()
    ->setSelect(['ID', 'NAME'])
    ->where('CODE', 'security-update')
    ->where('ACTIVE', 'Y')
    ->where(Query::filter()
        ->logic('or')
        ->whereNull('ACTIVE_FROM')
        ->where('ACTIVE_FROM', '<=', $now)
    )
    ->where(Query::filter()
        ->logic('or')
        ->whereNull('ACTIVE_TO')
        ->where('ACTIVE_TO', '>=', $now)
    )
    ->setLimit(1)
    ->fetchObject()
;

Метод классического API CIBlockElement::GetList() по умолчанию исключает копии документооборота и неопубликованные элементы. Права он проверяет, только если в фильтре передан CHECK_PERMISSIONS со значением Y.

Изменить элемент

Метод CIBlockElement::Update() меняет поля существующего элемента. Метод принимает ID элемента и массив изменяемых полей в формате из раздела Подготовить поля. Поля, которые не переданы, остаются без изменений.

Метод возвращает true при успешном изменении и false при ошибке. Текст ошибки возвращает метод getLastError(). Если элемента с переданным ID нет, метод вернет false с пустым текстом ошибки, поэтому в примере есть запасной текст. Необязательные параметры $bWorkFlow, $bUpdateSearch и $bResizePictures работают так же, как у метода Add(), но идут после ID и массива полей, начиная с третьего аргумента.

Пример. Измените название и текст анонса элемента.

$element = new \CIBlockElement();

$isUpdated = $element->Update($elementId, [
    'NAME' => 'Критическое обновление системы безопасности',
    'PREVIEW_TEXT' => 'Установите обновление до конца недели.',
]);

if (!$isUpdated)
{
    throw new \RuntimeException($element->getLastError() ?: 'Элемент не изменен');
}

Метод обновляет дату изменения, поисковый и фасетный индексы, сбрасывает тегированный кеш и вызывает события OnBeforeIBlockElementUpdate и OnAfterIBlockElementUpdate, подробнее — в разделе События элементов. Вычисленные SEO-свойства элемента метод не пересчитывает. Если название, тексты или свойства элемента используются в SEO-шаблонах, после Update() вызовите clearValues(), как описано в разделе Сбросить вычисленные значения.

Чтобы снять элемент с публикации без удаления, передайте 'ACTIVE' => 'N'. Стандартные компоненты, например bitrix:news.list и bitrix:news.detail, по умолчанию не показывают неактивные элементы, а данные элемента сохраняются.

Не передавайте в Update() ключ PROPERTY_VALUES, если нужно изменить только часть свойств. Метод удалит значения всех нефайловых свойств, которые не переданы в массиве. Чтобы изменить отдельные свойства, используйте способы из статьи Значения свойств элементов инфоблока. Если проект обрабатывает события классического API, используйте метод SetPropertyValuesEx() из раздела Записать значения с событиями классического API.

Перенести элемент в другой раздел

Чтобы перенести элемент, передайте новый раздел в ключе IBLOCK_SECTION_ID. Метод заменит все привязки элемента одним разделом.

Пример. Перенесите элемент в раздел $newSectionId. Результат проверьте так же, как в примере из раздела Изменить элемент.

$isUpdated = $element->Update($elementId, [
    'IBLOCK_SECTION_ID' => $newSectionId,
]);

Чтобы оставить элемент в нескольких разделах, передайте их все в ключе IBLOCK_SECTION, а основной — в IBLOCK_SECTION_ID. Основной раздел должен входить в массив IBLOCK_SECTION. Иначе метод отбросит IBLOCK_SECTION_ID, и основным станет раздел с наименьшим ID из массива. Исключение — у поля IBLOCK_SECTION в настройках инфоблока включен параметр KEEP_IBLOCK_SECTION_ID и прежний основной раздел остался в массиве, тогда метод сохранит прежний основной раздел.

Чтобы убрать элемент из всех разделов, передайте IBLOCK_SECTION с пустым массивом.

Заменить или удалить картинку

Чтобы заменить картинку, передайте новый массив файла в PREVIEW_PICTURE или DETAIL_PICTURE. Метод сохранит новый файл и удалит прежний.

Пример. Замените детальную картинку и удалите картинку анонса. Переменная $newPicturePath содержит путь к новому файлу на сервере. Результат проверьте так же, как в примере из раздела Изменить элемент.

$isUpdated = $element->Update($elementId, [
    'DETAIL_PICTURE' => \CFile::MakeFileArray($newPicturePath),
    'PREVIEW_PICTURE' => ['del' => 'Y'],
]);

Массив с ключом del и значением Y удаляет картинку и ее файл.

Удалить элемент

Статический метод CIBlockElement::Delete() удаляет элемент вместе со связанными данными:

  • значения свойств и файлы файловых свойств,

  • картинку для анонса и детальную картинку,

  • привязки к разделам,

  • права элемента и SEO-шаблоны,

  • запись в поисковом и фасетном индексах,

  • ссылки на элемент в свойствах типа Привязка к элементам у других элементов,

  • копии элемента из истории документооборота.

Метод возвращает true после удаления, в том числе для ID, которого нет в базе. Если отсутствие элемента считается ошибкой, проверьте существование элемента до вызова. Метод возвращает false, если удаление отменил обработчик события или произошла ошибка базы данных. Как отменить удаление из обработчика и получить текст ошибки, описано в разделе Отменить операцию.

Перед удалением метод вызывает событие OnBeforeIBlockElementDelete. Обработчик события может отменить удаление и передать текст ошибки через $APPLICATION->ThrowException(). Чтобы получить этот текст, вызовите $APPLICATION->GetException().

Метод удаляет данные несколькими запросами. Выполните удаление в транзакции, чтобы при ошибке не осталось частично удаленного элемента.

Пример. Удалите элемент и отмените изменения, если удаление не выполнено.

use Bitrix\Main\Application;

global $APPLICATION;

$connection = Application::getConnection();
$connection->startTransaction();

if (\CIBlockElement::Delete($elementId))
{
    $connection->commitTransaction();
}
else
{
    $connection->rollbackTransaction();

    $exception = $APPLICATION->GetException();
    throw new \RuntimeException($exception ? $exception->GetString() : 'Элемент не удален');
}

Удаление необратимо. Транзакция не восстанавливает удаленные файлы. Если данные элемента нужны для журнала или восстановления, прочитайте их до вызова Delete().

Записать много элементов

При импорте код добавляет и изменяет элементы в цикле. Каждый вызов Add() и Update() сбрасывает тегированный кеш инфоблока и обновляет фасетный индекс. Повторный запуск импорта без проверки создаст дубли.

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

Перед записью найдите элемент по внешнему коду XML_ID. Если элемент есть, измените его методом Update(), если нет — создайте методом Add() с этим внешним кодом.

Пример. Добавьте или обновите новость по внешнему коду. Переменные $externalId, $name и $author содержат внешний код, название и автора из данных импорта. Переменная $elementNewsClass содержит ORM-класс из раздела Получить элемент.

$existing = $elementNewsClass::query()
    ->setSelect(['ID'])
    ->where('XML_ID', $externalId)
    ->setLimit(1)
    ->fetchObject()
;

$element = new \CIBlockElement();

if ($existing === null)
{
    $isSaved = (bool)$element->Add([
        'IBLOCK_ID' => $iblockId,
        'XML_ID' => $externalId,
        'NAME' => $name,
        'PROPERTY_VALUES' => [
            'AUTHOR' => $author,
        ],
    ]);
}
else
{
    // Свойства существующего элемента меняйте способами из статьи о значениях свойств,
    // PROPERTY_VALUES в Update() удалит непереданные свойства
    $isSaved = $element->Update($existing->getId(), [
        'NAME' => $name,
    ]);
}

if (!$isSaved)
{
    throw new \RuntimeException($element->getLastError() ?: 'Элемент не сохранен');
}

Проверка перед записью защищает от дублей при последовательном запуске. Два параллельных процесса могут одновременно не найти элемент и создать два элемента с одним внешним кодом. Не запускайте импорт одного инфоблока параллельно.

Отложить обновление кеша и индексов

Чтобы не сбрасывать кеш и не пересчитывать фасетный индекс после каждого элемента, отложите эти действия до конца импорта. Так поступает импорт из 1С в ядре:

  • CIBlock::disableClearTagCache() и CIBlock::enableClearTagCache() — отключают и снова включают сброс тегированного кеша инфоблока при записи,

  • Bitrix\Iblock\PropertyIndex\Manager::enableDeferredIndexing() и disableDeferredIndexing() — включают и выключают накопление элементов для фасетного индекса вместо пересчета,

  • Manager::runDeferredIndexing() — пересчитывает фасетный индекс для накопленных элементов инфоблока.

Методы включения и выключения работают как счетчик, поэтому вызывайте их парами. Вызов в блоке finally возвращает обычный режим, даже если импорт прервался исключением. После цикла вызовите runDeferredIndexing() и сбросьте тегированный кеш инфоблока по тегу iblock_id_<ID инфоблока> методом clearByTag() тегированного кеша приложения.

Пример. Импортируйте элементы из массива $items и обновите кеш и фасетный индекс один раз в конце.

use Bitrix\Iblock\PropertyIndex\Manager;
use Bitrix\Main\Application;

\CIBlock::disableClearTagCache();
Manager::enableDeferredIndexing();

try
{
    foreach ($items as $item)
    {
        // Запись элемента способом из раздела «Не создавать дубли при повторном запуске»
    }
}
finally
{
    Manager::disableDeferredIndexing();
    \CIBlock::enableClearTagCache();
}

Manager::runDeferredIndexing($iblockId);
Application::getInstance()->getTaggedCache()->clearByTag('iblock_id_' . $iblockId);

Поисковый индекс методы Add() и Update() обновляют для каждого элемента. Чтобы отложить и его, передайте false в параметр $bUpdateSearch. У метода Add() это третий аргумент, у метода Update() — четвертый. После импорта вызовите CIBlockElement::UpdateSearch($id, true) для каждого записанного элемента. Второй аргумент true перезаписывает запись индекса. Без него модуль поиска пропускает элемент, у которого не изменилась дата начала активности.

Если элемент записывает ORM

ORM-методы save() и delete() подходят, если инфоблок не относится к торговому каталогу и не использует обработчики событий, поиск, расширенные права и документооборот. Например, для служебного инфоблока, данные которого выводит только код проекта. Для товаров каталога используйте способы из статьи Работа с товарами и торговыми предложениями.

Чтобы элемент, записанный через ORM, не отличался от записанного классическим API, задайте поля, которые ORM не заполняет:

  1. При добавлении в раздел вызовите setIblockSectionId() и setInSections(true). Без признака IN_SECTIONS классическая выборка и компоненты покажут элемент в корне инфоблока.

  2. При добавлении задайте setXmlId(), setCreatedBy() и setModifiedBy().

  3. При изменении задайте setTimestampX(new \Bitrix\Main\Type\DateTime()) и setModifiedBy().

  4. Проверьте уникальность символьного кода и обязательные свойства в коде проекта.

  5. После save() вызовите CIBlockElement::UpdateSearch($elementId, true). Если модуль поиска не установлен, метод ничего не делает.

  6. После delete() удалите связанные данные. Метод CFile::Delete($fileId) удаляет каждый файл картинок и файловых свойств — получите их ID до удаления. Метод CSearch::DeleteIndex('iblock', $elementId) удаляет запись поиска. Вызов (new \Bitrix\Iblock\InheritedProperty\ElementTemplates($iblockId, $elementId))->delete() удаляет SEO-шаблоны элемента.

Пример. Добавьте элемент в раздел через ORM с полями, которые заполняет классический API. Переменная $elementNewsClass содержит ORM-класс из раздела Получить элемент. Значение свойства задает метод set() с кодом свойства, подробнее — в разделе Записать значения. Переменная $userId содержит ID пользователя, от имени которого выполняется запись. Для действия пользователя на сайте возьмите ID текущего пользователя из \Bitrix\Main\Engine\CurrentUser::get()->getId(), для фонового импорта — ID служебного пользователя проекта.

$newElement = $elementNewsClass::createObject();
$newElement->setName('Обновление системы безопасности');
$newElement->setCode('security-update');
$newElement->setXmlId('security-update');
$newElement->setIblockSectionId($sectionId);
$newElement->setInSections(true);
$newElement->setCreatedBy($userId);
$newElement->setModifiedBy($userId);
$newElement->set('AUTHOR', 'Пресс-служба');

$result = $newElement->save();

if (!$result->isSuccess())
{
    throw new \RuntimeException(
        implode('; ', $result->getErrorMessages())
    );
}

$elementId = $newElement->getId();

\CIBlockElement::UpdateSearch($elementId, true);

Поле IBLOCK_ID объект получает от ORM-класса инфоблока, вызывать setIblockId() не нужно.

Статический метод $elementNewsClass::update() записывает только поля элемента. Значения свойств он не сохраняет и при этом возвращает успешный результат. Меняйте свойства через объект элемента.

Заменить методы классического API для чтения

Для чтения элементов у классического API есть ORM-замены. Таблица помогает перенести существующий код на ORM-класс.

Метод классического API Замена в ORM Что учесть
CIBlockElement::GetByID() getByPrimary() и fetchObject() Метод возвращает элемент любого инфоблока и не проверяет активность. ORM-класс ищет элемент только в своем инфоблоке
CIBlockElement::GetList() с фильтром по ID или CODE query(), where() и fetchObject() Метод исключает копии документооборота и проверяет права с CHECK_PERMISSIONS Y. ORM-запрос этого не делает, условия описаны в разделе Учесть, что отбирает ORM-запрос
GetNext() и GetNextElement() результата GetList() fetchObject() или fetchCollection() Метод GetNext() возвращает значения с HTML-экранированием и, если поле DETAIL_PAGE_URL есть в выборке, адрес детальной страницы по шаблону инфоблока. ORM возвращает исходные значения без адреса страницы. Экранируйте значения при выводе
GetFields() и GetProperties() результата GetNextElement() Методы объекта get<ИмяПоля>() и get() с кодом свойства Метод GetProperties() выполняет отдельный запрос свойств для каждого элемента

Замены методов, которые читают и записывают значения свойств, приведены в разделе Заменить методы классического API.

Проверить результат

Перед публикацией кода выполните на тестовом инфоблоке:

  1. Создайте элемент в разделе и проверьте, что Add() вернул ID.

  2. Откройте раздел в административном разделе или на странице с компонентом списка и убедитесь, что элемент виден в разделе и не виден в корне инфоблока.

  3. Получите элемент через getByPrimary() и сравните поля и свойства с исходными данными.

  4. Измените одно поле через Update() и убедитесь, что значения свойств сохранились.

  5. Перенесите элемент в другой раздел и проверьте привязки.

  6. Передайте некорректные данные, например элемент без названия, и проверьте обработку текста ошибки getLastError().

  7. Удалите элемент и убедитесь, что повторный getByPrimary() возвращает null.

Ошибки записи свойств и способы ускорить выборку собраны в статье Производительность и частые ошибки.