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