#инфоблоки

Советы с тегом "инфоблоки"

3 совета

walk() для ORM-коллекций: практичные массовые операции с инфоблоками

walk() для ORM-коллекций: практичные массовые операции с инфоблоками

Проблема/контекст

В D7 вы часто получаете из ORM не массив, а типизированную коллекцию объектов через fetchCollection(). Перебирать её через foreach нормально, но в реальном коде обычно хочется:

  • компактно “пройтись и изменить” объекты;
  • сохранить изменения одной цепочкой вызовов;
  • не плодить лишние временные переменные.

Начиная с main 26.0.0 у Bitrix\Main\ORM\Objectify\Collection появился метод walk(): это “аналог foreach, но как метод”, который удобно вставляется в цепочку.

Решение с кодом

walk() вызывает ваш callback для каждого объекта коллекции и возвращает эту же коллекцию (для чейнинга). В callback приходят:

  1. объект сущности,
  2. ключ итерации — это ключ внутреннего массива коллекции (обычно сериализованный первичный ключ; для составного — строка).

Пример 1. Массово деактивировать элементы инфоблока и сохранить одной цепочкой

Частый кейс: найти элементы по условию, поставить ACTIVE = 'N' и сохранить максимально “чисто”.

        <?php
declare(strict_types=1);

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

function deactivateSectionElements(int $iblockId, int $sectionId): void
{
	Loader::includeModule('iblock');

	$saveResult = ElementTable::query()
		->setSelect(['ID', 'ACTIVE', 'IBLOCK_ID', 'IBLOCK_SECTION_ID'])
		->where('IBLOCK_ID', $iblockId)
		->where('IBLOCK_SECTION_ID', $sectionId)
		->where('ACTIVE', 'Y')
		->fetchCollection()
		->walk(static function($element): void {
			$element->setActive('N');
		})
		->save()
	;

	if (!$saveResult->isSuccess())
	{
		throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
	}
}

    

Практические детали:

  • При одинаковом изменении для всех объектов save() умеет выполнять групповой UPDATE (однотипные правки). Это как раз тот случай, когда walk() хорошо “стыкуется” с коллекциями.
  • Если вы меняете разные значения для каждого объекта (например, SORT по формуле), ORM может уйти в серию UPDATE по одному объекту.

Пример 2. “Пройтись и собрать” данные без лишнего foreach

walk() удобен не только для мутаций, но и для побочных эффектов: собрать ID, составить карту, подсчитать суммы.

        <?php
declare(strict_types=1);

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

function collectElementIdsAndNames(int $iblockId, int $limit = 50): array
{
	Loader::includeModule('iblock');

	$ids = [];
	$namesById = [];

	ElementTable::query()
		->setSelect(['ID', 'NAME'])
		->where('IBLOCK_ID', $iblockId)
		->setLimit($limit)
		->fetchCollection()
		->walk(static function($element, $key) use (&$ids, &$namesById): void {
			$id = (int)$element->getId();
			$ids[] = $id;
			$namesById[$id] = (string)$element->getName();
		})
	;

	return [
		'ids' => $ids,
		'namesById' => $namesById,
	];
}

    

Пример 3. Когда walk() лучше, чем filter()

У коллекции есть “функциональный” filter(), но он не сработает, если в коллекции есть несохранённые изменения — тогда выбрасывается CollectionFilterException.

Поэтому практическое правило такое:

  • хотите “сначала отобрать, потом поменять” — делайте filter() первым шагом;
  • хотите просто “пройтись и поменять” — берите walk().
        <?php
declare(strict_types=1);

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

function deactivateActiveElementsInIblock(int $iblockId): void
{
	Loader::includeModule('iblock');

	$saveResult = ElementTable::query()
		->setSelect(['ID', 'ACTIVE'])
		->where('IBLOCK_ID', $iblockId)
		->fetchCollection()
		->filter(static fn($element) => $element->getActive() == 'Y')
		->walk(static function($element): void {
			$element->setActive('N');
		})
		->save();

	if (!$saveResult->isSuccess())
	{
		throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
	}
}

    

Ограничения, о которых полезно помнить

  • walk() не даёт “прервать” обход по условию. Если нужен ранний выход — используйте foreach или find() у коллекции.
  • В callback вторым аргументом приходит ключ итерации. Он удобен для отладки/логики по PK, но не стоит на него “завязывать” бизнес-логику (особенно при составных ключах).

Итог

walk() — маленький метод, но в D7 он хорошо раскрывается именно в связке fetchCollection() -> walk() -> save(): получается компактный, читабельный и часто более производительный массовый апдейт.

Получение DETAIL_PAGE_URL для элемента инфоблока в D7

Получение DETAIL_PAGE_URL для элемента инфоблока в D7

Проблема/контекст

В новом ядре D7 (таблица Bitrix\Iblock\ElementTable) нет готового поля DETAIL_PAGE_URL. Это связано с тем, что ссылка на детальную страницу — это динамический шаблон (например, /catalog/#SECTION_CODE#/#ELEMENT_CODE#/), который хранится в настройках инфоблока и требует подстановки реальных данных элемента для вычисления.

Решение с кодом

Чтобы получить корректную ссылку, необходимо выбрать данные элемента вместе с шаблоном URL из связанной таблицы инфоблока. Рекомендуется использовать Element API (если у инфоблока задан API_CODE), но способ также работает и с базовым ElementTable.

        <?php
declare(strict_types=1);

use Bitrix\Iblock\Elements\ElementCatalogTable; // Где 'Catalog' — это API_CODE вашего инфоблока
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

$elementId = 123;

// Если у инфоблока нет API_CODE, используйте \Bitrix\Iblock\ElementTable
$element = ElementCatalogTable::getList([
    'select' => [
        'ID',
        'CODE',
        'IBLOCK_SECTION_ID',
        // Получаем шаблон URL из настроек инфоблока через связь IBLOCK
        'DETAIL_PAGE_URL_TEMPLATE' => 'IBLOCK.DETAIL_PAGE_URL'
    ],
    'filter' => ['=ID' => $elementId]
])->fetch();

if ($element)
{
    // Метод ReplaceDetailUrl заменит плейсхолдеры типа #ID#, #CODE# на реальные значения из массива $element
    $detailUrl = \CIBlock::ReplaceDetailUrl(
        url: $element['DETAIL_PAGE_URL_TEMPLATE'],
        arr: $element,
        server_name: true,
        arrType: 'E' // Тип "E" означает Element
    );

    echo $detailUrl;
}

    

Пример с использованием fetchObject()

Если вы предпочитаете работать с объектами (EO_Element), данные для замены масок нужно подготовить через метод collectValues():

        $elementObject = ElementCatalogTable::getList([
    'select' => ['ID', 'CODE', 'IBLOCK_SECTION_ID', 'IBLOCK.DETAIL_PAGE_URL'],
    'filter' => ['=ID' => $elementId]
])->fetchObject();

if ($elementObject)
{
    // ReplaceDetailUrl ожидает массив, поэтому используем collectValues()
    $detailUrl = \CIBlock::ReplaceDetailUrl(
        url: $elementObject->getIblock()->getDetailPageUrl(),
        arr: $elementObject->collectValues(),
        server_name: true,
        arrType: 'E'
    );
}

    

Параметры метода ReplaceDetailUrl

  1. $url (string) — Шаблон URL из настроек инфоблока (например, /catalog/#SECTION_CODE#/#ELEMENT_CODE#/).
  2. $arr (array) — Массив данных элемента. Ключи должны совпадать с масками (ID для #ID#, CODE для #CODE# и т.д.).
  3. $server_name (bool) — Если true, метод подставит SITE_DIR и домен сайта. Полезно для генерации полных ссылок (в письмах или RSS).
  4. $arrType (string) — Тип сущности: "E" для элементов, "S" для разделов. Это определяет, какие маски будут обрабатываться (например, #SECTION_CODE_PATH#).

Практические детали:

  • Обязательные поля: В select запроса getList нужно обязательно включать все поля, которые используются в шаблоне ссылки (обычно это ID, CODE, IBLOCK_SECTION_ID). Если в шаблоне есть #SECTION_CODE#, вам придется добавить в select связь с секцией, например 'SECTION_CODE' => 'IBLOCK_SECTION.CODE'.
  • Element API vs ElementTable: Использование ElementCatalogTable предпочтительнее для новых проектов, так как это дает типизацию и упрощенную работу со свойствами. Но если вы пишете универсальный код или API_CODE не задан, \Bitrix\Iblock\ElementTable работает по тому же принципу.
  • Тип сущности: При вызове ReplaceDetailUrl последний параметр 'E' указывает, что мы работаем с элементом. Для разделов используется 'S'.

Итог

Для получения ссылки в D7 используйте выборку шаблона через связь IBLOCK.DETAIL_PAGE_URL и стандартный метод \CIBlock::ReplaceDetailUrl для подстановки данных. Это наиболее производительный и правильный способ в рамках Bitrix Framework.

Избавляемся от ID инфоблока при работе с разделами

Избавляемся от ID инфоблока при работе с разделами

Проблема

В типичном проекте на 1С-Битрикс код работы с разделами напрямую взаимодействует с ID инфоблока. При каждом запросе к SectionTable или CIBlockSection приходится явно указывать фильтр по инфоблоку. Если нужно изменить ID инфоблока или перенести код на другой проект — начинается ад с поиском и заменой всех вхождений.

Решение

Класс Bitrix\Iblock\Model\Section предоставляет метод compileEntityByIblock(), который создаёт специализированный класс для работы с разделами конкретного инфоблока. Все запросы через этот класс автоматически фильтруются по нужному IBLOCK_ID без явного указания.

Было: магические числа везде

        use Bitrix\Iblock\SectionTable;

// Приходится каждый раз указывать IBLOCK_ID
$sections = SectionTable::getList([
    'select' => ['ID', 'NAME', 'CODE'],
    'filter' => [
        'IBLOCK_ID' => 10, // Что это за инфоблок?
        'ACTIVE' => 'Y'
    ]
])->fetchAll();

// И снова тот же ID в другом месте
$section = SectionTable::getByPrimary(25, [
    'filter' => ['IBLOCK_ID' => 10] // Дублирование!
])->fetch();

    

Стало: чистый код без дублирования

        use Bitrix\Iblock\Model\Section;

// Один раз создаём класс для инфоблока по API_CODE
$catalogSectionEntity = Section::compileEntityByIblock('catalog');

// IBLOCK_ID больше не нужен - всё фильтруется автоматически
$sections = $catalogSectionEntity::getList([
    'select' => ['ID', 'NAME', 'CODE'],
    'filter' => ['ACTIVE' => 'Y']
])->fetchAll();


    

Бонус: автоматическая связь с родителем

Помимо избавления от IBLOCK_ID, получаем ещё и связь PARENT_SECTION для работы с иерархией:

        $catalogSectionEntity = Section::compileEntityByIblock('catalog');

// Получаем раздел с данными родителя одним запросом
$section = $catalogSectionEntity::getList([
    'filter' => ['=CODE' => 'laptops'],
    'select' => ['ID', 'NAME', 'PARENT_SECTION.NAME']
])->fetchObject();

echo $section->getName(); // "Ноутбуки"
echo $section->getParentSection()?->getName(); // "Компьютеры"

    

Практический пример: хлебные крошки без IBLOCK_ID

        use Bitrix\Iblock\Model\Section;

// Инициализируем класс для каталога
$catalogSectionEntity = Section::compileEntityByIblock('catalog');

$section = $catalogSectionEntity::query()
    ->setSelect([
        'ID', 
        'NAME',
        'CODE',
        'PARENT_SECTION.NAME',
        'PARENT_SECTION.PARENT_SECTION.NAME'
    ])
    ->where('CODE', 'gaming-laptops')
    ->exec()
    ->fetchObject();

// Строим хлебные крошки рекурсивно
$breadcrumbs = [];
$current = $section;

while ($current) {
    $breadcrumbs[] = [
        'name' => $current->getName(),
        'code' => $current->getCode()
    ];
    $current = $current->getParentSection();
}

$breadcrumbs = array_reverse($breadcrumbs);

    

Что получаем

  1. Нет магических чисел: IBLOCK_ID указан один раз при создании класса
  2. Переносимость: изменили ID инфоблока — код работает без изменений
  3. Читаемость: не нужно помнить, что 10 — это каталог, а 15 — новости
  4. Меньше ошибок: невозможно забыть добавить фильтр по IBLOCK_ID
  5. Связь с родителем: автоматическая PARENT_SECTION для иерархии
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.

AI Домовой История

0 / 100

Привет! Я помогу с вопросами по 1С-Битрикс.

Спрашивай про D7, ORM, компоненты или события.

Требуется авторизация

Войдите или зарегистрируйтесь, чтобы задавать вопросы AI-ассистенту.

Войти