Migrate Field Collection to Paragraphs from Drupal 7 to Drupal 8
Very often you will find yourself in situation that you need to do migrate from field collection to paragraphs bundles. In this example we will show you how to do on right way. If you reading this and you didn’t do previous migration example with ‘Blog’ nodes, it may be better for you to run through that lesson first.
In this example we will migrate ‘Company’ content type with field collection field bundle ‘Contact’. You can just go on and extend previous module.
First step
Start with vanilla drupal 7 site. Install Field Collection module and create ‘Contact’ bundle. Contact bundle will contain field_website(link), field_email (email/textfield), field_phone(textfield). After that create Company content type with field Contact (field_contact) and assign ‘contact’ bundle to that field (reference field). Add some nodes and export database.
Create vanilla drupal 8 site. Install paragraphs module. Create exactly same bundle as for drupal 7 but use paragraphs module instead of field collections.
You can use docker file from previous migration.Mail and link modules are needed!
It’s time for migration
Folder structure:
As before we are starting from group config file.
Create migrate_plus.migration_group.d7.yml file .
id: d7
label: D7 imports
description: Migration from the legacy D7 site
source_type: Drupal 7
shared_configuration:
source:
key: old_drupal
Source key points on drupal 7 database.
Now we need to separate paragraphs data from content type, make it as two different migrations.
So, we will migrate paragraphs/field collections data first.
Migrate_plus.migration.d7_field_collection_contacts.yml
langcode: en
status: true
dependencies: { }
id: d7_field_collection_contacts
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags:
- 'Drupal 7'
migration_group: d7
label: Contacts
source:
plugin: d7_field_collection_item
key: old_drupal
# field_name is used in our custom plugin to get data about this field_collection_item.
field_name: field_contact
process:
field_email:
plugin: iterator
source: field_email
process:
value: value
revision_id: revision_id
field_phone:
plugin: iterator
source: field_phone
process:
value: value
revision_id: revision_id
field_website:
plugin: iterator
source: field_website
process:
uri: url
title: title
revision_id: revision_id
destination:
plugin: 'entity_reference_revisions:paragraph'
default_bundle: contact
migration_dependencies:
required: { }
optional: { }
Now we need source plugin for Field Collection.
FieldCollection.php
namespace Drupal\custom_migrate\Plugin\migrate\source;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
/**
* d7_field_collection_item source.
*
* @MigrateSource(
* id = "d7_field_collection_item"
* )
*/
class FieldCollection extends FieldableEntity {
/**
* {@inheritdoc}
*/
public function query() {
// Select node in its last revision.
$query = $this->select('field_collection_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;
}
prepareRow() method.
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// If field specified, get field revision ID so there aren't issues mapping.
if(isset($this->configuration['field_name'])) {
$row->setSourceProperty('revision_id', $row->getSourceProperty($this->configuration['field_name'] . '_revision_id'));
}
// Get field API field values.
foreach (array_keys($this->getFields('field_collection_item', $row->getSourceProperty('field_name'))) as $field) {
$item_id = $row->getSourceProperty('item_id');
$revision_id = $row->getSourceProperty('revision_id');
$row->setSourceProperty($field, $this->getFieldValues('field_collection_item', $field, $item_id, $revision_id));
}
return parent::prepareRow($row);
}
Here we’ll need two methods, fields() and getIds() method, like we did in the previous example.
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'item_id' => $this->t('Item ID'),
'revision_id' => $this->t('Revision ID'),
'field_name' => $this->t('Name of field')
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['item_id']['type'] = 'integer';
$ids['item_id']['alias'] = 'fci';
return $ids;
}
We are done with paragraphs data.
Hit drush migrate-status
As you can see here is our migration. You can see migration state, total of data that are ready for migration and etc.
Hit drush migrate:import d7_field_collection_contactsand finish with paragraphs data.
Now you can check paragraph__field_email | paragraph__field_website | paragraph__field_phone tables in drupal 8 site.
Ok, first part is done. Let’s assign these data to Company content type. Create migrate_plus.migration.d7_node_company.yml
langcode: en
status: true
dependencies: { }
id: d7_node_company
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags:
- 'Drupal 7'
- Content
migration_group: d7
label: 'Nodes (Company)'
source:
plugin: d7_node
# key: old_drupal
node_type: company
process:
nid: nid
vid: vid
langcode:
plugin: default_value
source: language
default_value: und
title: title
uid: node_uid
status: status
created: created
changed: changed
promote: promote
sticky: sticky
revision_uid: revision_uid
revision_log: log
revision_timestamp: timestamp
body:
plugin: iterator
source: body
process:
value: value
format:
-
plugin: static_map
bypass: true
source: format
map:
- null
-
plugin: skip_on_empty
method: process
-
plugin: migration
migration:
- d6_filter_format
- d7_filter_format
source: format
field_contact:
-
plugin: skip_on_empty
method: process
source: field_contact_new
-
plugin: migration_lookup
migration: d7_field_collection_contacts
no_stub: true
-
plugin: iterator
process:
target_id: '0'
target_revision_id: '1'
destination:
plugin: 'entity:node'
default_bundle: company
migration_dependencies:
# required:
## - d7_user
## - d7_node
optional:
- d7_field_instance
- d6_filter_format
- d7_filter_format
Now we need one more step. We need to change the value of source field_contact in a hook_migrate_MIGRATION_ID_prepare_row in our custom module's .module file; this is done because we need to pass the values to the migration_lookup plugin keyed with item_id.
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
/**
* Implements hook_migrate_MIGRATION_ID_prepare_row().
*/
function custom_migrate_migrate_d7_node_company_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_new', $value_new);
}
}
That’s all, now we can import nodes from old database to new one.
drush migrate:import d7_node_company
Download code:
Migrate field collections from Drupal 7 to Paragraphs in drupal 8
