Migrate API и с чем его едят. На примере миграции форума по Drupal 7. Часть 1
Данное руководство является переводом статьи.

Хотелось бы поделиться своим опытом миграции форума с Drupal 7 на Drupal 8, а также рассказать о проблемах, с которым пришлось столкнуться во время этого процесса, а также об инструментах, которые я использовал. Кроме этого я расскажу о подводных камнях, которые встретились при миграции форума и терминов к нему.

Инструменты


Весь процесс миграции мы будем осуществлять с использованием Drush. Теперь давайте определимся с тем, что именно нам понадобится для проведения миграции (я буду указывать версии, которые будут использовать на момент написания статьи)

Drupal: 8.4.5

Модули для миграции:

  • migrate
  • migrate_drupal
  • migrate_plus: 8.x-4.0-beta2
  • migrate_tools: 8.x-4.0-beta2

Drush: 9.1.0

Весь процесс миграции происходил с использованием


PHP: 7.1.14
MySQL: MariaDB 10.1.31

Примечание:


  • Все пути будут указаны относительно корня модуля (директории custom_migration_forum, или того названия, которое вы дадите своему модулю).
  • Перед началом миграции отключите модуль rdf в Drupal 8, поскольку с ним могут возникнуть проблемы во время исполнения Rolling back (когда отменяем изменения миграции).

Тестовый контент


Для актуальности информации я решил по ходу написания статьи дописывать модуль, который проводит миграцию форума. Тестовый контент я создал, используя для генерации контента Devel. Общее количество сгенерированных тем форума составляет 300 шт., а также комментарии к ним. Получилось вот что:

Сгенерированный Devel'ом контент

Подготовка к миграции


Сначала мы развернем чистый сайт на Drupal 8. На чистом сайте мы создаем свой модуль, либо используем GitHub.

Создаем файл custom_migration_forum.info.yml, в который заносим основную информацию про наш модуль и зависимости:

name: Custom Migration Forum
description: Custom module for migrating forum from a Drupal 7 site.
package: Migrations
type: module
core: 8.x
dependencies:
   - drupal:migrate
   - drupal:migrate_drupal
   - drupal:forum
   - migrate_plus:migrate_plus  (>=4.0-beta2)
   - migrate_tools:migrate_tools  (>=4.0-beta2)

Для того, чтобы удалить старые конфиги миграций, при деинсталяции модуля это нужно описать в custom_migration_forum.install. Бывало, я сталкивался с ситуациями, когда возникал конфликт конфигов из-за того, что старые конфиги не были удалены при деинсталяции модуля. Поэтому, дабы обезопасить себя, лучше удалять их при деинсталяции модуля.

custom_migration_forum.install

<?php

/**
 * @file
 * Contains migrate_forum_drupal8.install.
 */
 
/**
 * Implements hook_uninstall().
 *
 * Removes stale migration configs during uninstall.
 */
function custom_migration_forum_uninstall() {
  $query = \Drupal::database()->select('config', 'c');
  $query->fields('c', ['name']);
  $query->condition('name', $query->escapeLike('migrate_plus.') . '%', 'LIKE');
 
  $config_names = $query->execute()->fetchAll();
 
  // Delete each config using configFactory.
  foreach ($config_names as $config_name) {
    \Drupal::configFactory()->getEditable($config_name->name)->delete();
  }
}

Или же Вам просто нужно будет прописать зависимость у каждой миграции:
dependencies:
  enforced:
    module:
      - custom_migration_forum

Миграция терминов форума


Поскольку форум в Drupal 7 является, по сути, нодами с терминами, то для начала, мы должны мигрировать термины. Начнем с создания плагинов для миграции терминов и словаря.
src/Plugin/migrate/source/Vocabulary.php:

<?php
 
/**
 * @file
 * Contains \Drupal\migrate_therasomnia\Plugin\migrate\source\Vocabulary.
 */
 
namespace Drupal\custom_migration_forum\Plugin\migrate\source;
 
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
 
/**
 * Drupal 7 vocabularies source from database.
 *
 * @MigrateSource(
 *   id = "custom_migration_forum_vocabulary",
 *   source_provider = "taxonomy"
 * )
 */
class Vocabulary extends SqlBase {
 
  /**
   * {@inheritdoc}
   */
  public function query() {
    $query = $this->select('taxonomy_vocabulary', 'v')
      ->fields('v', array(
        'vid',
        'name',
        'description',
        'hierarchy',
        'module',
        'weight',
        'machine_name'
      ));
    // Filtered out unnecessary dictionaries.
    $query->condition('machine_name', 'forums');
    return $query;
  }
 
  /**
   * {@inheritdoc}
   */
  public function fields() {
    return array(
      'vid' => $this->t('The vocabulary ID.'),
      'name' => $this->t('The name of the vocabulary.'),
      'description' => $this->t('The description of the vocabulary.'),
      'help' => $this->t('Help text to display for the vocabulary.'),
      'relations' => $this->t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'),
      'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'),
      'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'),
      'parents' => $this->t("The Drupal term IDs of the term's parents."),
      'node_types' => $this->t('The names of the node types the vocabulary may be used with.'),
    );
  }
 
  /**
   * {@inheritdoc}
   */
  public function getIds() {
    $ids['vid']['type'] = 'integer';
    return $ids;
  }
 
}

src/Plugin/migrate/source/Terms.php:

<?php

/**
 * @file
 * Contains \Drupal\migrate_therasomnia\Plugin\migrate\source\Terms.
 */
 
namespace Drupal\custom_migration_forum\Plugin\migrate\source;
 
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
 
/**
 * Drupal 7 taxonomy terms source from database.
 *
 * @MigrateSource(
 *   id = "custom_migration_forum_term",
 *   source_provider = "taxonomy"
 * )
 */
class Terms extends SqlBase {
 
  /**
   * {@inheritdoc}
   */
  public function query() {
    $query = $this->select('taxonomy_term_data', 'td')
      ->fields('td', ['tid', 'vid', 'name', 'description', 'weight', 'format'])
      ->fields('tv', ['vid', 'machine_name'])
      ->distinct();
    // Add table for condition on query.
    $query->innerJoin('taxonomy_vocabulary', 'tv', 'td.vid = tv.vid');
    // Filtered out unnecessary dictionaries.
    $query->condition('tv.machine_name', 'forums');
    return $query;
  }
 
  /**
   * {@inheritdoc}
   */
  public function fields() {
    return [
      'tid' => $this->t('The term ID.'),
      'vid' => $this->t('Existing term VID'),
      'name' => $this->t('The name of the term.'),
      'description' => $this->t('The term description.'),
      'weight' => $this->t('Weight'),
      'parent' => $this->t("The Drupal term IDs of the term's parents."),
    ];
  }
 
  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // Find parents for this row.
    $parents = $this->select('taxonomy_term_hierarchy', 'th')
      ->fields('th', ['parent', 'tid']);
    $parents->condition('tid', $row->getSourceProperty('tid'));
    $parents = $parents->execute()->fetchCol();
    $row->setSourceProperty('parent', reset($parents));
    return parent::prepareRow($row);
  }
 
  /**
   * {@inheritdoc}
   */
  public function getIds() {
    $ids['tid']['type'] = 'integer';
    return $ids;
  }
 
}

После создания плагинов для миграции терминов и словарей, необходимо сделать конфигурацию для нашей миграции. Для этого создаем migrate_plus.migration.term.yml и migrate_plus.migration.vocablary.yml.

config/install/migrate_plus.migration.vocablary.yml:

id: custom_migration_forum_vocabulary
label: Taxonomy vocabulary forum
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: custom_migration_forum_vocabulary
  target: migrate
process:
  vid:
    -
      plugin: machine_name
      source: machine_name
    -
      plugin: make_unique_entity_field
      entity_type: taxonomy_vocabulary
      field: vid
      length: 32
      migrated: true
  label: name
  name: name
  description: description
  hierarchy: hierarchy
  module: module
  weight: weight
destination:
  plugin: entity:taxonomy_vocabulary

В 'source' мы указываем наш плагин custom_migration_forum_vocabulary, который был описан в классе Vocabulary.

config/install/migrate_plus.migration.term.yml:

id: custom_migration_forum_term
label: Taxonomy terms forum
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: custom_migration_forum_term
  target: migrate
process:
  tid: tid
  vid:
    plugin: migration
    migration: custom_migration_forum_vocabulary
    source: vid
  name: name
  description: description
  weight: weight
  parent: parent
  changed: timestamp
destination:
  plugin: entity:taxonomy_term
migration_dependencies:
  required:
    - custom_migration_forum_vocabulary

При миграции терминов в vid мы указываем машинное имя (custom_migration_forum_vocabulary), из которого мы будем брать ID словаря форума.

Миграция форума


Хотелось бы обратить внимание, что в данной статье мы мигрируем форум без дополнительных полей. Перед тем, как начать миграцию форумов, нам нужно мигрировать пользователей, ведь без них у форумов не будет авторов. Такую миграцию мы проведем с использованием плагинов из Ядра, но добавим их в свою группу миграции “Custom Migration Forum” для того чтобы можно было запустить всю миграцию одной командой. Нам нужно три миграции:

  1. миграция ролей (custom_migration_forum_user_role)
  2. миграция пользователей (custom_migration_forum_user)
  3. Миграция форматов текста (custom_migration_forum_filter_format).

Вот наши три yml-файла.

config/install/migrate_plus.migration.user_role.yml:

id: custom_migration_forum_user_role
label: User roles
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: d7_user_role
process:
  id:
    -
      plugin: machine_name
      source: name
    -
      plugin: user_update_8002
  label: name
  permissions:
    -
      plugin: static_map
      source: permissions
      bypass: true
      map:
        'use PHP for block visibility': 'use PHP for settings'
        'administer site-wide contact form': 'administer contact forms'
        'post comments without approval': 'skip comment approval'
        'edit own blog entries': 'edit own blog content'
        'edit any blog entry': 'edit any blog content'
        'delete own blog entries': 'delete own blog content'
        'delete any blog entry': 'delete any blog content'
        'create forum topics': 'create forum content'
        'delete any forum topic': 'delete any forum content'
        'delete own forum topics': 'delete own forum content'
        'edit any forum topic': 'edit any forum content'
        'edit own forum topics': 'edit own forum content'
    - plugin: flatten
  weight: weight
destination:
  plugin: entity:user_role
migration_dependencies:
  optional:
    - custom_migration_forum_filter_format

config/install/migrate_plus.migration.user.yml:

id: custom_migration_forum_user
label: User accounts
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
class: Drupal\user\Plugin\migrate\User
source:
  plugin: d7_user
process:
  uid: uid
  name: name
  pass: pass
  mail: mail
  created: created
  access: access
  login: login
  status: status
  timezone: timezone
  langcode:
    plugin: user_langcode
    source: language
    fallback_to_site_default: false
  preferred_langcode:
    plugin: user_langcode
    source: language
    fallback_to_site_default: true
  preferred_admin_langcode:
    plugin: user_langcode
    source: language
    fallback_to_site_default: true
  init: init
  roles:
    plugin: migration_lookup
    migration: custom_migration_forum_user_role
    source: roles
  user_picture:
    -
      plugin: default_value
      source: picture
      default_value: null
    -
      plugin: migration_lookup
      migration: d7_file
destination:
  plugin: entity:user
migration_dependencies:
  required:
    - custom_migration_forum_user_role
  optional:
    - d7_field_instance
    - d7_file
    - language
    - default_language
    - user_picture_field_instance
    - user_picture_entity_display
    - user_picture_entity_form_display

config/install/migrate_plus.migration.filter_format.yml:

id: custom_migration_forum_filter_format
label: Filter format configuration
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: d7_filter_format
process:
  format: format
  name: name
  cache: cache
  weight: weight
  filters:
    plugin: sub_process
    source: filters
    key: '@id'
    process:
      id:
        plugin: filter_id
        bypass: true
        source: name
        map: { }
      settings:
        plugin: filter_settings
        source: settings
      status:
        plugin: default_value
        default_value: true
      weight: weight
destination:
  plugin: entity:filter_format

Теперь можно начинать подготовку плагина для миграции материалов форума.

src/Plugin/migrate/source/Forum.php:

<?php

namespace Drupal\custom_migration_forum\Plugin\migrate\source;

use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Extract forum from Drupal 7 database.
 *
 * @MigrateSource(
 *   id = "custom_migration_forum_forum",
 * )
 */
class Forum extends FieldableEntity {

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager);
    $this->moduleHandler = $module_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $migration,
      $container->get('state'),
      $container->get('entity.manager'),
      $container->get('module_handler')
    );
  }

  /**
   * The join options between the node and the node_revisions table.
   */
  const JOIN = 'n.vid = nr.vid';

  /**
   * {@inheritdoc}
   */
  public function query() {
    // Select node in its last revision.
    $query = $this->select('node_revision', 'nr')
      ->fields('n', [
        'nid',
        'type',
        'language',
        'status',
        'created',
        'changed',
        'comment',
        'promote',
        'sticky',
        'tnid',
        'translate',
      ])
      ->fields('nr', [
        'vid',
        'title',
        'log',
        'timestamp',
      ])
      ->fields('fb', [
        'body_value',
        'body_format',
      ]);

    $query->addField('n', 'uid', 'node_uid');
    $query->addField('n', 'type', 'node_type');
    $query->addField('nr', 'uid', 'revision_uid');
    $query->innerJoin('node', 'n', static::JOIN);
    $query->innerJoin('field_data_body', 'fb', 'n.nid = fb.entity_id');

    // If the content_translation module is enabled, get the source langcode
    // to fill the content_translation_source field.
    if ($this->moduleHandler->moduleExists('content_translation')) {
      $query->leftJoin('node', 'nt', 'n.tnid = nt.nid');
      $query->addField('nt', 'language', 'source_langcode');
    }

    $this->handleTranslations($query);
    // Filtered node type forum.
    $query->condition('n.type', 'forum');

    return $query;
  }

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    // Get Field API field values.
    foreach (array_keys($this->getFields('node', 'forum')) as $field) {
      $nid = $row->getSourceProperty('nid');
      $vid = $row->getSourceProperty('vid');

      $row->setSourceProperty($field, $this->getFieldValues('node', $field, $nid, $vid));
    }
    // Make sure we always have a translation set.
    if ($row->getSourceProperty('tnid') == 0) {
      $row->setSourceProperty('tnid', $row->getSourceProperty('nid'));
    }
    return parent::prepareRow($row);
  }

  /**
   * {@inheritdoc}
   */
  public function fields() {
    $fields = [
      'nid' => $this->t('Node ID'),
      'type' => $this->t('Type'),
      'title' => $this->t('Title'),
      'body_value' => $this->t('Full text of body'),
      'body_format' => $this->t('Format of body'),
      'node_uid' => $this->t('Node authored by (uid)'),
      'revision_uid' => $this->t('Revision authored by (uid)'),
      'created' => $this->t('Created timestamp'),
      'changed' => $this->t('Modified timestamp'),
      'status' => $this->t('Published'),
      'promote' => $this->t('Promoted to front page'),
      'sticky' => $this->t('Sticky at top of lists'),
      'revision' => $this->t('Create new revision'),
      'language' => $this->t('Language (fr, en, ...)'),
      'tnid' => $this->t('The translation set id for this node'),
      'timestamp' => $this->t('The timestamp the latest revision of this node was created.'),
    ];
    return $fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getIds() {
    $ids['nid']['type'] = 'integer';
    $ids['nid']['alias'] = 'n';
    return $ids;
  }

  /**
   * Adapt our query for translations.
   *
   * @param \Drupal\Core\Database\Query\SelectInterface $query
   *   The generated query.
   */
  protected function handleTranslations(SelectInterface $query) {
    // Check whether or not we want translations.
    if (empty($this->configuration['translations'])) {
      // No translations: Yield untranslated nodes, or default translations.
      $query->where('n.tnid = 0 OR n.tnid = n.nid');
    }
    else {
      // Translations: Yield only non-default translations.
      $query->where('n.tnid <> 0 AND n.tnid <> n.nid');
    }
  }

}

И yml-файл к плагину.

config/install/migrate_plus.migration.forum.yml:

id: custom_migration_forum_forum
label: Custom forum migration
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: custom_migration_forum_forum
  node_type: forum
  target: migrate
migration_dependencies:
  required:
    - custom_migration_forum_term
    - custom_migration_forum_user
    - custom_migration_forum_filter_format
process:
  nid: tnid
  vid: vid
  langcode:
    plugin: default_value
    source: language
    default_value: 'en'
  title: title
  type:
    plugin: default_value
    default_value: forum
  'body/value': body_value
  'body/format': body_format
  uid:
      plugin: migration
      migration: custom_migration_forum_user
      source: node_uid
  status: status
  created: created
  changed: changed
  promote: promote
  sticky: sticky
  revision_uid: revision_uid
  revision_log: log
  revision_timestamp: timestamp
  taxonomy_forums:
      plugin: migration
      migration: custom_migration_forum_term
      source: taxonomy_forums
destination:
  plugin: entity:node

Последнее, что нам осталось — это комментарии к темам (как же без них?).
Создаем плагин и описываем его в конфигурации.

src/Plugin/migrate/source/ForumComment.php:

<?php

/**
 * @file
 * Contains \Drupal\custom_migration_forum\Plugin\migrate\source\ForumComment.
 */

namespace Drupal\custom_migration_forum\Plugin\migrate\source;

use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;

/**
 * Drupal 7 comment forum source from database.
 *
 * @MigrateSource(
 *   id = "custom_migration_forum_forum_comment",
 *   source_provider = "comment",
 * )
 */
class ForumComment extends FieldableEntity {

  /**
   * {@inheritdoc}
   */
  public function query() {
    $query = $this->select('comment', 'c')->fields('c');
    $query->innerJoin('node', 'n', 'c.nid = n.nid');
    $query->addField('n', 'type', 'node_type');
    $query->addField('n', 'nid');
    $query->condition('n.type', 'forum');
    $query->orderBy('c.created');

    return $query;
  }

  /**
   * {@inheritdoc}
   */
  public function prepareRow(Row $row) {
    $cid = $row->getSourceProperty('cid');

    $node_type = $row->getSourceProperty('node_type');
    $comment_type = 'comment_node_' . $node_type;
    $row->setSourceProperty('comment_type', 'comment_forum');

    foreach (array_keys($this->getFields('comment', $comment_type)) as $field) {
      $row->setSourceProperty($field, $this->getFieldValues('comment', $field, $cid));
    }

    return parent::prepareRow($row);

  }

  /**
   * {@inheritdoc}
   */
  public function fields() {
    return [
      'cid' => $this->t('Comment ID.'),
      'pid' => $this->t('Parent comment ID. If set to 0, this comment is not a reply to an existing comment.'),
      'nid' => $this->t('The {node}.nid to which this comment is a reply.'),
      'uid' => $this->t('The {users}.uid who authored the comment. If set to 0, this comment was created by an anonymous user.'),
      'subject' => $this->t('The comment title.'),
      'comment' => $this->t('The comment body.'),
      'hostname' => $this->t("The author's host name."),
      'created' => $this->t('The time that the comment was created, as a Unix timestamp.'),
      'changed' => $this->t('The time that the comment was edited by its author, as a Unix timestamp.'),
      'status' => $this->t('The published status of a comment. (0 = Published, 1 = Not Published)'),
      'format' => $this->t('The {filter_formats}.format of the comment body.'),
      'thread' => $this->t("The vancode representation of the comment's place in a thread."),
      'name' => $this->t("The comment author's name. Uses {users}.name if the user is logged in, otherwise uses the value typed into the comment form."),
      'mail' => $this->t("The comment author's email address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on."),
      'homepage' => $this->t("The comment author's home page address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on."),
      'type' => $this->t("The {node}.type to which this comment is a reply."),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function getIds() {
    $ids['cid']['type'] = 'integer';
    return $ids;
  }

}

config/install/migrate_plus.migration.forum_comment.yml:

id: custom_migration_forum_forum_comment
label: Comments forum
migration_group: Custom Migration Forum
dependencies:
  enforced:
    module:
      - custom_migration_forum
source:
  plugin: custom_migration_forum_forum_comment
  target: migrate
  constants:
    entity_type: node
process:
  cid: cid
  pid:
    plugin: migration_lookup
    migration: custom_migration_forum_forum_comment
    source: pid
  entity_id: nid
  entity_type: 'constants/entity_type'
  comment_type: comment_type
  field_name: comment_type
  subject: subject
  uid: uid
  name: name
  mail: mail
  homepage: homepage
  hostname: hostname
  created: created
  changed: changed
  status: status
  thread: thread
  comment_body: comment_body
destination:
  plugin: entity:comment
migration_dependencies:
  required:
    - custom_migration_forum_forum

Начало миграции


Теперь мы готовы к проведению миграции. Добавляем базу данных Drupal 7 в файл конфигурации нашего сайта на Drupal 8. Делаем это в файле settings.php (либо в другом файле, который подключает ваши настройки. У меня это файл settings.local.php ):

<?php

$databases['migrate']['default'] = array (
  'database' => 'Drupal_7',
  'username' => 'root',
  'password' => 'root',
  'prefix' => '',
  'host' => 'localhost',
  'port' => '3306',
  'namespace' => 'Drupal\Core\Database\Driver\mysql',
  'driver' => 'mysql',
);

Включаем следующие модули:

drush en migrate migrate_drupal migrate_plus migrate_tools taxonomy forum -у

Если вы загрузили модуль с GitHub, можете просто включить его:

drush en custom_migration_forum -y

image

Миграция через Drush


Проверяем список доступных миграций:

drush ms

image

Запускаем все наши миграции одной командой:

drush mim --group="Custom Migration Forum"

image

Для того, чтобы отменить изменения миграции, существует команда drush mr:

drush mr --group="Custom Migration Forum"

image

Также при миграции иногда возникают ошибки, и миграция может зависнуть в статусе Importing или Rolling back. Для того чтобы сбросить статус миграции, нужно запустить:

drush php-eval 'var_dump(Drupal::keyValue("migrate_status")->set('custom_migration_forum_forum', 0))'

Где custom_migration_forum_forum — это ID миграции.

Наша миграция форума завершена. В результате мы получили полностью мигрированный форум с пользователями и комментариями к темам.

image

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


  1. afi13
    25.02.2018 17:37

    Добрый день, необязательно использовать hook_uninstall() для удаления конфигов при удалении модуля, достаточно указать enforced зависимость во всемх конфиг-файлах:

    dependencies:
      enforced:
        module:
          - custom_migration_forum
    


    1. helender Автор
      25.02.2018 19:16

      Здравствуйте, спасибо за конструктивное замечание. Я немного подкоректировал статью.