18.06.2026 15 мин чтения

Программное копирование инфоблока через Bitrix\Iblock\Copy\Manager

Кирилл Новожилов

Кирилл Новожилов

Автор

Программное копирование инфоблока через Bitrix\Iblock\Copy\Manager
Введение
Зачем это нужно

В коде механизм копирования инфоблока целиком нужен, когда:

  • разворачиваете шаблонный каталог/контент на новый сайт или стенд;
  • клонируете универсальный список в группу соцсети (модуль lists так и делает);
  • мигрируете IBLOCK между типами (newscontent) без ручного экспорта.

Точка входа — Bitrix\Iblock\Copy\Manager. Под капотом — фреймворк Bitrix\Main\Copy\EntityCopier и цепочка implementer-ов.

Архитектура копирования

        Manager::startCopy()
    └── EntityCopier(IblockImplementer)
            ├── add()           — CIBlock::Add (новый инфоблок)
            ├── copyChildren()
            │     ├── field     → FieldImplementer (поля + свойства + enum)
            │     ├── section   → SectionImplementer (дерево разделов)
            │     ├── element   → ElementImplementer → очередь + IblockStepper (агент)
            │     └── workflow  → WorkflowImplementer (шаблоны Bizproc, если модуль подключён)
            └── getMapIdsCopiedEntity() → [старый ID => новый ID]

    
⚠️ Важно
startCopy() синхронно создаёт инфоблок, свойства и разделы. Элементы ставятся в очередь и копируются агентом Bitrix\Iblock\Copy\Stepper\Iblock порциями по 3-5 штук за один проход.

Порядок children жёсткий: сначала field, затем section, затем element — enum/section/property mapping передаётся в copier элементов.

Шаг 0. Подготовка окружения

  1. Агенты и копирование элементов. После startCopy() элементы не копируются в том же HTTP-запросе — их подхватывает агент Bitrix\Iblock\Copy\Stepper\Iblock.
  2. На время копирования больших инфоблоков увеличивать лимиты PHP-FPM не обязательно: stepper сам дробит работу.
  3. Создайте целевой тип инфоблока заранее, если копируете в другой тип.
⚠️ Важно
Без cron элементы всё равно копируются — по 3-5 штук за проход агента, когда кто-то открывает страницу сайта (или когда вы сами дергаете URL в браузере). На малопосещаемом сайте это медленно; на production с трафиком — лишняя нагрузка на хиты. Cron рекомендуется для больших инфоблоков и боевых контуров: перенесите агентов на cron_events.php, чтобы не ждать посетителей и не грузить публичные запросы.

Как проверить:

В админке: «Настройки → Инструменты → Агенты» — ищите Bitrix\Iblock\Copy\Stepper\Iblock::execAgent();. Статус очереди — опция IblockGroupChecker_{newIblockId}.

Шаг 1. Минимальное копирование «как есть»

Скрипт для CLI или одноразового запуска через браузер

⚠️ Внимание
Не оставляйте публичный URL на production!

Важно перед кодом:

  1. STDERR доступен только в CLI; для веб-запуска нужен fallback на echo.
  2. Ядро сбрасывает XML_ID, но не API_CODE. При копировании инфоблока с заполненным API-кодом получите ошибку «Символьный код API должен быть уникальным» — нужен кастомный implementer (см. ниже).

Пример скрипта запуска:

        <?php
declare(strict_types=1);

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

use Bitrix\Iblock\Copy\Implement\Iblock as IblockCopyImplementer;
use Bitrix\Iblock\Copy\Manager;
use Bitrix\Iblock\IblockTable;
use Bitrix\Main\Copy\Container;
use Bitrix\Main\Loader;

$writeError = static function (string $message): void {
    if (defined('STDERR')) {
        fwrite(STDERR, $message . "\n");
        return;
    }

    echo htmlspecialcharsbx($message) . "<br>\n";
};

if (!Loader::includeModule('iblock')) {
    $writeError('Module iblock not loaded');
    exit(1);
}

final class UniqueApiCodeIblockCopy extends IblockCopyImplementer
{
    public function prepareFieldsToCopy(Container $container, array $fields): array
    {
        $fields = parent::prepareFieldsToCopy($container, $fields);

        if (!empty($fields['API_CODE'])) {
            $fields['API_CODE'] = $this->makeUniqueApiCode((string)$fields['API_CODE']);
        }

        return $fields;
    }

    private function makeUniqueApiCode(string $base): string
    {
        $base = preg_replace('/[^a-z0-9]/i', '', $base) ?: 'Iblock';
        $base = lcfirst(substr($base, 0, 40));

        for ($suffix = 0; $suffix < 1000; $suffix++) {
            $candidate = $suffix === 0 ? $base . 'Copy' : $base . 'Copy' . $suffix;
            if (!preg_match('/^[a-z][a-z0-9]{0,49}$/i', $candidate)) {
                continue;
            }
            if (!$this->apiCodeExists($candidate)) {
                return $candidate;
            }
        }

        return 'IblockCopy' . random_int(1000, 9999);
    }

    private function apiCodeExists(string $apiCode): bool
    {
        return (bool)IblockTable::getList([
            'select' => ['ID'],
            'filter' => ['=API_CODE' => $apiCode],
            'limit' => 1,
        ])->fetch();
    }
}

$sourceIblockId = 2;
$iblockTypeId = 'news';

$manager = new Manager($iblockTypeId, [$sourceIblockId]);
$manager->setIblockImplementer(new UniqueApiCodeIblockCopy());
$result = $manager->startCopy();

if (!$result->isSuccess()) {
    foreach ($result->getErrors() as $error) {
        $writeError($error->getMessage());
    }
    exit(1);
}

$map = $manager->getMapIdsCopiedEntity();
$newIblockId = $map[$sourceIblockId] ?? null;

echo "New iblock ID: {$newIblockId}\n";
echo "Elements will be copied in background by agent (on hits or cron).\n";

    
Проверка
  1. В админке появился новый инфоблок с тем же именем (копия).
  2. Свойства и разделы уже на месте.
  3. Элементы появляются постепенно — после хитов с агентами или по крону.
  4. $map[$sourceIblockId] — ID нового инфоблока.

Шаг 2. Копирование в другой тип или группу

Если нужен не дубликат «рядом», а перенос:

        $manager = new Manager('lists', [$sourceIblockId], $sourceGroupId = 0);

$manager->setTargetLocation(
    targetIblockTypeId: 'content',   // целевой тип
    targetSocnetGroupId: 15,         // или 0 для «обычного» IBLOCK
);
$manager->setIblockImplementer(new UniqueApiCodeIblockCopy());

$result = $manager->startCopy();

    

IblockImplementer::getFields() подставит IBLOCK_TYPE_ID и SOCNET_GROUP_ID в поля нового инфоблока. Права доступа переносятся с учётом режима (RIGHTS_MODE E/S).

XML_ID у инфоблока сбрасывается в prepareFieldsToCopy(), API_CODEнет. Для копии на том же сайте подключайте UniqueApiCodeIblockCopy из шага 1 или задайте код явно в своём implementer.

Шаг 3. Выборочное копирование (features)

По умолчанию активны все features:

Feature Что копирует
field Поля элемента/раздела, свойства, enum
section Дерево разделов
element Элементы (асинхронно)
workflow Шаблоны Bizproc (нужен модуль)

Отключение:

        $manager = new Manager('catalog', [$catalogIblockId]);

// Только структура без контента — шаблон для нового сайта
$manager->removeFeature('element');
$manager->removeFeature('workflow');

$result = $manager->startCopy();

    
Проверка
Новый IBLOCK содержит свойства и разделы, элементов нет, агент IblockStepper не ставится в очередь.

Шаг 4. Dictionary — передача контекста в copier

Bitrix\Main\Type\Dictionary кладётся в Container и доходит до ElementImplementer::prepareFieldsToCopy().

Стандартные ключи для элементов (stepper заполняет сам):

  • targetIblockId — ID нового инфоблока;
  • sectionsRatio[oldSectionId => newSectionId];
  • enumRatio[oldEnumId => newEnumId] для списков;
  • fieldRatio[oldPropertyId => newPropertyId].

Кастомные ключи — для своих implementer-ов. Пример из модуля lists:

        use Bitrix\Main\Type\Dictionary;

$manager->setDictionary(new Dictionary([
    'LIST_ELEMENT_URL' => '/company/lists/#list_id#/element/#section_id#/#element_id#/',
]));

    

Шаг 5. Кастомные implementer-ы

Manager позволяет подменить реализацию до startCopy():

        $manager->setIblockImplementer(new UniqueApiCodeIblockCopy());
$manager->setFieldImplementer(new MyFieldImplementer());
$manager->setWorkflowImplementer(new MyWorkflowImplementer($iblockTypeId));

    

Типовой кейс — уникальный API_CODE при клонировании на одной установке.

Если REST на копии не нужен, в prepareFieldsToCopy() достаточно:

        unset($fields['API_CODE']);
$fields['REST_ON'] = 'N';

    

Модуль lists наследует базовые классы:

  • Bitrix\Lists\Copy\Implement\Iblock — переопределяет поля списка;
  • Bitrix\Lists\Copy\Implement\Children\Field — особенности свойств списков.

Паттерн расширения: наследуйте Bitrix\Iblock\Copy\Implement\Iblock (или Children\Field), переопределите prepareFieldsToCopy() / getFields() и зарегистрируйте через setter Manager-а.

Не подменяйте EntityCopier напрямую — Manager собирает цепочку children сам.

Шаг 6. Контроль фонового копирования элементов

После startCopy() с feature element:

  • В b_option (модуль iblock) появляются ключи:
    • IblockGroupQueue — очередь ID новых инфоблоков;
    • IblockGroupStepper_{newIblockId} — сериализованные параметры (source ID, ratios);
    • IblockGroupChecker_{newIblockId} — флаг активного копирования.
  • Агент Bitrix\Iblock\Copy\Stepper\Iblock::execAgent() обрабатывает по 3-5 элементов.
  • Прогресс косвенно — по ElementTable::getCount(['=IBLOCK_ID' => $newIblockId]) в stepper (offset = count в целевом IBLOCK).
  • Ошибки элементов — в IblockGroupError_{newIblockId} (serialize массива ID источника).

Проверка завершения:

        use Bitrix\Iblock\ElementTable;
use Bitrix\Main\Config\Option;

$sourceCount = ElementTable::getCount(['=IBLOCK_ID' => $sourceIblockId]);
$targetCount = ElementTable::getCount(['=IBLOCK_ID' => $newIblockId]);
$checker = Option::get('iblock', 'IblockGroupChecker_' . $newIblockId, '');

$done = ($checker !== 'Y') && ($targetCount >= $sourceCount);

    
ℹ️ Совет Для production добавьте свой агент-надсмотрщик или событие по завершении очереди — штатного OnAfterCopy на уровне Manager нет, hook — Stepper\Entity::onAfterCopy().

Шаг 7. CLI-команда в модуле (рекомендуемая обёртка)

File: local/modules/vendor.tools/lib/Cli/Command/CopyIblockCommand.php

        <?php
declare(strict_types=1);

namespace Vendor\Tools\Cli\Command;

use Bitrix\Iblock\Copy\Manager;
use Bitrix\Main\Loader;
use Vendor\Tools\Iblock\UniqueApiCodeIblockCopy;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

final class CopyIblockCommand extends Command
{
    protected static $defaultName = 'vendor:iblock:copy';

    protected function configure(): void
    {
        $this
            ->addArgument('type', InputArgument::REQUIRED, 'IBLOCK_TYPE_ID')
            ->addArgument('id', InputArgument::REQUIRED, 'Source IBLOCK_ID')
            ->addOption('target-type', null, InputOption::VALUE_REQUIRED, 'Target IBLOCK_TYPE_ID')
            ->addOption('without-elements', null, InputOption::VALUE_NONE, 'Skip elements');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if (!Loader::includeModule('iblock')) {
            $output->writeln('<error>iblock module required</error>');
            return Command::FAILURE;
        }

        $iblockId = (int)$input->getArgument('id');
        $manager = new Manager((string)$input->getArgument('type'), [$iblockId]);
        $manager->setIblockImplementer(new UniqueApiCodeIblockCopy());

        if ($targetType = $input->getOption('target-type')) {
            $manager->setTargetLocation($targetType);
        }
        if ($input->getOption('without-elements')) {
            $manager->removeFeature('element');
        }

        $result = $manager->startCopy();
        if (!$result->isSuccess()) {
            foreach ($result->getErrors() as $error) {
                $output->writeln('<error>' . $error->getMessage() . '</error>');
            }
            return Command::FAILURE;
        }

        $map = $manager->getMapIdsCopiedEntity();
        $output->writeln('<info>New IBLOCK_ID: ' . ($map[$iblockId] ?? 'unknown') . '</info>');
        $output->writeln('<comment>Elements copy via agent on hits or cron — run cron_events.php to speed up.</comment>');

        return Command::SUCCESS;
    }
}

    

Запуск:

        php bitrix/bitrix.php vendor:iblock:copy news 2 --target-type=content

    

Нюансы копирования элементов

Bitrix\Iblock\Copy\Implement\Element при подготовке полей:

  • переносит PREVIEW_PICTURE / DETAIL_PICTURE через CFile::makeFileArray;
  • свойства типа F — файлы; L — через enumRatio; пользовательские типы — через ConvertFromDB;
  • мапит IBLOCK_SECTION_ID через sectionsRatio;
  • заменяет ID свойств через fieldRatio;
  • сбрасывает XML_ID, DATE_CREATE, TIMESTAMP_X;
  • CODE переносит без изменений — при UNIQUE=Y в настройках поля дубликаты отсекаются на CIBlockElement::Add;
  • копирует права элемента (CIBlockElementRights), исключая унаследованные от IBLOCK.

Не ждите автоматики для:

  • привязок к SKU/торговому каталогу (catalog module) — нужна отдельная логика;
  • SEO-шаблонов inherited properties — копируется базовая структура, не весь SEO-контекст сайта;
  • привязок элемент→элемент в других IBLOCK — только «сырые» значения свойств.

Антипаттерны и типичные ошибки

Симптом Причина Решение
Class "Bitrix\Iblock\Copy\Implement\Iblock" not found Класс-implementer объявлен до prolog / includeModule('iblock') Сначала bootstrap Bitrix, затем use и объявление класса
Undefined constant "STDERR" Скрипт запущен через браузер, не CLI $writeError с проверкой defined('STDERR')
«Символьный код API должен быть уникальным» API_CODE копируется как у источника UniqueApiCodeIblockCopy или unset($fields['API_CODE'])
Много одинаковых элементов → один в копии Общий CODE + UNIQUE=Y у поля элемента; ошибки Add не прерывают stepper Проверить GROUP BY CODE в источнике, опцию IblockGroupError_{id}; уникализировать CODE при копировании
В копии меньше элементов, чем в источнике Очередь ещё не отработала (мало хитов / cron не настроен) или часть Add упала с ошибкой Дождаться IblockGroupChecker_{id} != Y, прогнать cron_events.php; смотреть IblockGroupError_{id}
Новый IBLOCK пустой по элементам Агенты отключены на хитах (check_agents = N) и cron не запускается Включить cron или вернуть агентов на хиты; прогнать cron_events.php
Свойства есть, значения списков пустые field скопирован до element, но enumRatio битый Не менять порядок features; не копируйте элементы без field
Дубликат XML_ID / ошибка Add Ручная подстановка XML_ID Ядро unset-ит XML_ID — не восстанавливайте без нужды
Timeout при startCopy() Пытаетесь синхронно копировать элементы сами Используйте только Manager + stepper
Bizproc не скопировался Модуль bizproc выключен или feature снят Loader::includeModule('bizproc'), не вызывайте removeFeature('workflow')
Права «не те» в группе SOCNET_GROUP_ID не передан в setTargetLocation Явно задайте target group

Не используйте Manager для копирования между разными установками Bitrix — только внутри одной БД/файловой системы (CFile ссылается на локальные файлы).

Связь с модулем Lists

Референсная интеграция — Bitrix\Lists\Controller\Iblock::copyAction():

        $manager = new Manager($params['IBLOCK_TYPE_ID'], [$params['IBLOCK_ID']], $params['SOCNET_GROUP_ID']);
$manager->setIblockImplementer(new \Bitrix\Lists\Copy\Implement\Iblock());
$manager->setFieldImplementer(new \Bitrix\Lists\Copy\Implement\Children\Field());
$manager->setDictionary(new Dictionary(['LIST_ELEMENT_URL' => $params['LIST_ELEMENT_URL'] ?? '']));
$result = $manager->startCopy();
return $manager->getMapIdsCopiedEntity()[$params['IBLOCK_ID']] ?? null;

    

Массовое копирование списков группы — Bitrix\Lists\Copy\Integration\GroupStepper (очередь IBLOCK-ов по 5 за проход).

Заключение

Bitrix\Iblock\Copy\Manager — штатный способ программного клонирования инфоблока: синхронная структура + асинхронный контент. Минимальный сценарий — три строки (new Manager, startCopy, getMapIdsCopiedEntity). Production-сценарий — target location, выбор features, cron-мониторинг и при необходимости кастомные implementer-ы по образцу lists.

Следующий шаг: обернуть вызов в консольную команду или action контроллера, логировать $result->getErrors() и ID mapping. Для ускорения копирования элементов прогоняйте cron_events.php или дождитесь хитов с агентами.

Опубликовано 5 дней назад

Комментарии (0)

Пожалуйста, войдите в аккаунт, чтобы оставить комментарий

Оставить комментарий

Пока нет ни одного комментария. Будьте первым!

Похожие статьи

Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.

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

0 / 25

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

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

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

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

Войти