By resorting to YAML files for configuration, Drupal 8 achieved a huge boost in flexibility and understandability of the parameters. Even more it is useful for such a “commonly uncommon” task as migration.
Indeed, in D7 you had a set of forms to match source to destination, using some limited set of processing options. Now, in D8, these three stages of migration are totally separated out, unleashing a (nearly) unlimited power of building processing pipes and referencing other migrations.
However, there are tasks, which are, intuitively, often requested, but cannot be achieved in the pre-defined set of plugins. In the upcoming series of posts I am going to present my solutions to some of these tasks. Today I start with one of the most common: taxonomy term lookups.
Here is the process plugin, that does the “text to taxonomy term” conversion:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
<?php namespace Drupal\your_module\Plugin\migrate\process; use Drupal\migrate\MigrateExecutableInterface; use Drupal\migrate\ProcessPluginBase; use Drupal\migrate\Row; use Drupal\taxonomy\Entity\Term; use Drupal\Core\Language\LanguageInterface; /** * This plugin creates a new paragraph entity based on the source. * * @MigrateProcessPlugin( * id = "text_to_taxonomy" * ) */ class TextToTaxonomy extends ProcessPluginBase { /** * The main function for the plugin, actually doing the data conversion. */ public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) { $field = empty($this->configuration['field']) ? 'value' : $this->configuration['field']; $termName = FALSE; /* * Depending on the context (whether the plugin is called * as a part of an Iterator process, a pipe, or takes its * sources from multiple-input Get plugin), we have to check * for all value placement variants. **/ if (isset($value[$field])) { $termName = $value[$field]; } elseif (!empty($row->getSourceProperty($field))) { $termName = $row->getSourceProperty($field); } elseif (is_string($value)) { $termName = $value; } if (!$termName) { // return FALSE if nothing found return $termName; } // Getting a term by lookup (if it exists), or creating one $term = $this->getTerm($termName, $row, $this->configuration['vocabulary']); // Save it $term->save(); // Yes, all we need is ID. return $term->id(); } /** * Creates a new or returns an existing term for the target vocabulary. * * @param string $name * The value. * @param Row $row * The source row. * @param string $vocabulary * The vocabulary name. * * @return Term * The term. */ protected function getTerm($name, Row $row, $vocabulary) { // Attempt to fetch an existing term. $properties = []; if (!empty($name)) { $properties['name'] = $name; } $vocabularies = taxonomy_vocabulary_get_names(); if (isset($vocabularies[$vocabulary])) { $properties['vid'] = $vocabulary; } else { // Return NULL when filtering by a non-existing vocabulary. return NULL; } $terms = \Drupal::getContainer()->get('entity.manager')->getStorage('taxonomy_term')->loadByProperties($properties); $term = reset($terms); if (!empty($term)) { if ($row->getDestinationProperty('langcode') && $row->getDestinationProperty('langcode') != LanguageInterface::LANGCODE_NOT_SPECIFIED) { if (!$term->hasTranslation($row->getDestinationProperty('langcode'))) { $term->addTranslation($row->getDestinationProperty('langcode')); } $term = $term->getTranslation(); } return $term; } // Fallback, create a new paragraph. $term = Term::create($properties); if ($row->getDestinationProperty('langcode')) { $term->langcode = $row->getDestinationProperty('langcode'); } return $term; } } |
It can be used as follows:
1 2 3 4 5 6 7 |
plugin: iterator source: some_source process: target_id: - plugin: text_to_taxonomy vocabulary: your_vocabulary_name |
Or like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
plugin: iterator process: target_id: - plugin: static_map source: name map: Music: Music Dance: Dance Drama: Drama 'Liberal Arts': null - plugin: skip_on_empty method: process - plugin: text_to_taxonomy vocabulary: division field: name |
There is one drawback – the created terms are not tracked for migration reversion. Please keep that in mind when using.