Migrate Field Collection to Paragraphs from Drupal 7 to Drupal 8

Development |

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:

Zoom with default options

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

Zoom with default options

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

RELATED ARTICLES