Migrate in core is among my favorite parts of Drupal 8 and 9. The framework is super flexible, and it makes migrating content from any source you can dream up pretty straight forward. Today I want to show a trick that I use when I receive a csv (or Excel file) from clients, where they want all of the contents in it migrated to Drupal. One very simple example would be a list of categories.
Typically the file will come with one term on each line. However, migrate would want us to set an ID for all of the terms, which currently none of the rows have. One solution to this is to place an ID on all of the rows manually with some sort of spreadsheet software, and then point our migration to the new column for its IDs. But since that both involves the words "manual" and "spreadsheet software" it immediately makes me want to find another solution. Is there a way we can set the row id programmatically based on the row number instead? Why, yes, it is!
So, here is a trick I use to set the ID from the line number:
The migration configuration looks something like this:
id: my_module_categories_csv
label: My module categories
migration_group: my_module
source:
# We will use a custom source plugin, so we can set the
# ID from there.
plugin: my_module_categories_csv
track_changes: TRUE
header_row_count: 1
keys:
- id
delimiter: ';'
# ... And the rest of the file
As stated in the yaml file, we will use a custom source plugin for this. Let's say we have a custom module called "my_module". Inside that module folder, we create a file called Categories Csv.php inside the folder src/Plugin/migrate/source/CategoriesCsv.php. And in that file we put something like this:
<?php
namespace Drupal\my_module\Plugin\Migrate\source;
use Drupal\migrate\Plugin\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_source_csv\Plugin\migrate\source\CSV;
/**
* Source plugin for Categories in csv.
*
* @MigrateSource(
* id = "my_module_categories_csv"
* )
*/
class CategoriesCsv extends CSV {
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Delta is here the row number.
$delta = $this->file->key();
$row->setSourceProperty('id', $delta);
return parent::prepareRow($row);
}
}
In the code above we set the source property of id to the delta (the row number) of the row. Which means you can have a source like this:
Name
Category1
Category2
Category3
Instead of this
id;Name
1;Category1
2;Category2
3;Category3
The best part of this is that when your client changes their mind, you can just update the file instead of editing it before updating it. And with editing, I mean "manually" and with "spreadsheet software". Yuck.
To finish this post, here is an animated gif called "spreadsheet software yuck"
DLatty-NJT•Friday, Mar 19th 2021 (over 3 years ago)
eiriksm•Friday, Mar 19th 2021 (over 3 years ago)
The id in question is the migration row Id. In the article it has no relationship to the node id. You have to have a migration row Id to be able to re-run migrations and only migrate changes, for example. Sorry if that was not clear 😬🤓
batigolix•Monday, Feb 28th 2022 (over 2 years ago)
I had to changed "keys" for "ids".:
`You must declare "ids" as a unique array of fields in your source settings.`
This may be because of recent changes in the csv source plugin or drupal 9 core migrate.
Also the ID you set in prepareRow() is not recognized. The migration fails before prepareRow() because it looks for a ID column in the CSV :
`[error] Migratie mislukt met bronplugin-melding: 'my_id' is defined as a source ID but has no value. in /var/www/html/web/core/modules/migrate/src/Row.php regel 107`
(apologies for the partial dutch message)
eiriksm•Wednesday, Mar 2nd 2022 (over 2 years ago)
AIeksandr•Saturday, Nov 25th 2023 (10 months ago)
```
id: my_module_categories_csv
label: My module categories
migration_group: my_module
source:
# We will use a custom source plugin, so we can set the
# ID from there.
plugin: my_module_categories_csv
header_row_count: 1
ids:
- id
delimiter: ';'
# ... And the rest of the file
```
```
namespace Drupal\my_module\Plugin\Migrate\source;
use Drupal\migrate_source_csv\Plugin\migrate\source\CSV;
/**
* Source plugin for Categories in csv.
*
* @MigrateSource(
* id = "my_module_categories_csv"
* )
*/
class CategoriesCsv extends CSV {
private $row = 0;
/**
* {@inheritdoc}
*/
public function next() {
$this->configuration[end($this->configuration['ids'])] = ++$this->row;
parent::next();
}
}
```
Do you want to comment?
This article uses github for commenting. To comment, you can visit https://github.com/eiriksm/eiriksm.dev-comments/issues/10.