I am in the process of porting a non Drupal database to Drupal 9.
The original database contains, among other things, a table of Books (with the usual infos) and and separate table (don't ask me why) for Travel Books. The former is 10K records, and was successfully migrated from a books.csv to Drupal. The latter (travelbooks.csv) is just 200 records.
What is interesting (sic!) is that most of Travel Books are also in the Books table, with some new fields in Travel Books table, notably a country and the number of pages (sic again!).
In Drupal we have a taxonomy for type_of_book with things like romance, adventure, true stories, crime, poetry etc.. as well as travel book.
Books from books.csv are already in Drupal, with new Nodes and the correct taxonomy terms applied to the field field_book_type which, of course, it's a multi-value field.
Now I want to migrate travelbooks.csv in Drupal but:
- if a book already exists (looking up with title, since the IDs in the original tables were different!), just update the node in Drupal, adding the taxonomy term
travel_book
- if a book does not exist in Drupal, create it and just set the taxonomy term
travel_book
So far I've been able to do this via a custom Destination plugin, and not just with a migration configuration YAML.
THis is what I hoped to use:
uuid: d66124cf-232b-4080-911c-1d26aefd0246
langcode: en
status: true
dependencies: { }
id: quote_fornitore_voucher_csv_import
class: null
field_plugin_method: null
cck_plugin_method: null
migration_tags: null
migration_group: books
label: 'Import travel books'
source:
plugin: csv
path: /Users/walter/travel_books.csv
delimiter: ','
enclosure: '"'
header_offset: 0
ids:
- id
fields:
-
name: id
label: 'Unique Id'
-
name: book_title
-
name: country
-
name: pages
-
name: synopsis
.....
constants:
travel_book: 'Travel Book'
process:
type:
plugin: default_value
default_value: book
field_pages: pages
field_country: country
title: book_title
field_book_type:
plugin: entity_lookup
entity_type: taxonomy_term
bundle: book_type
bundle_key: vid
source: constants/travel_book
nid:
plugin: entity_generate
entity_type: node
bundle: book
bundle_key: type
value_key: title
source: book_title
access_check: 0
destination:
plugin: 'entity:node'
overwrite_properties:
- field_pages
- field_country
migration_dependencies: null
I have two problems with this migration:
- Even if
entity_generate finds the correct existing node for the current book, the Migration Engine still tries to CREATE a new node, with the same NID, and of course fails with a big red SQL Error
23000]: Integrity constraint violation: 1062 Duplicate entry '2662' for key 'PRIMARY': INSERT INTO "node" ("nid", "vid", "type", "uuid", "langcode") VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4); Array
- I overcome this, only not to be able to add to an existing Book the correct taxonomy term to the list of
field_book_type.
I traced the problem with 1 to be that the Entity has enforceIsNew set to True. Or at least that's what I've found. I've written a destination plugin that, based on a new configuration, can force the newly created entity with enforceIsNew = FALSE and it seems to work as expected. Travel books not found in the Books i nDrupal are created, while the one already existing are updated, with the fields pages and country updated.
This is the relevant code, I think.
abstract class EntityBase implements EntityInterface {
...
public function isNew() {
return !empty($this->enforceIsNew) || !$this->id();
}
...
Even though there is an ID, which is being set by the nid process, enforceIsNew is set to TRUE and so isNew() returns True.
<?php
namespace Drupal\migrate_books\Plugin\migrate\destination;
use Drupal\Core\Entity\Entity;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateDestination(
* id = "update_node"
* )
*/
class UpdateNodeDestination extends EntityContentBase {
/** @var string $entityType */
public static $entityType = 'node';
protected function updateEntity(EntityInterface $entity, Row $row) {
$entity = parent::updateEntity($entity, $row);
//check parameter..
if(!empty($this->configuration['prefer_update']) && $this->configuration['prefer_update']){
// force NOT to enforce NEW. This will avoid inserting twice nodes already present (which will result
// in an integrity violation from MySQL
$entity->enforceIsNew(FALSE);
}
return $entity;
}
}
and the YAMLS is changed with my plugin and the new param prefer_update
destination:
plugin: update_node
prefer_update: true
overwrite_properties:
- field_pages
- field_country
but it seems to me that it should be simpler than that. I am obviously missing something. I am sure this is a pretty standard use case, so it could/should be doable entirely in YAML.
Secondly, I am still not able to add the correct book_type to the list of existing tags.
If I add field_book_type to overwrite_properties, of course it gets overwritten and we lose all the previous terms.
I don't seem a way to update by adding a value to a multi value field.
Any help?
Thanks
W