Миграция контента из Drupal 6 в 7 при помощи Migrate API
Привет, друпалеры! С трудом нашел несколько часов, чтобы дописать этот пост: прям разрываюсь между Docker'ом, Drupal 8 и этой статьей. Но, если не написать сейчас, то велик шанс того, что материал так и не дойдет до блога. Рассказывать я сегодня буду о миграции данных с Drupal 6 на 7, используя модуль Migrate.
Как и многие записи в блоге, этот материал стал результатом моего знакомства с новым для меня модулем — Migrate. Да, разрабатывая уже более 5 лет на Drupal, я впервые столкнулся с задачей на перенос сайта с 6-ой версии Drupal'a на 7-ую — чему был несказанно рад. Ну а как не радоваться, когда ты изучаешь что-то новое для себя, а тебе за конечный результат еще и деньги платят?
Для начала, как обычно, немного о проекте, в рамках которого осуществлялась миграция данных. Задачей проекта не являлось полностью скопировать сайт с 6-ки на 7-ку — необходимо было внести значительное число правок в структуру контента, а именно:
- перенести на новый сайт лишь ноды определнных контент типов (Content types);
- несколько контент типов должны быть перенесены в один;
- уменьшение количества терминов таксономии: заказчик предоставил документ в котором указаывалось на какой термин необходимо заменить термины со старого сайта;
- добавление новых полей, значения которых должны будут формироваться на основе данных со старого сайта;
- на новый сайт должны быть перенесены лишь необходимые файлы, которые связаны с полями нод;
- сохранение связей Entity reference между нодами (была связка из трех контент типов: "Конференция" - "Выступление" - "Докладчик");
- полное изменение дизайна сайта.
В общем, миграция данных была самым интересным таском на проекте и я забрал его конечно же себе. Реализовывать перенос контента с Drupal 6 на 7 было решено с использованием модуля Migrate. Собственно, что же этот модуль позволяет и нужен ли он вообще?
Migrate API
Модуль Migrate — это своего рода фреймворк для переноса данных с различных источников в Drupal. Т.е. перенос сайта с Drupal 6 на 7 — это частный случай. С таким же успехом вы можете импортировать данные с XML, JSON источников, а также с баз данных других фреймворков — например с Wordpress и Joomla. Для полноценной работы Migrate необходимо наличие уникальных ключей для поставляемых данных. От части Migrate является более гибким решением по сравнению с модулем Feeds.
Как вы должны понимать, для начала продуктивной работы с каким бы то ни было фреймворком вам необходимо знать его API. Лично у меня ушло порядка 2 дней для того, чтобы более менее ориентироваться в Migrate API. Да, сразу может показаться, что вы зря теряете время на изучение какого-то инструмента, но, уверяю вас, в будущем это время окупится вам сполна!
Пожалуй, приведу несколько аргументов для того, чтобы окончательно вас убедить в том, что Migrate — полезная штука:
- возможность настраивать миграцию как через собственный модуль, так и используя интерфейс в админке;
- возможность отката (Rollback) миграции, если что-то пошло не так (ну или в целях тестирования);
- возможность поэтапной миграции данных;
- достаточно гибкое API (я нашел в нем отклик на все мои нестандартные "хотелки");
- возможность миграции и импорта данных с БД, XML, RSS, CSV;
- готовые модули для стандартных миграций с D6, Wordperss.
Модуль Migrate включает в себя аж 2 модуля примеров 'migrate_example' и 'migrate_example_baseball' — именно с разбора кода этих модулей и стоит начинать, т.к. он обильно покрыт комментариями, проливающими свет на API. Так-с.. модули посмотреть можете и попозже, сначала мой пост дочитайте: я ж тут все по-русски разжевывать буду!
Миграция контента Drupal-to-Drupal
Как я уже сказал, Migrate — универсальный инструмент для миграции данных с различных источников. Однако для наиболее популярных задач уже существуют дополнительные модули: например, для миграции Drupal-to-Drupal или же Wordpress-to-Drupal. Не стоит пренебрегать ими — ставьте сразу. Собственно, как же работают эти sub-модули и в целом сам Migrate?
Фреймворк Migrate , в отличие от нынешнего Drupal 7, написан с использованием ООП, а это значит, что вам необходимо понимать несколько вещей: что такое "класс", "метод" и "наследование". Таким образом, модуль предоставляют набор готовых классов, призванных упростить миграцию данных. Sub-модули предоставляют дополнительные классы, которые унаследованы от базовых модуля Migrate и имеют более функциональные методы под конкретную задачу.
Что такое Source & Destination?
Чисто интуитивно уже можно догадаться: Source — это источник с данными, Destination — это ваша база данных, куда необходимо перетянуть данные. Эти понятия — можно сказать, основа идеологии Migrate. Ваша задача, как раз и состоит, в том, чтобы настроить правила миграции ("маппинг" ин инглиш) из Source в Destination. Migrate позаботился даже о программистах-кликерах: базовый маппинг вы можете настроить через админку. Однако админка — это лишь вершина айсберга по сравнению с тем, что можно вытворять в собственных классах.
Раз я рассказываю про миграцию Drupal-to-Drupal, то пора бы уже пролить свет и на то, как же подключиться в базе данных D6. У вас есть два варианта: вы можете поднять локально дамп БД или же подключаться прямо к продакшену. Настройки подключения к базе данных D6 рекомендуется внести прямо в settings.php — просто добавьте в массив $database еще один массив, например, с ключом 'legacy' . Далее вам придется указывать этот самый ключ 'legacy' в качестве значения для 'source_connection' (это приблуда от модуля migrate_d2d).
Вот. Теперь, будем, считать ваш Drupal знает и про Source, и про Destination базы данных. Самое время создать модуль и настроить маппинг.
Создание модуля миграции на базе Migrate API
Итак, оставим килознаки теории и перейдем к практике. Модуль, с которого будут приведены примеры, у меня, если что, называется 'ncsrc_migration' . Info-файл модуля ничего необычного не содержит, кроме того, что необходимо подключать файлы с классами миграции:
Основной файл модуля 'ncsrc_migration.module' у меня так и остался пустым. Имплементацию хука hook_migrate_api, согласно канонам, лучше закинуть в файл 'ncsrc_migration.migrate.inc':
На самом деле имплементация хука hook_migrate_api — это самое простое. Теперь необходимо описать все указанные классы, да при этом еще соблюдая все требования Migrate API. Начну я пожалуй со вспомогательного файла 'ncsrc_migration.general.inc':
Понимаю, что с первого взгляда нихрена не понятно, но я не буду останавливаться и продолжу валить листингами кода. Читайте комментарии к коду. Начнем с класса миграции типа контента Докладчик (он же Presenter ):
Видимо, все же придется остановиться и прояснить некоторый моменты. По сути ваш класс миграции — это не только маппинг полей, но и возможность адаптировать миграцию под вашу конкретную задачу. Каждый метод в этом примере расширяет функционал метода из родительского класса, который предоставляет Migrate . Если говорить языком Drupal'a, то представьте, что эти методы — это хуки, которые мы привыкли имплементировать. Попробую немного объяснить какой метод за что отвечает и что в него надо писать.
Основные методы классов Migrate
__construct() — конструктор класса, стреляет сразу же, когда дело доходит до инициализации вашего класса. Не забывайте включать parent :: __construct ( $arguments ) ; в начало вашего кода, иначе рискуете потерять важные данные из родительских классов. В этом методе настраивается, как правило, маппинг полей. Откуда я взял все эти ключи полей? Принцип довольно прост: открываете в админке Task (термин взял как раз из интерфейса) с этой миграцией и глядите на что ругается вам система: какие Sorce поля не описаны, какие Destination поля остались неиспользуемыми. Собственно ваша задача настроить маппинг так, чтобы в интерфейсе не было никаких красных шрифтов с ошибками и предупреждениями. Для этого у вас есть набор методов, таких как addFieldMapping ( ) , addUnmigratedDestinations ( ) , removeFieldMapping ( ) .
query() — это ваша возможность "альтернуть" запрос, которым будут выгребаться данные из Source таблицы. Результат конечного запроса вы кстати опять же можете глянуть в интерфейсе на вкладке Source. В общем, этот метод нужен для того, чтобы вытягивать из Source базы больше данных, чем это делают родительские классы. На самом деле, если не включать $query = parent :: query ( ) ; , то вы можете написать свой query с нуля сами.
prepareRow() — отрабатывает после query(), позволяет изменить Source данные, которые придут на маппинг. Если у вас не получается одним запросом выгрести все данные из Source базы, то в этом методе можете инициировать еще несколько дополнительных запросов. Конечно лучше поработать над основным запросом в query() , но не всегда это удается. Чуть ниже вы увидите пример с этим методом.
prepare() — стреляет перед сохранением объекта в Destination базу. Это один из последних этапов миграции: данные из Source вытянуты, преобразованы согласно маппингу и готовы к сохранению. В вышеприведенном коде мне надо было отключить генерацию алиаса, чтобы использовался алиас с Sorce сайта.
complete() — стреляет после того, как объект сущности уже сохранен в Destination. Я был очень удивлен, когда уперся в его необходимость и нашел его в родительских классах. Все же приятно, когда ты только захотел, а оно уже и есть. Пример использования увидите ниже в миграции типа Webinar .
Методы разместил в порядке их выполнения. Теперь опять перейдем к наглядным примерам. Класс для миграции ноды типа "Доклад" ( Session в оригинале):
Заключительный листинг миграции контент типа Event для связки "Конференция" - "Выступление" - "Докладчик":
Еще один интересный момент из миграции типа контента Webinar :
Если я не путаю, то вышеприведенный код решает следующую проблему. На Destination сайте стоит File Entity модуль. У сущности File есть поле с описанием, однако мы не мигрируем все файлы, а только те, которые связаны с нодой Webinar. При создании ноды Webinar с файлом, создается также сущность File, которую, собствено, мы и обновляем в методе complete ( ) .
Фух.. Вот, наверное, и все нестандартные приемы миграции, с которыми я столкнулся при переносе контента с Drupal 6 на Drupal 7.
Используйте Drush для Migrate
Да, ребятки, запускайте ваши миграции через консоль. Во-первых, Drush предоставляет больше опций для запуска процесса миграции (например, можно мигрировать объекты с указанным ID). Во-вторых, при миграции большого числа данных процесс займет меньше времени и не завалится, как это бывает с запуском в браузере. Короче вот вам ниже парочка ссылок — не хочу все это переводить и переписывать. Тем более там и так все складно и понятно:
Все, моя совесть наконец-то чиста: я поделился всем, что сам узнал о Migrate. Не знаю, насколько еще актуален этот пост — большую часть поста я написал еще полгода назад, но никак не мог его закончить. Надеюсь, говнокода никто не узрел в моих листингах — я старался как мог ;)