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