Переезд с самописной таблицы на инфоблоки
Переносим данные из своей таблицы в инфоблок: карта полей, повторяемый скрипт, редиректы и сверка перед отключением.
Механика
Своя таблица оправдана ровно до того момента, когда данные понадобились редакции. Как только нужны список и форма в админке, права, поиск, кэш, человекопонятные адреса и произвольные поля, инфоблок обходится дешевле любого своего интерфейса.
Инфоблок отдаёт весь этот набор готовым, и в этом весь смысл переезда. Список и карточка в админке, права на группы, история изменений, свойства, разделы, поиск и обмен с учётной системой начинают работать без единой строки кода.
Переезд при этом - не копирование строк из таблицы в таблицу. Это карта полей, ключ связи, адреса страниц, повторяемый скрипт и сверка результата, и каждый из этих пунктов способен утопить работу в одиночку.
Ключ связи решает всё остальное. Первичный ключ старой таблицы кладут во внешний код элемента, и тогда скрипт можно запускать сколько угодно раз: он обновит уже перенесённое и добавит новое.
Старые адреса переживают переезд только вместе с редиректами. Ссылки из поиска, писем и чужих сайтов ведут на прежние страницы, и без правила перенаправления переезд превращается в потерю трафика.
Данные переносят порциями, а не одним проходом. Сто тысяч строк не влезают ни в одну выборку и ни в один таймаут веб-сервера, поэтому скрипт запоминает последний обработанный ключ и продолжает с него.
Старое хранилище отключают последним, а не первым. Месяц параллельной жизни стоит дешевле любого срочного восстановления, потому что прямые запросы к старой таблице находятся по всему проекту ещё долго.
Шаги
- Составить карту полей: что станет свойством, что полем, что выбросим.
- Завести инфоблок, свойства и права миграцией, а не руками через админку.
- Перенести данные порциями, положив первичный ключ во внешний код элемента.
- Настроить редиректы со старых адресов страниц на новые адреса элементов.
- Переключить чтение на инфоблок, оставив запись в оба хранилища сразу.
- Сверить количество и содержимое, и только потом убирать старую таблицу.
Код
Описываем карту полей:
$map = [ 'TITLE' => 'NAME', // заголовок становится названием элемента 'ANONS' => 'PREVIEW_TEXT', 'BODY' => 'DETAIL_TEXT', 'IS_ACTIVE' => 'ACTIVE', 'VENDOR' => 'PROPERTY_VENDOR', // остальное уходит в свойства 'YEAR' => 'PROPERTY_YEAR',];// поля, которые никто не читает годами, в карту не попадают вовсеКарта полей - это первое, что стоит записать и показать заказчику. Половина колонок старой таблицы обычно оказывается мусором, и переносить их - значит тащить в новый инфоблок чужие ошибки пятилетней давности.
Читаем старую таблицу порциями:
$rows = OldItemTable::getList([ 'select' => ['*'], 'filter' => ['>ID' => $lastId], // продолжаем с последнего обработанного 'order' => ['ID' => 'ASC'], 'limit' => 500,])->fetchAll();Порция в пятьсот строк проходит за секунды и укладывается в любой таймаут. Смещение при этом считают по ключу, а не по номеру страницы: строки в старой таблице продолжают меняться прямо во время переноса.
Ищем уже перенесённый элемент по внешнему коду:
$exists = CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId, '=XML_ID' => 'old-' . $row['ID']], false, false, ['ID'])->Fetch();// внешний код связывает строку таблицы с элементом инфоблока навсегдаВнешний код делает скрипт повторяемым, а перенос - постепенным. Запуск в среду догружает то, что появилось после запуска в понедельник, и не создаёт ни одного дубля.
Пишем элемент инфоблока:
$element = new CIBlockElement();$fields = [ 'IBLOCK_ID' => $iblockId, 'NAME' => $row['TITLE'], 'ACTIVE' => $row['IS_ACTIVE'], 'CODE' => CUtil::translit($row['TITLE'], 'ru', ['replace_space' => '-']), 'XML_ID' => 'old-' . $row['ID'], 'PROPERTY_VALUES' => ['VENDOR' => $row['VENDOR'], 'YEAR' => $row['YEAR']],];// третий аргумент - переиндексация поиска, на массовой записи её выключают// второй аргумент - документооборот, у обычных инфоблоков он не нужен$exists ? $element->Update($exists['ID'], $fields, false, false) : $element->Add($fields, false, false);Символьный код собирают из названия транслитерацией, а не берут из старой таблицы. Совпадения кодов при этом неизбежны, поэтому к коду добавляют первичный ключ строки, иначе второй элемент молча получит чужой адрес.
Переносим картинки из файловой системы:
$path = $_SERVER['DOCUMENT_ROOT'] . '/old_upload/' . $row['IMAGE'];if (is_file($path)) { $fields['DETAIL_PICTURE'] = CFile::MakeFileArray($path); // копия в /upload/}// старую папку не трогают до конца переезда: это единственный оригиналКартинки платформа хранит своей записью в таблице файлов, а не путём в поле. Поэтому файл именно копируют, а старую папку оставляют нетронутой до тех пор, пока перенос не проверен целиком.
Уводим старые адреса на новые:
$row = RedirectTable::getRow([ 'filter' => ['=OLD_URL' => $APPLICATION->GetCurPage()], 'select' => ['NEW_URL'],]);if ($row) { LocalRedirect($row['NEW_URL'], false, '301 Moved Permanently');}Таблица соответствия адресов заполняется тем же скриптом переноса. Постоянный код ответа важнее самого редиректа: временный код оставляет старый адрес в поисковой выдаче на месяцы.
Сверяем результат по количеству:
$oldCount = OldItemTable::getCount();$newCount = CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId], []);// третий параметр как пустой массив возвращает количество, а не выборкуecho "было {$oldCount}, стало {$newCount}\n";Совпадение количества - необходимое условие, но не достаточное. После него берут десяток случайных записей и сравнивают поля глазами, потому что пустое свойство в тысяче элементов подсчёт не покажет.
Пересобираем поиск после переноса:
CIBlockElement::UpdateSearch($elementId, true); // элемент попадает в поиск сайта$GLOBALS['CACHE_MANAGER']->ClearByTag('iblock_id_' . $iblockId);// массовую запись обычно завершают полной переиндексацией из настроек модуляМассовая запись через прямые вызовы не поднимает механизмы, которые работают при ручном сохранении. Поиск, фасетный индекс и кэш после переезда приводят в порядок отдельным шагом, иначе новый раздел выглядит пустым.
Ограничения
Инфоблок не бесплатен, и это стоит понимать до переезда. На миллионах записей с десятком свойств выборка обходится дороже плоской таблицы, и такие объёмы оставляют в своём хранилище или переносят в highload-блок.
Справочники переносят раньше элементов. Свойства типа списка и привязки ссылаются на значения и на другие элементы, поэтому порядок переноса задаётся зависимостями, а не удобством скрипта.
Прямые запросы к старой таблице разбросаны по проекту сильнее, чем кажется. Поиск по имени таблицы в коде обязателен до отключения, и обычно он находит пару обработчиков, о которых никто уже не помнил.
Права на новый инфоблок задают до наполнения. Иначе первый же контент-менеджер увидит раздел, которого видеть не должен, а исправлять права на трёх тысячах элементов сложнее, чем на пустом инфоблоке.
Переезд удобно делать в два захода. Сначала переносят данные и оставляют оба хранилища живыми, потом переключают чтение, и только после спокойной недели убирают старую таблицу вместе со скриптом.
Типичные проблемы
Повторный запуск скрипта наплодил дубли элементов.
Скрипт не ищет уже перенесённое, потому что первичный ключ старой строки никуда не записан. Ключ кладут во внешний код элемента.
После переезда старые ссылки отдают ошибку 404.
Адреса элементов собираются по новым правилам инфоблока, а редиректы со старых адресов никто не завёл.
Скрипт переноса падает по нехватке памяти.
Выборка тянет всю таблицу разом вместо порции в несколько сотен строк. Порции задают ключом последней обработанной записи.
Значения свойства-списка после переноса пустые.
Справочник значений не перенесён до элементов, и запись просто нечему было сопоставить. Справочники переносят первыми.
Поиск по сайту не находит перенесённые записи.
Массовая запись не поднимает механизмы, работающие при ручном сохранении элемента. Индекс поиска пересобирают отдельным шагом.
Часть проекта продолжает писать в старую таблицу.
Прямые запросы к ней остались в обработчиках и в шаблонах. Их ищут по имени таблицы до отключения старого хранилища.
Частые вопросы
Как перенести данные из самописного решения в инфоблоки?
Скриптом, который читает старую таблицу порциями и пишет элементы с внешним кодом. Первичный ключ старой строки в поле внешнего кода делает перенос повторяемым и позволяет догружать данные частями.
Стоит ли вообще переезжать на инфоблоки?
Стоит, если данные нужны редакции: админка, права, поиск, свойства и адреса появятся сами. Если данные читает только код и объём большой, своя таблица остаётся дешевле по запросам.
Что делать со старыми адресами страниц?
Заполнять таблицу соответствия прямо в скрипте переноса и отдавать постоянный редирект. Временный код ответа оставляет старый адрес в поисковой выдаче на месяцы.
Можно ли оставить старую таблицу навсегда?
Можно, но тогда данные живут в двух местах и рано или поздно расходятся. Гибридный вариант оправдан только как переходный период на несколько недель.
Как проверить, что перенос прошёл полностью?
Сравнить количество записей и выборочно сверить содержимое десятка элементов. Совпадение чисел ничего не говорит о пустых свойствах и потерянных картинках.
Смежное
- Свои таблицы на ORM - оглавление подтемы
- Где хранить данные: инфоблок, highload-блок или своя таблица - как выбрать хранилище с самого начала
- Своя таблица на ORM: сущность, запросы, изменение структуры - хранилище, из которого уезжаем
- Миграции структуры: перенос инфоблоков и настроек между стендами - как завести новый инфоблок кодом
- Импорт каталога из файла: CSV, повторный запуск, свои поля - тот же приём с внешним кодом
- Массовая правка товаров: обновление свойств скриптом - обход больших объёмов порциями
- Ядро D7 - устройство ядра целиком