Программное копирование инфоблока через Bitrix\Iblock\Copy\Manager
Кирилл Новожилов
Автор
Содержание
В коде механизм копирования инфоблока целиком нужен, когда:
- разворачиваете шаблонный каталог/контент на новый сайт или стенд;
- клонируете универсальный список в группу соцсети (модуль
listsтак и делает); - мигрируете IBLOCK между типами (
news→content) без ручного экспорта.
Точка входа — 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. Подготовка окружения
- Агенты и копирование элементов. После
startCopy()элементы не копируются в том же HTTP-запросе — их подхватывает агентBitrix\Iblock\Copy\Stepper\Iblock. - На время копирования больших инфоблоков увеличивать лимиты PHP-FPM не обязательно: stepper сам дробит работу.
- Создайте целевой тип инфоблока заранее, если копируете в другой тип.
cron_events.php, чтобы не ждать посетителей и не грузить публичные запросы.Как проверить:
В админке: «Настройки → Инструменты → Агенты» — ищите Bitrix\Iblock\Copy\Stepper\Iblock::execAgent();. Статус очереди — опция IblockGroupChecker_{newIblockId}.
Шаг 1. Минимальное копирование «как есть»
Скрипт для CLI или одноразового запуска через браузер
Важно перед кодом:
STDERRдоступен только в CLI; для веб-запуска нужен fallback наecho.- Ядро сбрасывает
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";
- В админке появился новый инфоблок с тем же именем (копия).
- Свойства и разделы уже на месте.
- Элементы появляются постепенно — после хитов с агентами или по крону.
$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();
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);
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/торговому каталогу (
catalogmodule) — нужна отдельная логика; - 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 или дождитесь хитов с агентами.
Комментарии (0)
Пожалуйста, войдите в аккаунт, чтобы оставить комментарий
Оставить комментарийПока нет ни одного комментария. Будьте первым!
Похожие статьи