1С-Битрикс: работа с инфоблоками через D7 ORM
Старый API (CIBlockElement::GetList) работает до сих пор, но у него неудобная выборка свойств, массивы «магических» ключей и нет нормальных связей. D7 ORM даёт классы-таблицы с автодополнением в IDE, объекты и связи через точку.
Главная особенность: для элементов инфоблоков ORM-класс генерируется отдельно для каждого инфоблока. Чтобы он появился, у инфоблока должен быть заполнен символьный код API.
В примерах — инфоблок «Статьи» со свойствами:
| Свойство | Код | Тип |
|---|---|---|
| Автор | AUTHOR |
Строка |
| Теги | TAGS |
Строка, множественное |
| Раздел сайта | SECTION_LINK |
Привязка к элементам другого инфоблока |
Получить ORM-класс инфоблока
Для инфоблока «Статьи» пока нет ORM-класса: обращение к нему заканчивается ошибкой Class not found. Настройте инфоблок так, чтобы Битрикс сгенерировал класс, узнайте его полное имя и проверьте, что он работает: выведите количество активных статей.
Показать решение Скрыть решение
- Админка → Контент → Инфоблоки → «Статьи» → вкладка «Инфоблок».
- Заполните поле «Символьный код API», например
Articles. Только латиница и цифры, с буквы. - Сохраните.
Битрикс создаст класс \Bitrix\Iblock\Elements\ElementArticlesTable. Шаблон имени: Element + API-код + Table.
use Bitrix\Main\Loader;
Loader::includeModule('iblock');
$count = \Bitrix\Iblock\Elements\ElementArticlesTable::getCount(['=ACTIVE' => 'Y']);
echo "Активных статей: $count";
Если код API не задан, класса не будет и вы получите Class not found.
Выбрать элементы с простыми свойствами
Получите 10 последних активных статей с автором.
Показать решение Скрыть решение
Свойство выбирается по коду, а значение лежит в поле VALUE. Для массивов используем алиас:
use Bitrix\Iblock\Elements\ElementArticlesTable;
$articles = ElementArticlesTable::getList([
'select' => [
'ID',
'NAME',
'CODE',
'AUTHOR_NAME' => 'AUTHOR.VALUE', // алиас => путь к значению свойства
],
'filter' => ['=ACTIVE' => 'Y'],
'order' => ['ACTIVE_FROM' => 'DESC'],
'limit' => 10,
])->fetchAll();
foreach ($articles as $article) {
echo $article['NAME'] . ' — ' . $article['AUTHOR_NAME'] . PHP_EOL;
}
Тот же запрос в объектном режиме — с автодополнением и без алиасов:
$articles = ElementArticlesTable::query()
->setSelect(['ID', 'NAME', 'CODE', 'AUTHOR'])
->where('ACTIVE', 'Y')
->setOrder(['ACTIVE_FROM' => 'DESC'])
->setLimit(10)
->fetchCollection();
foreach ($articles as $article) {
$author = $article->getAuthor()?->getValue() ?? 'Без автора';
echo $article->getName() . ' — ' . $author . PHP_EOL;
}
Для каждого свойства генерируется геттер: AUTHOR → getAuthor(). Если у элемента свойство не заполнено, геттер вернёт null — отсюда ?->.
Работать с множественным свойством
Выведите статьи с перечнем их тегов.
Показать решение Скрыть решение
В режиме массивов множественное свойство «размножает» строки: статья с тремя тегами придёт тремя строками. Поэтому для множественных свойств удобнее объекты:
$articles = ElementArticlesTable::query()
->setSelect(['ID', 'NAME', 'TAGS'])
->where('ACTIVE', 'Y')
->fetchCollection();
foreach ($articles as $article) {
$tags = [];
foreach ($article->getTags()->getAll() as $tag) {
$tags[] = $tag->getValue();
}
echo $article->getName() . ': ' . implode(', ', $tags) . PHP_EOL;
}
getTags() у множественного свойства возвращает коллекцию, даже пустую — проверка на null не нужна.
Если limit используется вместе с множественным свойством в режиме массивов, лимит считается по строкам, а не по элементам — легко получить меньше статей, чем ожидали. Объектный режим с fetchCollection() собирает строки в элементы правильно.
Фильтр по свойству и по связанному элементу
Найдите статьи автора «Иван Петров» и статьи, привязанные к разделу сайта с кодом php.
Показать решение Скрыть решение
В фильтре путь к значению указывается так же, как в select:
$byAuthor = ElementArticlesTable::getList([
'select' => ['ID', 'NAME'],
'filter' => [
'=ACTIVE' => 'Y',
'=AUTHOR.VALUE' => 'Иван Петров',
],
])->fetchAll();
Свойство-привязка к элементам имеет поле ELEMENT — сам связанный элемент. Через него можно фильтровать и выбирать поля другого инфоблока:
$phpArticles = ElementArticlesTable::getList([
'select' => [
'ID',
'NAME',
'SECTION_NAME' => 'SECTION_LINK.ELEMENT.NAME',
],
'filter' => [
'=ACTIVE' => 'Y',
'=SECTION_LINK.ELEMENT.CODE' => 'php',
],
])->fetchAll();
Операторы фильтра: = равно, != не равно, >, <, >=, <=, % — содержит (LIKE '%...%'), @ — в списке ('@ID' => [1, 2, 3]).
Пагинация
Выведите список статей по 20 на страницу со стандартной навигацией Битрикса.
Показать решение Скрыть решение
use Bitrix\Main\UI\PageNavigation;
$nav = new PageNavigation('articles');
$nav->allowAllRecords(false)
->setPageSize(20)
->initFromUri();
$result = ElementArticlesTable::getList([
'select' => ['ID', 'NAME', 'CODE'],
'filter' => ['=ACTIVE' => 'Y'],
'order' => ['ID' => 'DESC'],
'count_total' => true,
'offset' => $nav->getOffset(),
'limit' => $nav->getLimit(),
]);
$nav->setRecordCount($result->getCount());
$articles = $result->fetchAll();
// Вывод переключателя страниц
$APPLICATION->IncludeComponent('bitrix:main.pagenavigation', '', [
'NAV_OBJECT' => $nav,
'SEF_MODE' => 'N',
]);
count_total => true добавляет запрос COUNT(*). Без него getCount() выбросит исключение.
Кэшировать тяжёлый запрос
Запрос со связями выполняется на каждой странице. Закэшируйте его на час.
Показать решение Скрыть решение
У getList есть встроенный кэш:
$articles = ElementArticlesTable::getList([
'select' => ['ID', 'NAME', 'SECTION_NAME' => 'SECTION_LINK.ELEMENT.NAME'],
'filter' => ['=ACTIVE' => 'Y'],
'cache' => [
'ttl' => 3600,
'cache_joins' => true, // по умолчанию запросы с JOIN не кэшируются
],
])->fetchAll();
Этот кэш не сбрасывается при изменении элементов — только по ttl. Если данные должны обновляться сразу, оборачивайте выборку в кэш компонента с тегом инфоблока (статья «Свой компонент с кэшированием»).
Добавить и обновить элемент
Добавьте в инфоблок «Статьи» (ID 5) новую активную статью с анонсом, автором и двумя тегами. Затем поменяйте у неё только автора, не затрагивая остальные свойства. Если добавить не удалось, выведите причину ошибки.
После записи должны срабатывать обработчики событий инфоблока, обновляться поиск и сбрасываться кэш — подумайте, через какой API это надёжнее.
Показать решение Скрыть решение
Для записи в инфоблоки надёжнее старый API: он запускает события, пересчитывает поиск, фасетный индекс и сбрасывает тегированный кэш. ORM-сохранение элементов инфоблоков эти механизмы обходит не всегда предсказуемо.
$el = new CIBlockElement();
$id = $el->Add([
'IBLOCK_ID' => 5,
'NAME' => 'Новая статья',
'CODE' => 'new-article',
'ACTIVE' => 'Y',
'PREVIEW_TEXT' => 'Анонс',
'PROPERTY_VALUES' => [
'AUTHOR' => 'Иван Петров',
'TAGS' => ['php', 'bitrix'],
],
]);
if (!$id) {
echo 'Ошибка: ' . $el->LAST_ERROR;
}
// Обновить только одно свойство, не трогая остальные
CIBlockElement::SetPropertyValuesEx($id, 5, ['AUTHOR' => 'Мария Иванова']);
Правило на практике: читаем через D7 ORM, пишем через CIBlockElement.
Итог
- Заполните «Символьный код API» — без него ORM-класса нет.
- Значение свойства —
PROPERTY.VALUE, связанный элемент —PROPERTY.ELEMENT.FIELD. - Для множественных свойств используйте объектный режим и
fetchCollection(). count_total— для пагинации,cacheсcache_joins— для запросов со связями.- Запись — через
CIBlockElement, чтобы сработали события и сброс кэша.