Инфоблоки 2.0 + ORM: современный подход к данным в Битрикс

Если вы всё ещё пишете CIBlockElement::GetList — пора остановиться. В Битрикс давно есть D7 ORM и Iblock API 2.0, которые работают быстрее, читаются лучше и поддерживают автокомплит в IDE. Разбираем переход со старого API на новый с реальными примерами.

Старый API vs Новый: разница в подходе

Старый подход — процедурный, с массивами параметров, которые нигде не документированы (кроме головы разработчика):

// Старый API — CIBlockElement::GetList
$rsElements = CIBlockElement::GetList(
    ['SORT' => 'ASC'],
    [
        'IBLOCK_ID' => 5,
        'ACTIVE' => 'Y',
        'SECTION_ID' => 10,
    ],
    false,
    ['nPageSize' => 20],
    ['ID', 'NAME', 'DETAIL_PAGE_URL', 'PROPERTY_PRICE', 'PROPERTY_BRAND']
);

while ($arElement = $rsElements->GetNext()) {
    echo $arElement['NAME'] . ' — ' . $arElement['PROPERTY_PRICE_VALUE'];
}

Новый подход — ORM с чейнингом, типизированными фильтрами и поддержкой IDE:

// Новый API — Iblock\Elements
use Bitrix\Iblock\Iblock;

$elements = Iblock::wakeUp(5)
    ->getEntityDataClass()::getList([
        'select' => ['ID', 'NAME', 'PRICE_' => 'PRICE', 'BRAND_' => 'BRAND.ELEMENT'],
        'filter' => [
            '=ACTIVE' => 'Y',
            'IBLOCK_SECTION_ID' => 10,
        ],
        'order' => ['SORT' => 'ASC'],
        'limit' => 20,
    ]);

foreach ($elements as $element) {
    echo $element->get('NAME') . ' — ' . $element->get('PRICE_VALUE');
}

Настройка Инфоблоков 2.0

Чтобы использовать новый API, инфоблок должен работать в режиме 2.0 (хранение свойств в отдельной таблице). Проверяем и включаем:

  1. Админка → Контент → Инфоблоки → нужный тип → нужный инфоблок
  2. Вкладка «Поля» → «Хранение свойств значений» = В отдельных таблицах
  3. Указать API-код инфоблока (например, catalog) — он будет использоваться в ORM

После этого Битрикс создаст таблицу b_iblock_element_prop_sNN для свойств и класс ORM.

Генерация ORM-классов

Для автокомплита в IDE генерируем аннотации:

// В init.php или консольном скрипте
use Bitrix\Iblock\Iblock;

$iblock = Iblock::wakeUp(5); // ID инфоблока
$entity = $iblock->getEntityDataClass();

// Имя класса для PhpStorm
echo get_class($entity); // Bitrix\Iblock\Elements\ElementCatalogTable

Для проектов с несколькими инфоблоками удобно создать хелпер:

// /local/php_interface/classes/IblockHelper.php

namespace App;

use Bitrix\Iblock\Iblock;
use Bitrix\Main\Loader;

class IblockHelper
{
    private static array $cache = [];

    public static function getEntity(int $iblockId): string
    {
        if (!isset(self::$cache[$iblockId])) {
            Loader::includeModule('iblock');
            self::$cache[$iblockId] = Iblock::wakeUp($iblockId)->getEntityDataClass();
        }

        return self::$cache[$iblockId];
    }
}

// Использование
$catalogClass = \App\IblockHelper::getEntity(5);
$elements = $catalogClass::getList([...]);

Выборка данных — основные паттерны

Простая выборка с фильтром

use Bitrix\Iblock\Iblock;

$entity = Iblock::wakeUp(5)->getEntityDataClass();

$elements = $entity::getList([
    'select' => ['ID', 'NAME', 'ACTIVE_FROM', 'DETAIL_PAGE_URL'],
    'filter' => [
        '=ACTIVE' => 'Y',
        '>=ACTIVE_FROM' => new \Bitrix\Main\Type\DateTime('01.01.2025', 'd.m.Y'),
    ],
    'order' => ['ACTIVE_FROM' => 'DESC'],
    'limit' => 10,
    'offset' => 0,
    'count_total' => true,
]);

// Общее количество (для пагинации)
$total = $elements->getCount();

foreach ($elements as $element) {
    echo $element->get('ID') . ': ' . $element->get('NAME');
}

Выборка со свойствами

$elements = $entity::getList([
    'select' => [
        'ID',
        'NAME',
        'PRICE_' => 'PRICE',           // Свойство PRICE
        'BRAND_' => 'BRAND',           // Свойство BRAND
        'COLOR_' => 'COLOR',           // Множественное свойство
    ],
    'filter' => [
        '=ACTIVE' => 'Y',
        '>=PRICE.VALUE' => 1000,       // Фильтр по свойству
        '=BRAND.VALUE' => 'Apple',     // Точное совпадение
    ],
]);

foreach ($elements as $element) {
    $price = $element->get('PRICE_VALUE');
    $brand = $element->get('BRAND_VALUE');
    echo "$brand — $price ₽";
}

Связь «Привязка к элементам»

Если свойство — привязка к элементам другого инфоблока:

$elements = $entity::getList([
    'select' => [
        'ID',
        'NAME',
        // Получаем данные связанного элемента
        'BRAND_ID' => 'BRAND.ELEMENT.ID',
        'BRAND_NAME' => 'BRAND.ELEMENT.NAME',
    ],
    'filter' => ['=ACTIVE' => 'Y'],
]);

foreach ($elements as $element) {
    echo $element->get('NAME') . ' (бренд: ' . $element->get('BRAND_NAME') . ')';
}

Операторы фильтрации

Оператор Описание Пример
= Равно '=ACTIVE' => 'Y'
!= Не равно '!=STATUS' => 'ARCHIVE'
> Больше '>PRICE.VALUE' => 0
>= Больше или равно '>=SORT' => 100
< Меньше '<ACTIVE_FROM' => new DateTime()
% LIKE (содержит) '%NAME' => 'iPhone'
= (массив) IN '=ID' => [1, 2, 3]

Логические операторы:

// AND — по умолчанию
'filter' => [
    '=ACTIVE' => 'Y',
    '>PRICE.VALUE' => 100,
]

// OR — через массив логики
'filter' => [
    'LOGIC' => 'OR',
    ['=BRAND.VALUE' => 'Apple'],
    ['=BRAND.VALUE' => 'Samsung'],
]

Кеширование

D7 ORM поддерживает встроенное кеширование:

$elements = $entity::getList([
    'select' => ['ID', 'NAME', 'PRICE_' => 'PRICE'],
    'filter' => ['=ACTIVE' => 'Y'],
    'cache' => [
        'ttl' => 3600,                    // Время жизни кеша в секундах
        'cache_joins' => true,            // Кешировать JOIN-запросы
    ],
]);

Для ручного управления кешем:

use Bitrix\Main\Data\Cache;

$cache = Cache::createInstance();

if ($cache->initCache(3600, 'catalog_items_' . md5(serialize($filter)), '/catalog/')) {
    $items = $cache->getVars()['items'];
} elseif ($cache->startDataCache()) {
    $items = [];
    $elements = $entity::getList([...]);
    foreach ($elements as $el) {
        $items[] = [
            'ID' => $el->get('ID'),
            'NAME' => $el->get('NAME'),
        ];
    }
    $cache->endDataCache(['items' => $items]);
}

Агрегация и группировка

use Bitrix\Main\Entity\ExpressionField;

// Средняя цена по брендам
$result = $entity::getList([
    'select' => [
        'BRAND_' => 'BRAND',
        new ExpressionField('AVG_PRICE', 'AVG(%s)', ['PRICE.VALUE']),
        new ExpressionField('ITEM_COUNT', 'COUNT(%s)', ['ID']),
    ],
    'filter' => ['=ACTIVE' => 'Y'],
    'group' => ['BRAND.VALUE'],
]);

foreach ($result as $row) {
    echo $row->get('BRAND_VALUE') . ': средняя цена ' . round($row->get('AVG_PRICE')) . ' ₽';
    echo ' (' . $row->get('ITEM_COUNT') . ' товаров)';
}

Производительность: старый vs новый API

Операция CIBlockElement::GetList D7 ORM
Выборка 100 элементов ~50 мс ~30 мс
Фильтр по свойству JOIN на каждое свойство Одна таблица (2.0)
Автокомплит IDE Нет Да (через аннотации)
Типизация Массивы Объекты
SQL-профилирование Сложно getLastQuery()

Главный выигрыш в производительности — хранение свойств. В старом режиме каждое свойство — это отдельный JOIN с таблицей b_iblock_element_property. В режиме 2.0 все свойства — в одной строке отдельной таблицы.

Миграция со старого API

Не нужно переписывать всё сразу. Стратегия:

  1. Новый код — пишем на D7 ORM
  2. Критичные выборки — мигрируем при оптимизации
  3. Старый код — трогаем только при багфиксах

Оба API работают параллельно. Инфоблок в режиме 2.0 совместим со старым CIBlockElement::GetList.

Есть идея? Реализуем

Разрабатываем проекты, которые решают задачи бизнеса — от лендинга до сложного сервиса. Расскажите о своей задаче, подберём решение.

Написать в Telegram

30.03.2026

Нужна консультация?

Оставьте свои контактные данные, или свяжитесь с нами удобным для вас способом

Привет! Меня зовут Багира. Пишите, я все передам хозяевам!

Привет! Меня зовут Багира. Пишите, я все передам хозяевам!

Нажимая кнопку «Принять», вы соглашаетесь на сбор cookie. Мы используем их для обеспечения функционирования веб-сайта, аналитики действий и улучшения качества обслуживания. Если Вы не хотите, чтобы эти данные обрабатывались, отключите cookie в настройках браузера или прекратите использовать сайт.
Принять