Migrate nodes from Drupal 7 to custom entity in Drupal 8
In this section we will show you how to migrate nodes from drupal 7 into a custom content entity in drupal 8. If you didn’t run through previous lessons I would suggest you to do it first.
For this lesson you can use the same docker-compose.yml file as previous.
Let’s start
Title (default)
Body (default)
field_image
field_price (integer)
field_isbn (integer)
Build up vanilla drupal 7 site. Create book content type with fields listed below:
Create few nodes of book content type so you have enough stuff for migration. Export database.sql file.
Now you have to install vanilla drupal 8 site. Make sure your drupal console works correctly. We will use drupal console to generate module and custom content entity.
Jump into php container and type ‘drupal’ if you don’t have drupal console installed you will see message like this one:
Just type ‘composer require drupal/console:~1.0 --prefer-dist --optimize-autoloader’ and installation will start.
Now when you type ‘drupal’ the commands for auto generate should be listed.
Ok, we are ready to continue. Create new module storage_module. You can do it easy by typing drupal generate:module or just drupal gm (alias).
This module we will use for our custom content entity. When module is created, we are ready to create custom content entity.
Also we will use drupal console for that job. Just type drupal generate:entity:content or drupal geco and generate book custom entity.
When entity is created we need to modify entity fields. Make the same fields as for book content type in drupal 7.
Book.php is the main file for our custom content entity, all you need is to modify fields in this file.
Here is how it should looks like:
class Book extends RevisionableContentEntityBase implements BookInterface { use EntityChangedTrait; /** * {@inheritdoc} */ public static function preCreate(EntityStorageInterface $storage_controller, array &$values) { parent::preCreate($storage_controller, $values); $values += [ 'user_id' => \Drupal::currentUser()->id(), ]; } /** * {@inheritdoc} */ protected function urlRouteParameters($rel) { $uri_route_parameters = parent::urlRouteParameters($rel); if ($rel === 'revision_revert' && $this instanceof RevisionableInterface) { $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); } elseif ($rel === 'revision_delete' && $this instanceof RevisionableInterface) { $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); } return $uri_route_parameters; } /** * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); foreach (array_keys($this->getTranslationLanguages()) as $langcode) { $translation = $this->getTranslation($langcode); // If no owner has been set explicitly, make the anonymous user the owner. if (!$translation->getOwner()) { $translation->setOwnerId(0); } } // If no revision author has been set explicitly, make the book owner the // revision author. if (!$this->getRevisionUser()) { $this->setRevisionUserId($this->getOwnerId()); } } /** * {@inheritdoc} */ public function getName() { return $this->get('name')->value; } /** * {@inheritdoc} */ public function setName($name) { $this->set('name', $name); return $this; } /** * {@inheritdoc} */ public function getImage() { return $this->get('image')->value; } /** * {@inheritdoc} */ public function setImage($name) { $this->set('image', $name); return $this; } /** * {@inheritdoc} */ public function getNotes() { return $this->get('notes')->value; } /** * {@inheritdoc} */ public function setNotes($name) { $this->set('notes', $name); return $this; } /** * {@inheritdoc} */ public function getIsbn() { return $this->get('isbn')->value; } /** * {@inheritdoc} */ public function setIsbn($name) { $this->set('isbn', $name); return $this; } /** * {@inheritdoc} */ public function getPrice() { return $this->get('price')->value; } /** * {@inheritdoc} */ public function setPrice($name) { $this->set('price', $name); return $this; } /** * {@inheritdoc} */ public function getCreatedTime() { return $this->get('created')->value; } /** * {@inheritdoc} */ public function setCreatedTime($timestamp) { $this->set('created', $timestamp); return $this; } /** * {@inheritdoc} */ public function getOwner() { return $this->get('user_id')->entity; } /** * {@inheritdoc} */ public function getOwnerId() { return $this->get('user_id')->target_id; } /** * {@inheritdoc} */ public function setOwnerId($uid) { $this->set('user_id', $uid); return $this; } /** * {@inheritdoc} */ public function setOwner(UserInterface $account) { $this->set('user_id', $account->id()); return $this; } /** * {@inheritdoc} */ public function isPublished() { return (bool) $this->getEntityKey('status'); } /** * {@inheritdoc} */ public function setPublished($published) { $this->set('status', $published ? TRUE : FALSE); return $this; } /** * {@inheritdoc} */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields = parent::baseFieldDefinitions($entity_type); $fields['user_id'] = BaseFieldDefinition::create('entity_reference') ->setLabel(t('Authored by')) ->setDescription(t('The user ID of author of the Book entity.')) ->setRevisionable(TRUE) ->setSetting('target_type', 'user') ->setSetting('handler', 'default') ->setTranslatable(TRUE) ->setDisplayOptions('view', [ 'label' => 'hidden', 'type' => 'author', 'weight' => 0, ]) ->setDisplayOptions('form', [ 'type' => 'entity_reference_autocomplete', 'weight' => 5, 'settings' => [ 'match_operator' => 'CONTAINS', 'size' => '60', 'autocomplete_type' => 'tags', 'placeholder' => '', ], ]) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); $fields['image'] = BaseFieldDefinition::create('image') ->setLabel(t('Image')) ->setRequired(TRUE) ->setDisplayOptions('view', [ 'label' => 'above', 'type' => 'image', 'weight' => 0, ]) ->setDisplayOptions('form', [ 'type' => 'image_image', 'weight' => 0, ]) ->setRequired(FALSE); $fields['name'] = BaseFieldDefinition::create('string') ->setLabel(t('Name')) ->setDescription(t('The name of the Book entity.')) ->setRevisionable(TRUE) ->setSettings([ 'max_length' => 50, 'text_processing' => 0, ]) ->setDefaultValue('') ->setDisplayOptions('view', [ 'label' => 'above', 'type' => 'string', 'weight' => -4, ]) ->setDisplayOptions('form', [ 'type' => 'string_textfield', 'weight' => -4, ]) ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE) ->setRequired(TRUE); $fields['isbn'] = BaseFieldDefinition::create('integer') ->setLabel(t('ISBN')) ->setDescription(t('ISBN of the Book')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setDisplayOptions('form', array( 'type' => 'string_textfield', 'settings' => array( 'display_label' => TRUE, ), )) ->setDisplayOptions('view', array( 'label' => 'hidden', 'type' => 'string', )) ->setDisplayConfigurable('form', TRUE) ->setRequired(FALSE); $fields['price'] = BaseFieldDefinition::create('float') ->setLabel(t('Price')) ->setDescription(t('Price of the Book')) ->setRevisionable(TRUE) ->setTranslatable(TRUE) ->setDisplayOptions('form', array( 'type' => 'string_textfield', 'settings' => array( 'display_label' => TRUE, ), )) ->setDisplayOptions('view', array( 'label' => 'hidden', 'type' => 'string', )) ->setDisplayConfigurable('form', TRUE) ->setRequired(FALSE); $fields['notes'] = BaseFieldDefinition::create('string_long') ->setLabel(t('Notes')) ->setDescription(t('Example of string_long field.')) ->setDefaultValue('') ->setRequired(FALSE) ->setDisplayOptions('view', [ 'label' => 'visible', 'type' => 'basic_string', 'weight' => 5, ]) ->setDisplayOptions('form', [ 'type' => 'string_textarea', 'weight' => 5, 'settings' => ['rows' => 4], ]) ->setDisplayConfigurable('view', TRUE) ->setDisplayConfigurable('form', TRUE); $fields['status'] = BaseFieldDefinition::create('boolean') ->setLabel(t('Publishing status')) ->setDescription(t('A boolean indicating whether the Book is published.')) ->setRevisionable(TRUE) ->setDefaultValue(TRUE) ->setDisplayOptions('form', [ 'type' => 'boolean_checkbox', 'weight' => -3, ]); $fields['created'] = BaseFieldDefinition::create('created') ->setLabel(t('Created')) ->setDescription(t('The time that the entity was created.')); $fields['changed'] = BaseFieldDefinition::create('changed') ->setLabel(t('Changed')) ->setDescription(t('The time that the entity was last edited.')); return $fields; } }
At the end of the day you can use my code from bitbucket.
To be sure that everything is ok go to /admin/structure/book and add some books.
Now we are ready for migration.
Create new module entity_migrate
As before, we need migration group migrate_plus.migration_group.yml
File migration migrate_plus.migration.book_file.yml
And node migration migrate_plus.migration.custom_book
migrate_plus.migration_group.d7.yml
id: d7 label: D7 imports description: Migrations importing from the legacy D7 ya_example site source_type: Drupal 7 shared_configuration: source: key: old_drupal
migrate_plus.migration.book_file.yml
# Every migration that references a file by Drupal 7 fid should specify this # migration as an optional dependency. id: book_file label: d7 blog files audit: true migration_group: d7 migration_tags: - Drupal 7 - Content source: plugin: d7_file scheme: public constants: # The tool configuring this migration must set source_base_path. It # represents the fully qualified path relative to which URIs in the files # table are specified, and must end with a /. See source_full_path # configuration in this migration's process pipeline as an example. source_base_path: 'sites/default/files/migrate_files' process: # If you are using this file to build a custom migration consider removing # the fid field to allow incremental migrations. fid: fid filename: filename source_full_path: - plugin: concat delimiter: / source: - constants/source_base_path - filepath - plugin: urlencode uri: plugin: file_copy source: - '@source_full_path' - uri filemime: filemime # No need to migrate filesize, it is computed when file entities are saved. # filesize: filesize status: status # Drupal 7 didn't keep track of the file's creation or update time -- all it # had was the vague "timestamp" column. So we'll use it for both. created: timestamp changed: timestamp uid: uid destination: plugin: entity:file
As before, pay attention on
source_base_path: 'sites/default/files/migrate_files
migrate_files is folder in drupal 8 where we have to copy whole site directory from drupal 7.
Folder structure:
migrate_plus.migration.custom_book.yml
id: custom_book label: Custom book node migration from Drupal 7 migration_group: d7 dependencies: enforced: module: - entity_migrate source: plugin: d7_node node_type: book destination: plugin: entity:book process: id: nid vid: vid type: type langcode: plugin: static_map bypass: true source: language map: und: en name: title status: status created: created changed: changed promote: promote sticky: sticky notes: body price: field_price image: plugin: iterator source: field_image process: target_id: plugin: migration_lookup migration: book_file source: fid alt: title title: title height: height width: width isbn: field_isbn
The field mapping is really simple 1:1
Custom entity fields:
name (in drupal 7 it is title)
notes (in drupal 7 it is body field)
image (in drupal 7 it is field_image)
price (in drupal 7 it is field_price)
isbn (in drupal 7 it is field_isbn)
Now we are ready for migration
Type drush migrate-status
First thing we are gonna migrate is book_file.
Run migration by typing drush migrate:import book_file.
After that finish migration with drush migrate:import custom_book.
That’s all, have a fun with migration.
You can find source code here:
Drupal 8 custom content entity migration