К содержимому

1С-Битрикс: работа с инфоблоками через D7 ORM

Старый API (CIBlockElement::GetList) работает до сих пор, но у него неудобная выборка свойств, массивы «магических» ключей и нет нормальных связей. D7 ORM даёт классы-таблицы с автодополнением в IDE, объекты и связи через точку.

Главная особенность: для элементов инфоблоков ORM-класс генерируется отдельно для каждого инфоблока. Чтобы он появился, у инфоблока должен быть заполнен символьный код API.

В примерах — инфоблок «Статьи» со свойствами:

Свойство Код Тип
Автор AUTHOR Строка
Теги TAGS Строка, множественное
Раздел сайта SECTION_LINK Привязка к элементам другого инфоблока
Задача 1 Средняя

Получить ORM-класс инфоблока

Для инфоблока «Статьи» пока нет ORM-класса: обращение к нему заканчивается ошибкой Class not found. Настройте инфоблок так, чтобы Битрикс сгенерировал класс, узнайте его полное имя и проверьте, что он работает: выведите количество активных статей.

Показать решение Скрыть решение
  1. Админка → Контент → Инфоблоки → «Статьи» → вкладка «Инфоблок».
  2. Заполните поле «Символьный код API», например Articles. Только латиница и цифры, с буквы.
  3. Сохраните.

Битрикс создаст класс \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.

Задача 2 Средняя

Выбрать элементы с простыми свойствами

Получите 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 — отсюда ?->.

Задача 3 Средняя

Работать с множественным свойством

Выведите статьи с перечнем их тегов.

Показать решение Скрыть решение

В режиме массивов множественное свойство «размножает» строки: статья с тремя тегами придёт тремя строками. Поэтому для множественных свойств удобнее объекты:

Множественное свойство
$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() собирает строки в элементы правильно.

Задача 4 Средняя

Фильтр по свойству и по связанному элементу

Найдите статьи автора «Иван Петров» и статьи, привязанные к разделу сайта с кодом 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]).

Задача 5 Средняя

Пагинация

Выведите список статей по 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() выбросит исключение.

Задача 6 Средняя

Кэшировать тяжёлый запрос

Запрос со связями выполняется на каждой странице. Закэшируйте его на час.

Показать решение Скрыть решение

У 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. Если данные должны обновляться сразу, оборачивайте выборку в кэш компонента с тегом инфоблока (статья «Свой компонент с кэшированием»).

Задача 7 Средняя

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

Добавьте в инфоблок «Статьи» (ID 5) новую активную статью с анонсом, автором и двумя тегами. Затем поменяйте у неё только автора, не затрагивая остальные свойства. Если добавить не удалось, выведите причину ошибки.

После записи должны срабатывать обработчики событий инфоблока, обновляться поиск и сбрасываться кэш — подумайте, через какой API это надёжнее.

Показать решение Скрыть решение

Для записи в инфоблоки надёжнее старый API: он запускает события, пересчитывает поиск, фасетный индекс и сбрасывает тегированный кэш. ORM-сохранение элементов инфоблоков эти механизмы обходит не всегда предсказуемо.

Добавление через CIBlockElement
$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, чтобы сработали события и сброс кэша.