Работа с элементами инфоблока
Элемент инфоблока — одна запись однотипных данных: новость, товар, статья. Элементы читают и записывают разными слоями 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 |
Название элемента |
CODEstring |
Символьный код. Метод не создает код из названия, даже если в настройках поля включена транслитерация. Код по этим настройкам возвращает метод createMnemonicCode() |
XML_IDstring |
Внешний код для обмена с другой системой. Если код не передан, метод записывает в него ID элемента |
ACTIVEstring |
Активность, Y или N. По умолчанию Y |
ACTIVE_FROM, ACTIVE_TOstring |
Начало и окончание активности в формате даты сайта |
SORTint |
Сортировка. По умолчанию 500 |
IBLOCK_SECTION_IDint |
Основной раздел элемента |
IBLOCK_SECTIONarray |
Все разделы элемента, если элемент привязан к нескольким разделам |
PREVIEW_TEXT, DETAIL_TEXTstring |
Описание для анонса и детальное описание |
PREVIEW_TEXT_TYPE, DETAIL_TEXT_TYPEstring |
Тип описания, text или html |
PREVIEW_PICTURE, DETAIL_PICTUREarray |
Картинка для анонса и детальная картинка — массив файла, который возвращает CFile::MakeFileArray() |
PROPERTY_VALUESarray |
Значения свойств. Ключ — код или ID свойства, значение — в формате из раздела Значение по типу свойства |
IPROPERTY_TEMPLATESarray |
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 не заполняет:
-
При добавлении в раздел вызовите
setIblockSectionId()иsetInSections(true). Без признакаIN_SECTIONSклассическая выборка и компоненты покажут элемент в корне инфоблока. -
При добавлении задайте
setXmlId(),setCreatedBy()иsetModifiedBy(). -
При изменении задайте
setTimestampX(new \Bitrix\Main\Type\DateTime())иsetModifiedBy(). -
Проверьте уникальность символьного кода и обязательные свойства в коде проекта.
-
После
save()вызовитеCIBlockElement::UpdateSearch($elementId, true). Если модуль поиска не установлен, метод ничего не делает. -
После
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.
Проверить результат
Перед публикацией кода выполните на тестовом инфоблоке:
-
Создайте элемент в разделе и проверьте, что
Add()вернулID. -
Откройте раздел в административном разделе или на странице с компонентом списка и убедитесь, что элемент виден в разделе и не виден в корне инфоблока.
-
Получите элемент через
getByPrimary()и сравните поля и свойства с исходными данными. -
Измените одно поле через
Update()и убедитесь, что значения свойств сохранились. -
Перенесите элемент в другой раздел и проверьте привязки.
-
Передайте некорректные данные, например элемент без названия, и проверьте обработку текста ошибки
getLastError(). -
Удалите элемент и убедитесь, что повторный
getByPrimary()возвращаетnull.
Ошибки записи свойств и способы ускорить выборку собраны в статье Производительность и частые ошибки.