Как использовать fetchObject и fetchCollection после getList в D7 ORM?

Вы уже переписали каталог на ElementCatalogTable::getList(), но в цикле крутите массивы $row['NAME'] и правки не доходят до витрины? Типичная ошибка на практике: setName() без save() — в БД ничего не меняется, или путаница fetch() с объектным слоем. В D7 ORM есть fetchObject() и fetchCollection() - типизированные объекты с геттерами и единым save(). Здесь разберём, когда хватит fetch() для read-only, а когда нужны объекты, createObject и wakeUpObject - и проверите цепочку на staging за один проход.

getList() возвращает DB\Result: fetch() и fetchAll() дают ассоциативные массивы, fetchObject() и fetchCollection() - EntityObject и Collection с set*() и save(). После любого set* обязателен save() и проверка Result::isSuccess(). Для новой записи - createObject(), для данных из кеша или импорта - wakeUpObject() без лишнего SELECT. Коллекцию объектов нельзя класть в ORM cache - кешируется Result выборки, не EO_Collection.

Речь про коробочную CMS 1С-Битрикс: Управление сайтом, не про CRM-портал Bitrix24. ORM (Object-Relational Mapping) - слой D7, который связывает PHP-класс *Table с таблицей MySQL. Базовый синтаксис getList (select, filter, order) разобрали в сравнении GetList и ElementTable (B60). Свою таблицу и DataManager - в гайде по своей таблице (B71). Здесь - второй уровень: objectify-слой после getList, когда массивов уже мало.

Выберите, когда нужен objectify-слой, а когда хватит fetch()

Сравнительная таблица fetch, fetchObject и fetchCollection в D7 ORM Битрикс

getList() всегда возвращает Result. Дальше выбор простой: читаете данные без правок - fetch() или fetchAll(); меняете поля, связи или пакетно обновляете список - fetchObject() и fetchCollection().

Метод Что получаете Когда использовать
fetch() / fetchAll() Ассоциативный массив полей Отчёты, выгрузки, read-only шаблоны без save()
fetchObject() EntityObject с get*() / set*() Одна запись: правка полей, delete(), collectValues()
fetchCollection() Collection объектов Цикл по списку, fill(), групповой save(), walk() с main 26.0.0+
getByPrimary()->fetchObject() Объект по ID Карточка элемента, когда ID уже известен

Делайте: для витрины без записи в БД оставляйте fetch(). Не делайте: не присваивайте $row['NAME'] = 'Новое' в массиве - ORM об этом не узнает, пока не вызовете Table::update() или object->save().

Настройте fetchObject: select, set* и save() с проверкой Result

Чеклист настройки fetchObject: select, set и save с проверкой Result в D7 ORM

Минимальный рабочий паттерн для одной записи:

use Bitrix\Iblock\Elements\ElementCatalogTable;

$element = ElementCatalogTable::getByPrimary(42)->fetchObject();
if ($element) {
    $element->setName('Новое название');
    $result = $element->save();
    if (!$result->isSuccess()) {
        // $result->getErrorMessages()
    }
}

Геттер getName() вернёт null, если поле NAME не попало в select при getList. Перед save() полезно сверить remindActualName() - до записи в БД оно показывает старое значение из базы, после save() - обновлённое. Если set* установил то же, что уже в БД, UPDATE может не сформироваться - это оптимизация ORM, не ошибка.

  1. Убедитесь, что в select есть все поля, которые читаете через get*().
  2. Вызовите set*() для нужных полей.
  3. Сохраните через $result = $object->save().
  4. Проверьте $result->isSuccess() и getErrorMessages() при сбое.

Автогенерируемые классы EO_* не проверяйте через instanceof EO_Book - после регенерации имена сменятся. Кастомизируйте через getObjectClass() в *Table и сравнивайте с Table::getObjectClass().

Делайте: оборачивайте save() в проверку Result, как при add(). Не делайте: не полагайтесь на set* без save() - изменения останутся только в памяти PHP.

Соберите fetchCollection: foreach, fill() и групповой save()

Схема workflow fetchCollection: getList, fill и групповой save в D7 ORM

Цепочка от primary-запроса "getlist битрикс d7" к objectify:

Table::getList([...]) → fetchCollection() → foreach / walk() → set*() → collection->save()
$collection = ElementCatalogTable::getList([
    'select' => ['ID', 'NAME', 'ACTIVE'],
    'filter' => ['=ACTIVE' => 'Y'],
    'limit'  => 50,
])->fetchCollection();

foreach ($collection as $item) {
    $item->setActive('N');
}
$result = $collection->save();

fetchAll() вернул бы массив строк без set* и save(). getIdList() и getNameList() на коллекции удобны для логов. Дозагрузка полей - один вызов $collection->fill(['PRICE', 'DETAIL_TEXT']), не fill() в цикле по каждому объекту: иначе N+1 SELECT вместо одного WHERE ID IN (...).

С main 26.0.0+ доступен Collection::walk() - цепочка query()->fetchCollection()->walk(...)->save() без ручного foreach. JOIN-выборки с Reference - отдельная тема в гайде по связям D7 ORM (B79); здесь важно: fetchCollection() работает и с обычным getList без runtime.

Групповой save() эффективен, когда у всех элементов меняются одни и те же поля. Если правки уникальны для каждой записи, ORM сформирует отдельные UPDATE. Для массового INSERT без событий - $collection->save(true) с $ignoreEvents=true.

Делайте: групповой fill() на коллекции. Не делайте: не кладите fetchCollection() в CPHPCache или ORM cache - подробнее в гайде по ORM cache (B85).

Подключите связанные объекты в коллекции без N+1 в цикле

Когда в map *Table настроен Reference, у объекта есть get*Collection, addTo*Collection и removeFrom*Collection. В реальном проекте часто ломается так: в foreach снова вызывают getList() на связанной таблице. Правильнее fetchCollection() → fill() на связи или runtime в том же getList - JOIN в B79.

Workflow: getList → fetchCollection() → fill при необходимости → addTo* / removeFrom* → save(). Так нет N+1 на проде.

Делайте: планируйте select и fill до цикла foreach. Не делайте: не вызывайте отдельный getList на каждую итерацию, если связь уже описана в Reference.

Создайте запись через createObject и восстановите wakeUpObject без SELECT

Новая строка без массива fields в add():

$book = BookTable::createObject();
$book->setTitle('Новая книга');
$book->setAuthorId(5);
$result = $book->save();

createObject(false) создаёт объект без дефолтов полей сущности; createObject() - с дефолтами из map. Для уже известных ID или массива полей после импорта:

$book = BookTable::wakeUpObject(17);
// или Book::wakeUp(['ID' => 17, 'TITLE' => 'Из кеша']);
$book->setTitle('Обновлённый заголовок');
$book->save();

wakeUpObject не делает SELECT - объект "оживлён" в памяти для дальнейшего set/save. События OnBeforeAdd и OnAfterAdd при save() нового объекта срабатывают так же, как при Table::add() - детали в гайде по событиям D7 ORM (B80).

Для PATCH-логики и API-сериализации:

use Bitrix\Main\ORM\Objectify\Values;
use Bitrix\Main\ORM\Fields\FieldTypeMask;

$changed = $book->collectValues(
    Values::CURRENT,
    FieldTypeMask::SCALAR
);

Удаление - $object->delete() с тем же Result. isChanged() и hasModified() покажут несохранённые правки. Если set*() совпал с БД, save() может вернуть успех без UPDATE - сверяйте remindActual*().

Делайте: wakeUp после кеша импорта вместо повторного getByPrimary. Не делайте: не путайте createObject (новая запись) с wakeUp (существующие данные в памяти).

Пройдите чек-лист staging перед выкладкой на prod

  1. Проверьте select: все поля для get*() и set*() на месте.
  2. Измените одну запись через fetchObject → set* → save() и сверьте админку.
  3. Прогоните fetchCollection с fill() - в SqlTracker один IN-запрос, не N штук.
  4. Убедитесь, что EO_Collection не лежит в ORM cache (B85).

Нужна помощь с миграцией legacy GetList на objectify - обсудим задачу, примеры внедрений - в портфолио.

Автор: Максим Мольков, Senior-разработчик 1С-Битрикс.
Источники: объекты ORM Bitrix Framework, коллекции ORM, выборка данных getList, урок D7: wakeUpObject.

Частые вопросы

Чем fetchObject отличается от fetch?

fetch() возвращает массив полей из Result. fetchObject() возвращает EntityObject с типизированными get*() и set*(), после которых нужен save(). Для read-only отчётов достаточно fetch(); для правок в БД берите objectify-слой.

Когда использовать fetchCollection вместо fetchAll?

fetchAll() даёт массив строк без save() на элементах. fetchCollection() даёт Collection с foreach, fill(), getIdList() и групповым save(). Выбирайте коллекцию, если планируете менять поля у нескольких записей одной цепочкой.

Как создать новый объект без Table::add(['fields' => ...])?

Вызовите Table::createObject(), заполните поля через set*() и сохраните $object->save(). Проверьте Result::isSuccess(). Это эквивалент add(), но с objectify-API и теми же событиями OnBeforeAdd.

Почему нельзя кешировать EO_Collection?

ORM cache в getList сохраняет Result выборки как набор строк, не живые объекты с методами. Коллекция из fetchCollection() держит состояние в памяти PHP; при кешировании данные залипают. Кешируйте Result или массив после fetchAll(), см. B85.

Чем save() на объекте отличается от Table::update с массивом?

Оба пишут в БД через DataManager. save() на EntityObject учитывает только изменённые поля (CURRENT vs ACTUAL), возвращает Result и запускает те же события. update() с массивом удобен без objectify; save() - когда уже работаете с fetchObject или createObject.

Зачем wakeUpObject, если есть getByPrimary?

wakeUpObject восстанавливает объект по ID или массиву полей без SELECT - после импорта, кеша или когда ID уже есть в памяти. getByPrimary всегда ходит в базу. Для экономии запросов после массовой выгрузки используйте wakeUp.

Обсудить вашу задачу

Было полезно?

Поставьте лайк, если статья помогла разобраться.

Поделиться статьёй

Отправьте тому, кому это пригодится.

Telegram ↗ MAX ↗ ВКонтакте ↗ Одноклассники ↗
Все статьи ↗

Как настроить регистрацию и личный кабинет покупателя на 1С-Битрикс?

Структура шаблонов сайта в Битриксе и поиск header footer

Как настроить скидки и промокоды на 1С-Битрикс: правила корзины и купоны