Розробка кастомних імпорт/експорт модулів 1С-Бітрікс
Стандартний обмін даними Бітрікс — CommerceML для 1С, CSV-імпорт в інфоблоки — покриває типові завдання. Коли джерело даних нестандартне, структура даних складна або потрібна двостороння синхронізація в реальному часі — пишемо власний модуль.
Архітектура кастомного модуля
Модуль Бітрікс — це директорія в /local/modules/{vendor}.{modulename}/, зареєстрована через RegisterModule. Структура:
local/modules/company.import/
├── install/
│ ├── index.php # InstallDB(), UnInstallDB(), DoInstall()
│ └── db/mysql/install.sql
├── lib/
│ ├── Importer.php # основна логіка
│ ├── Parser/
│ │ ├── XmlParser.php
│ │ └── CsvParser.php
│ └── Queue/
│ └── ImportQueue.php
├── admin/
│ └── import_settings.php # адміністративний інтерфейс
├── include.php
└── .settings.php
Імпорт із довільних джерел
Імпорт із XML (кастомна схема, не CommerceML):
namespace Company\Import;
use Bitrix\Main\Loader;
use Bitrix\Catalog\ProductTable;
class Importer {
private \SimpleXMLElement $xml;
public function __construct(string $filePath) {
Loader::includeModule('iblock');
Loader::includeModule('catalog');
$this->xml = simplexml_load_file($filePath);
}
public function run(): array {
$stats = ['created' => 0, 'updated' => 0, 'errors' => 0];
foreach ($this->xml->products->product as $product) {
try {
$this->processProduct($product, $stats);
} catch (\Throwable $e) {
\Bitrix\Main\Diag\Debug::writeToFile($e->getMessage(), 'IMPORT ERROR', '/bitrix/modules/company.import/error.log');
$stats['errors']++;
}
}
return $stats;
}
private function processProduct(\SimpleXMLElement $p, array &$stats): void {
$externalId = (string)$p->id;
$existing = $this->findByExternalId($externalId);
$fields = [
'IBLOCK_ID' => IMPORT_IBLOCK_ID,
'NAME' => (string)$p->name,
'CODE' => \CUtil::translit((string)$p->name, 'ru'),
'ACTIVE' => (string)$p->is_active === '1' ? 'Y' : 'N',
'PROPERTY_VALUES' => [
'EXTERNAL_ID' => $externalId,
'VENDOR_CODE' => (string)$p->sku,
'DESCRIPTION' => (string)$p->description,
],
];
if ($existing) {
\CIBlockElement::Update($existing, $fields);
$stats['updated']++;
} else {
$el = new \CIBlockElement();
$newId = $el->Add($fields);
if (!$newId) throw new \RuntimeException($el->LAST_ERROR);
$stats['created']++;
}
// Оновлюємо ціну та залишок
\CPrice::SetBasePrice($newId ?? $existing, (float)$p->price, 'RUB');
\CCatalogProduct::Update($newId ?? $existing, ['QUANTITY' => (int)$p->stock]);
}
}
Пакетна обробка та прогрес
При великому обсязі даних (100 000+ записів) обробка через веб-запит неможлива — timeout. Використовуємо агент Бітрікс зі збереженням прогресу:
// У таблиці зберігається: файл, поточна позиція, статистика
class ImportQueue {
public static function processChunk(int $jobId, int $offset, int $limit = 500): array {
$job = ImportJobTable::getById($jobId)->fetch();
// ... читаємо $limit рядків починаючи з $offset
// ... обробляємо
// ... оновлюємо прогрес у БД
return ['processed' => $count, 'total' => $job['total_rows']];
}
}
// Агент викликається щохвилини, обробляє наступний чанк
function ImportAgent(): string {
$activeJob = getActiveImportJob();
if (!$activeJob) return '';
$result = ImportQueue::processChunk($activeJob['id'], $activeJob['offset']);
if ($activeJob['offset'] + $result['processed'] >= $result['total']) {
markJobComplete($activeJob['id']);
return ''; // агент завершено
}
return 'ImportAgent();'; // агент продовжує
}
Експорт даних
Експорт у довільний формат для зовнішньої системи:
class Exporter {
public function exportOrders(\DateTime $from, \DateTime $to): string {
$orders = \Bitrix\Sale\OrderTable::getList([
'filter' => [
'>=DATE_INSERT' => $from->format('d.m.Y H:i:s'),
'<=DATE_INSERT' => $to->format('d.m.Y H:i:s'),
'CANCELED' => 'N',
],
'select' => ['ID', 'ACCOUNT_NUMBER', 'PRICE', 'CURRENCY', 'DATE_INSERT', 'USER_ID'],
])->fetchAll();
$xml = new \XMLWriter();
$xml->openMemory();
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('orders');
foreach ($orders as $order) {
$xml->startElement('order');
$xml->writeElement('id', $order['ID']);
$xml->writeElement('number', $order['ACCOUNT_NUMBER']);
$xml->writeElement('amount', $order['PRICE']);
$xml->writeElement('date', $order['DATE_INSERT']->format(\DateTime::ATOM));
// ... позиції замовлення
$xml->endElement();
}
$xml->endElement();
return $xml->outputMemory();
}
}
Двостороння синхронізація
Найскладніше — синхронізація без втрат при одночасних змінах в обох системах.
Рішення: timestamp-based sync із полем SYNC_HASH:
ALTER TABLE b_iblock_element ADD COLUMN sync_hash VARCHAR(32);
ALTER TABLE b_iblock_element ADD COLUMN synced_at DATETIME;
При експорті записуємо хеш стану. При наступному імпорті: якщо хеш змінився в джерелі — оновлюємо в Бітрікс. Якщо хеш змінився в Бітрікс (користувач відредагував) — відправляємо зміни назад у джерело. Якщо обидва змінилися — конфлікт, логуємо для ручного розбору.
Адміністративний інтерфейс модуля
// admin/import_settings.php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_before.php';
$APPLICATION->SetTitle('Налаштування імпорту');
// Форма налаштувань: шлях до FTP, розклад агента, маппінг полів
// Лог останніх запусків із результатами (створено/оновлено/помилки)
// Кнопка "Запустити зараз"
Формати та джерела
| Формат/Джерело | Інструменти | Особливості |
|---|---|---|
| XML (кастомний) | SimpleXML, XMLReader | XMLReader для файлів > 100 МБ |
| CSV/XLSX | PhpSpreadsheet, fgetcsv | XLSX — бінарний, потребує бібліотеки |
| JSON REST API | curl, Guzzle | Пагінація, rate limiting |
| FTP/SFTP | phpseclib | Автоматичне завантаження файлів |
| 1С CommerceML | Вбудований обмін Бітрікс | Кастомізація через події |
| Google Sheets | Google Sheets API v4 | Для невеликих обсягів |
Терміни
| Етап | Термін |
|---|---|
| Аналіз форматів і маппінг полів | 1–2 дні |
| Розробка парсера/експортера | 3–5 днів |
| Пакетна обробка, агент Бітрікс | 2–3 дні |
| Адміністративний інтерфейс | 2–3 дні |
| Двостороння синхронізація (якщо потрібна) | 3–5 днів |
| Тестування на реальних даних | 2–3 дні |
Разом: 2–3 тижні для одностороннього імпорту; 3–4 тижні для двосторонньої синхронізації.







