B A C K
How to Migrate Paragraphs from Drupal 7 to Drupal 8

How to Migrate Paragraphs from Drupal 7 to Drupal 8

Knowledge

A few days ago, we encountered an exciting challenge: migrating paragraphs from a Drupal 7 platform to Drupal 8. While searching for the right solution, we scoured the web but found nothing that perfectly fit our needs. As a result, our backend developers created a custom solution. Special thanks to mtech-llc.com and Ada Hernández, whose work on field collection migration inspired this solution.

While Ada's original article describes migrating field collections, we adapted the approach for paragraphs migration. Her insights proved invaluable to our process.

Before proceeding, ensure you have:

  1. A functional Drupal 8 installation.
  2. The following modules installed: migrate_drupal, migrate_plus, migrate_tools, migrate_drupal_ui. Alternatively, add these as dependencies to your custom module.

After installing the required modules, create a paragraph with bundle "contact" containing these fields:

  • Text plain (machine name: field_name)
  • Email (machine name: field_email)
  • Number (Integer) (machine name: field_phone)

Then create a content type called Organization (machine name: organization) with:

  • Default body field.
  • Field paragraph type (machine name: field_contact).

This procedure is complex and requires careful attention to detail. If errors occur during any step, you may need to start over. Please read each step thoroughly before proceeding.

1. Specify Database Connection in settings.php

Add the second connection to Drupal 7 in your settings.php file. In this example, the database name is "drupal_7_56":

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

2. Create a Custom Module

Create a custom module with the machine name "custom".

3. Create YML Templates

Place the following YML templates in the config/install folder.

First, create the paragraph migration template (filename: migrate_plus.migration.d7_paragraph_contact.yml):

langcode: en
status: true
dependencies: {}
id: d7_paragraph_contact
migration_tags:
  - 'Drupal 7'
migration_group: migrate_drupal_7
label: Contacts
source:
  plugin: d7_paragraph_item
  key: migrate
  field_name: field_contact
process:
  field_name:
    plugin: iterator
    source: field_name
    process:
      value: value
  revision_id: revision_id
  field_email:
    plugin: iterator
    source: field_email
    process:
      value: email
  field_phone:
    plugin: iterator
    source: field_phone
    process:
      value: value
destination:
  plugin: 'entity_reference_revisions:paragraph'
  default_bundle: contact
migration_dependencies:
  required: {}
  optional: {}

Then create the node migration template (filename: migrate_plus.migration.d7_node_organization.yml):

langcode: en
status: true
id: d7_node_organization
migration_tags:
  - 'Drupal 7'
  - Content
migration_group: migrate_drupal_7
label: 'Nodes (Organization)'
source:
  plugin: d7_node
  node_type: organization
process:
  nid: nid
  vid: vid
  title: title
  uid: node_uid
  status: status
  created: created
  changed: changed
  field_contact:
    - plugin: skip_on_empty
      method: process
      source: field_contact
    - plugin: migration_lookup
      migration: d7_paragraph_contact
      no_stub: true
    - plugin: iterator
      process:
        target_id: '0'
        target_revision_id: '1'
destination:
  plugin: 'entity:node'
  default_bundle: organization
migration_dependencies:
  required: {}
  optional: {}

4. Create the Source Plugin Class

Create a file named ContactParagraph.php in the /src/Plugin/migrate/source directory:

<?php
namespace Drupal\mparagraf\Plugin\migrate\source;

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

/**
 * D7_paragraph_item source.
 *
 * @MigrateSource(
 *   id = "d7_paragraph_item"
 * )
 */
class ContactParagraph extends FieldableEntity {

  public function query() {
    $query = $this->select('paragraphs_item', 'fci')
      ->fields('fci', ['item_id', 'field_name', 'revision_id']);
    if (isset($this->configuration['field_name'])) {
      $query->innerJoin('field_data_' . $this->configuration['field_name'], 'fd',
        'fd.' . $this->configuration['field_name'] . '_value = fci.item_id');
      $query->fields('fd', ['entity_type', 'bundle', 'entity_id',
        $this->configuration['field_name'] . '_revision_id']);
      $query->condition('fci.field_name', $this->configuration['field_name']);
    }
    return $query;
  }

  public function prepareRow(Row $row) {
    if (isset($this->configuration['field_name'])) {
      $row->setSourceProperty('revision_id',
        $row->getSourceProperty($this->configuration['field_name'] . '_revision_id'));
    }
    foreach (array_keys($this->getFields('paragraphs_item', 'contact')) as $field) {
      $item_id = $row->getSourceProperty('item_id');
      $revision_id = $row->getSourceProperty('revision_id');
      $row->setSourceProperty($field,
        $this->getFieldValues('paragraphs_item', $field, $item_id, $revision_id));
    }
    return parent::prepareRow($row);
  }

  public function fields() {
    return [
      'item_id'     => $this->t('Item ID'),
      'revision_id' => $this->t('Revision ID'),
      'field_name'  => $this->t('Name of field'),
    ];
  }

  public function getIds() {
    $ids['item_id']['type']  = 'integer';
    $ids['item_id']['alias'] = 'fci';
    return $ids;
  }
}

5. Implement the Migration Hook

Add the following code to your module's .module file:

<?php
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Row;

/**
 * Implements hook_migrate_MIGRATION_ID_prepare_row().
 */
function custom_migrate_d7_node_organization_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
  $values = $row->getSourceProperty('field_contact');
  $value_new = [];
  if ($values) {
    foreach ($values as $value) {
      $value_new[] = ['item_id' => $value['value']];
    }
    $row->setSourceProperty('field_contact', $value_new);
  }
}

6. Final Steps

Once you've completed all the above steps successfully, you can run your migration and watch as your paragraphs are transferred from Drupal 7 to Drupal 8. Take a moment to verify the results before proceeding with any additional paragraph-related tasks.

Remember to test thoroughly in a development environment before attempting this migration on a production site.